論文筆記《Improving Docker Registry Design based on Production Workload Analysis》
會議:File and Storage Technologies(FAST)
時間:2018-12-15
思維導圖
梗概
爲了更好地研究 docker 註冊表服務對 docker 的作用,本文基於75天的時間內從五個託管生產級註冊表的IBM數據中心收集的跟蹤信息,對註冊表工作進行分析。
本文編寫了一個跟蹤重播器來進行分析,推斷出許多關於容器工作負載的關鍵點,例如請求類型分佈,訪問模式和響應時間。基於這些關鍵點,本文得出註冊表的設計優化點,並提高了註冊表的性能。
追蹤器和重播器都是開源的,網址如下:
文檔:https://dssl.cs.vt.edu/drtp/
github:https://github.com/chalianwar/docker-performance
1. 介紹
本文對一個真實的Docker註冊表工作負載進行了大規模和全面的分析。
首先從IBM Cloud container registry service中的五個數據中心收集大跨度的生產級跟蹤。
跟蹤覆蓋了註冊中心服務的所有可用區域和許多組件,在75天的時間裏,總共有超過3800萬的請求和超過181.3 TB的數據傳輸
通過跟蹤和分析數據,初步發現:
- 與push操作相比,工作負載是高度讀密集型的,包括90- 95%的pull操作
- 不同註冊中心的特徵不同。工作負載不僅取決於註冊的目的,還取決於註冊中心服務的年齡;較舊的註冊中心服務在訪問模式和圖像流行方面顯示出更可預測的趨勢。
- 觀察到25%的請求是針對前10個庫的,12%的請求是針對前10個層的。此外,註冊中心將95%的時間花在從後端對象存儲中獲取圖像內容上。
根據這些發現,提出了幾個容器註冊表服務的設計指示
2. 背景知識
每一層都有一個稱爲digest的內容可尋址標識符,該標識符通過獲取其數據的抗碰撞散列(默認爲SHA256)來唯一地標識一層。這使得Docker能夠有效地檢查兩個層是否相同,並對它們進行重複數據刪除,以便在不同的圖像之間共享。
2.1 Docker Registry
本文使用 Docker Registry V2 的 Rest API 做測試;分別包括 pull 和 push 操作
push與pull的工作順序相反。在本地創建清單之後,守護進程首先將所有層推入註冊表,然後將manifest推入註冊表。
下表爲論文中提及的 Docker Registry Rest API
http方法 | URL | 作用 |
---|---|---|
GET | {name}/manifests/{tag} | 獲取圖像清單,其中name 定義用戶和存儲庫名稱,而tag 定義圖像標籤。 |
HEAD | {name}/blobs/{digest} | 檢查註冊表中是否有可用的層 |
GET | {name}/blobs/{digest} | 拉取壓縮的層tar文件 |
HEAD | {name}/blobs/{digest} | 檢查一個層是否已經存在於註冊表中 |
POST | {name}/blobs/uploads/ | 若層不在,使用此URL進行上傳開始操作,其返回一個URL包含一個唯一的上傳標識符(uuid ),客戶端可以使用它來傳輸實際的層數據。 |
PUT | {name}/blobs/upload/{uuid} | 使用單片上傳整個層數據 |
PATCH | {name}/blobs/upload/{uuid} | 使用分塊策略上傳層數據,請求在頭部指定一個字節範圍以及blob的相應部分 |
PUT | {name}/manifests/{digest} | 上傳完所有layers後,上傳 manifest 文件信息 |
Docker 在上傳層數據時可以選擇使用單片或分塊傳輸上傳層。單片傳輸上載在一個PUT請求中裝載一層的全部數據。爲了進行分塊傳輸,Docker使用PATCH <name>/blobs/upload /<uuid>
請求在頭部指定一個字節範圍以及blob的相應部分。
2.2 IBM 雲容器 Registry
IBM的租戶衆多,並且其V2版本是基於開源 Docker Registry 搭建的。
其包含超過十八個組件,本文中主要追蹤了三個組件,如下圖所示:
- Nginx:負載均衡器。跟蹤它可以提供響應時間信息
- Registry:使用OpenStack Swift作爲後端對象存儲。跟蹤它可以提供關於請求分佈的信息
- Broadcaster:提供註冊表事件過濾和分發,例如,它會在推送新鏡像時通知其他的漏洞能力顧問組件。跟蹤它可以研究層的大小
儘管Nginx會進行log記錄,但是本文對Registry和Broadcaster也進行了日誌收集。
不僅如此,本文還介紹了 IBM 容器倉庫分佈於五個地理位置:Dallas (dal), London (lon), Frank- furt (fra), Sydney (syd), 和 Montreal。
每個不同地點的容器倉庫提供不同的功能,如面向客戶的,作爲開發和測試用的,面向內部員工的,等等;並且根據功能的不同,其倉庫規模也不同,不同的容器倉庫其使用年齡也不同
每個可用地區(AZ)都有一個單獨的控制平面和入口路徑,但是後端組件,例如對象存儲,是共享的。這意味着AZ是完全網絡隔離的,但是在AZ之間共享映像。
3. 跟蹤方法
本文獲得了 IBM 雲倉庫的系統日誌服務的訪問權限,除了收集追蹤信息,本文還開發了追蹤回放器用於評估其他指標
3.1 日誌服務
IBM 雲容器日誌服務是由 ELK 技術棧進行管理
- ElasticSearch
- Logstash
- Kibana
每個服務器上的Logstash代理將日誌發送到一個集中的日誌服務器,在那裏它們被索引並添加到ElasticSearch集羣中。
每個AZ有自己的ElasticSearch集羣,每日日誌數據達2TB,這包括系統使用情況、運行狀況信息、來自不同組件的日誌等。收集的數據按時間排序。
3.2 收集數據
通過在ElasticSearch 中對三個組件的日誌收集,收集了和鏡像推拉有關的所有請求。從2017-6-20 到 2017-9-2 ,得到了表1 的數據
通過移除重複項和過濾數據,剔除掉了22.4G的匿名追蹤數據
下圖就是單次匿名追蹤的記錄格式
3.3 跟蹤重播器
主要介紹了 跟蹤重播器 的架構,用途,行爲方式和作用影響
跟蹤重播器架構如下圖所示:
跟蹤重播器作用主要是將收集來的追蹤信息,通過重播的方式進行再現。各組件的行爲如下:
-
Master:master 節點一次解析一個請求的匿名跟蹤文件,並將其轉發給其中一個客戶端。請求以輪詢方式或散列http.request跟蹤中的remoteaddr字段轉發給客戶端。
通過使用散列,跟蹤回放器維護請求的位置,以確保與一個鏡像推或拉對應的所有HTTP請求都是由相同的客戶端節點生成的,就像原始註冊中心服務所看到的那樣。
-
Client:客戶端負責向 Registry 發出HTTP請求。
對於所有PUT層請求,客戶端都會生成一個具有相應大小的隨機文件,並將其傳輸到註冊表。 由於新生成的文件的內容與在跟蹤中看到的層的內容不同,因此兩者的digest / SHA256將有所不同。 因此,在成功完成請求後,客戶端將以請求延遲以及新生成的文件的摘要回復主服務器。 Master 維護一個跟蹤中的摘要及其對應的新生成的摘要之間的映射。 對於此層將來所有的GET請求,Master 都會發出對新摘要的請求,而不是在原跟蹤中看到的摘要請求。 對於所有GET請求,客戶端僅報告延遲。
跟蹤重播器以兩種模式發出請求:
- 儘可能快
- 保持原樣
跟蹤重播器的主端是多線程的,並且在單獨的線程中跟蹤每個客戶端的進度。 一旦所有客戶端完成工作,就可以計算總吞吐量和延遲。 每個請求延遲,每個客戶端延遲和吞吐量分別記錄。
跟蹤重播器通過兩種模式可以執行兩種分析
- 大規模註冊表設置的性能分析
- 脫機跟蹤分析:在這種模式下,主服務器不把請求轉發給客戶端,而是把它們交給一個分析插件來處理任何請求的操作。例如,trace播放器可以模擬不同的緩存策略,並確定使用不同緩存大小的效果。
4. 工作負載特徵
本文針對 Registry 優化提出了5個問題:
- 本文的一般工作量是多少?什麼是請求類型和大小分佈?
- 生產,暫存,預階段和開發部署之間的響應時間是否有所不同?
- Registry 請求是否有空間侷限性?
- 後續請求之間存在相關性嗎?可以預測未來的請求嗎?
- 工作負載的時間屬性是什麼?是否有突發事件,在時間上有侷限性嗎?
4.1 請求分析
根據拿到的請求日誌,將其分爲請求類型分佈和請求大小分佈
請求類型分佈
上圖的a顯示了 push 和 pull 在每個 registry 的比例,registry 是讀密集型的
Syd registry 顯示出較低的拉取比例 78%,因爲它是一個較新的 registry,因此它比成熟的registry 需要更密集地進行數據填充。
圖b顯示了,所有註冊中心接收60%以上的GET請求和10%-22%的HEAD請求。PUT請求比PATCH請求多了1.9-5.8倍,因爲PUT用於上傳清單文件(除了層之外),而且許多層都足夠小,可以在一個請求中上傳。
上圖顯示 清單文件(manifest) 和 層文件的請求比例。對於 pull 而言,更多的請求是對於層,syd 和 fra registry 的清單請求更多,是因爲有很多請求都是拉取過一次再拉取
push 操作裏,訪問層比清單比例更大
請求大小分佈
圖6顯示的是在 GET 請求中的,清單和層大小的累積分佈函數
6a可以看到,65%的層小於1MB,80%的小於10MB
6b則顯示了,清單大小一般在1KB左右
對於lon registry 來說,大量的請求都是針對與舊Docker版本兼容的清單,因此增加了它們的大小。
4.2 registry 加載和響應時間
加載分佈
通過研究各個 regsitry 的每分鐘被請求數,得到兩個結論
- 首先,開發和預開發註冊中心的利用率很低。
- 其次,註冊表負載隨着年齡的增長而增加。
響應時間分佈
對於層請求來說,大部分請求都可以在1到10s完成,對於清單請求則完成時間更低
4.3 熱度分析
本文還對層,清單,庫等熱度進行分析,發現top 1,2 的熱度和命中率很高,之後就呈斷崖式下降
啓發就是,過小的緩存不足以有效地緩存數據。例如,根據這些結果,我們估計數據集大小的2%的緩存大小可以提供40%或更高的命中率。緩存對於提高容器註冊中心的性能非常有效。
4.4 請求相關
本文還研究對某個清單的GET請求是否總是會導致相應層的後續GET請求。
本文將一次請求會話的時間閾值定義爲1分鐘,然後計算一個會話中跟隨GET manifest請求的所有GET層請求。
在大多數情況下,GET manifest請求之後沒有任何後續請求。原因是,每當客戶端已經獲取一個鏡像,然後提取一個鏡像,只有清單文件被請求檢查鏡像中是否有任何更改。這表明GET清單和層請求之間沒有很強的相關性。
總的來說,分析表明,如果考慮前面的PUT請求,那麼GET清單請求和後續層請求之間存在很強的相關性。
4.5 時間特性
客戶和請求併發性
間隔時間和空閒時間
間隔時間定義爲兩個後續請求之間的時間
空閒時間是指沒有活動請求的時間。
分析表明雖然一些可用分區有大量的空閒期,但它們的持續時間很短,因此很難用傳統的資源供應方法來利用它們
4.6 分析總結*
經過大量的數據和多維度的分析,總結出了7條結論
-
GET請求在所有註冊表中占主導地位,超過一半的請求是針對層的,這爲有效的層緩存和註冊表預取提供了機會。
-
65%的層小於1mb, 80%的層小於10mb,這使得各個層都適合緩存。
-
registry 負載受 registry 預期用例和註冊表年齡的影響。
與長期運行的生產系統相比,年輕的非生產註冊中心的負載更低。
在爲AZ提供資源以節省成本和更有效地使用現有資源時,應該考慮到這一點。
-
響應時間與註冊表負載相關,因此也取決於註冊表的使用年限(較年輕的註冊表的負載較少)和用例。
-
對層、清單、庫和用戶的 registry 訪問是嚴重傾斜的。有非常少的熱門的鏡像會被頻繁訪問,但熱度會迅速下降。因此,緩存技術是可行的,但應該謹慎選擇緩存大小。
-
PUT請求與後續的GET清單和GET層請求之間有很強的相關性。註冊中心可以利用這個模式將層從後端對象存儲區預取到緩存中,這大大減少了客戶端的拉取延遲。對於流行鏡像和非流行鏡像都存在這種相關性。
-
儘管週末的請求率下降幅度很小,但我們沒有發現可用於改善資源配置的明顯重複峯值。
5. Registry 設計改善*
通過上述的分析,對 registry 進行兩點改善
- 對熱度層使用一個多層緩存
- 一個新推送層的跟蹤器,它支持從後端對象存儲中預取最新的層。
5.1 實現
對於緩存和預取,我們實現了兩個單獨的模塊。爲了實現內存層緩存,我們修改了註冊表的Swift存儲驅動程序(修改/添加了大約200 LoC)。修改後的驅動程序將小的層存儲在內存中,對大的層使用Swift。
5.2 性能分析
本文在一樣的配置環境下比較了四種不同的後端
- 直接用OpenStack Swift
- 緩存小於1mb的層,其餘層用Swift存儲(內存+ Swift)
- 本地文件系統與SSD(本地FS)
- 重定向,即註冊表通過Swift中的層鏈接進行回覆,然後客戶端直接從Swift中獲取層。
結果如下圖所示:
結果突出了註冊表的快速後端存儲系統的優勢,並展示了緩存可以顯著提高註冊表性能的機會。
5.3 二級緩存**
本文觀察到,一小部分的層太大了,不能證明使用內存來緩存它們是合理的。
因此,設計了一個由主存(用於較小的層)和ssd(用於較大的層)組成的兩級緩存。
命中率分析
緩存策略:使用 LRU 緩存策略。淘汰時,從內存緩存中逐出的任何對象在完全逐出之前都會首先進入SSD緩存。將小於100 MB的層存儲在內存級緩存中,而較大的層存儲在SSD級緩存中。
爲每個AZ選擇數據輸入的2%、4%、6%、8%和10%的緩存大小
對於SSD級緩存大小,我們選擇內存緩存大小爲10×、15×和20×
下圖爲命中率分析圖
緩存對於大部分registry 還是有效的,但是 prs 和 dev 這兩個跟蹤表示註冊表開發團隊的測試交互,因此在這種情況下,我們看不到使用緩存的任何好處。
5.4 預取層**
預取算法如下圖所示:
算法描述:
L M t h r e s h LM_{thresh} LMthresh:PUT層和隨後的GET清單請求之間持續時間的閾值
M L t h r e s h ML_{thresh} MLthresh:保持預取層的持續時間閾值
上述兩個閾值是配置的
if r=PUT layer then
:當接收到PUT時,存儲庫和請求中指定的層將被添加到一個包含請求到達時間和客戶端地址的查找表中。
else if r=GET manifest then
:當在某個閾值 L M t h r e s h LM_{thresh} LMthresh內從客戶端接收到GET清單請求時,主機檢查查找表是否包含請求中指定的存儲庫。如果是命中,並且客戶端的地址不在表中,那麼客戶端的地址就添加到表中,並且從後端對象存儲中預取層。
命中分析
下圖爲預取技術的命中分析,單條bar 表示 ML 值,而一組條則爲 LM 值
提高 M L t h r e s h ML_{thresh} MLthresh可以顯著提高命中率,但提高 LM 命中率只有略微提高
6. 相關工作
Docker 容器
Docker 倉庫
工作負載分析
緩存和預取
7. 總結
本文在75天的時間跨度內跟蹤了在5個不同地理位置的 registry,總共3800萬個請求,最終拿到了181.3 TB的跟蹤日誌
本文提供了改進Docker註冊表性能和使用的見解。提出了有效的緩存和預取策略,利用特定於註冊表的工作負載特徵來顯著提高性能。
最後,開源了跟蹤器,並提供了一個跟蹤回放器,它可以作爲容器註冊和基於容器的虛擬化的新研究和研究的堅實基礎。