Nydus —— 下一代容器映象的探索實踐

SOFAStack發表於2022-06-15

文|嚴鬆(花名:井守 )

Nydus 映象開源專案 Maintainer、螞蟻集團技術專家

螞蟻集團基礎設施研發,專注雲原生映象與容器執行時生態

本文 7060 字 閱讀 15 分鐘

|前言|

容器映象是雲原生的基礎設施之一,作為容器執行時檔案系統檢視基礎,從它誕生到現在,衍生出了映象構建、儲存、分發到執行時的整個映象生命週期的各種生態。

然而,雖然映象生態眾多,但自它誕生以來,映象設計本身並沒有多少改進。這篇文章要探討的就是對容器映象未來發展的一些思考,以及 Nydus 容器映象的探索和實踐。

讀完這篇文章,你能夠了解到:

- 容器映象的基本原理,以及它的組成格式;

- 目前的映象設計有哪些問題,應該如何改進;

- Nydus 容器映象做了哪些探索,以及怎麼實踐。

PART. 1

容器映象

OCI 容器映象規範

容器提供給了應用一個快速、輕量且有著基本隔離環境的執行時,而映象提供給了容器 RootFS,也就是容器內能看到的整個 Filesystem 檢視,其中至少包括了檔案目錄樹結構、檔案後設資料以及資料部分。映象的特點如下:

- 易於傳輸,例如通過網路以 HTTP 的方式從 Registry 上傳或下載;

- 易於儲存,例如可以打包成 Tar Gzip 格式,儲存在 Registry 上;

- 具備不可變特性,整個映象有一個唯一 Hash,只要映象內容發生變化,映象 Hash 也會被改變。

早期的映象格式是由 Docker 設計的,經歷了從 Image Manifest V1[1]、V2 Scheme 1[2]到 V2 Scheme 2[3]的演進。後來出現了諸如 CoreOS 推出的其他容器執行時後,為了避免競爭和生態混亂,OCI 標準化社群成立。它定義了容器在執行時、映象以及分發相關的實現標準,我們目前用的映象格式基本都是 OCI 相容的。

映象主要是由映象層和容器配置兩大部分組成的。

什麼是映象層?

可以回想下平時寫的 Dockerfile 檔案:每條 ADD、COPY、RUN 指令都可能會產生新的映象層,新層包含的是在舊層的基礎上,新增加或修改的檔案 (包含後設資料和資料) ,或被刪除的檔案 (用稱之為  Whiteout *[4] 的特殊檔案表示刪除)* 。

所以簡單來說映象的每一層儲存的是 Lower 與 Upper 之間的 Diff,非常類似 Git Commit。這層 Diff 通常會被壓縮成 Tar Gzip 格式後上傳到 Registry。

在執行時,所有 Diff 堆疊起來後,就組成了提供給容器的整個檔案系統檢視,也就是 RootFS。映象的另外一部分是容器執行時配置,這部分包含了命令、環境變數、埠等資訊。

映象層和執行時配置各自有一個唯一 Hash (通常是 SHA256) ,這些 Hash 會被寫進一個叫 Manifest[5]的 JSON 檔案裡,在 Pull 映象時實際就是先拉取 Manifest 檔案,然後再根據 Hash 去 Registry 拉取對應的映象層/容器執行時配置。

目前的映象設計問題

第一,我們注意到映象層需要全部堆疊後,容器才能看到整個檔案系統檢視,所以容器需要等到映象的每一層都下載並解壓之後才能啟動。有一篇 FAST 論文研究分析[6]說映象拉取佔了大約容器 76% 的啟動時間,但卻只有 6.4% 的資料是會被容器讀取的。這個結果很有趣,它激發了我們可以通過按需載入的方式來提高容器啟動速度。另外,在層數較多的情況下,執行時也會有 Overlay 堆疊的開銷。

第二,每層映象是由後設資料和資料組成的,那麼這就導致某層映象中只要有一個檔案後設資料發生變化,例如修改了許可權位,就會導致層的 Hash 發生變化,然後導致整個映象層需要被重新儲存,或重新下載。

第三,假如某個檔案在 Upper 層裡被刪除或者被修改,舊版本檔案依然留存在 Lower 層裡不會被刪除。在拉取新映象時,舊版本還是會被下載和解壓,但實際上這些檔案是容器不再需要的了。當然我們可以認為這是因為映象優化做的不夠好,但在複雜場景下卻很難避免出現這樣的問題。

第四,映象 Hash 能夠保證映象在上傳和下載時候的不可變,但在映象被解壓落盤後,很難保證執行時資料不被篡改,這也就意味著執行時的資料是不可信的。

第五,映象是以層為基本儲存單位,資料去重是通過層的 Hash,這也導致了資料去重的粒度較粗。從整個 Registry 儲存上看,映象中的層與層之間,映象與映象之間存在大量重複資料,佔用了儲存和傳輸成本。

映象設計應該如何改進

我們看到了 OCI 映象設計的諸多問題,在大規模叢集場景下,儲存與網路負載壓力會被放大,這些問題的影響尤為明顯,因此映象設計急需從格式、構建、分發、執行、安全等各方面做優化。

首先,我們需要實現按需載入。 在容器啟動時,容器內業務 IO 請求了哪些檔案的資料,我們再從遠端 Registry 拉取這些資料,通過這種方式,可以避免映象大量資料拉取阻塞容器的啟動。

其次,我們需要用一個索引檔案記錄某個檔案的資料塊在層的 Offset 偏移位置。 因為現在的問題是,Tar 格式是不可定址的,也就是說需要某個檔案時,只能從頭順序讀取整個 Tar 流才能找到這部分資料,那麼我們自然就想到了可以用這種方式來實現。

接著,我們改造層的格式以支援更簡單的定址。 由於 Tar 是會被 Gzip 壓縮的,這導致了就算知道 Offset 也比較難 Unzip。

我們讓原來的映象層只儲存檔案的資料部分 (也就是圖中的 Blob 層) 。Blob 層儲存的是檔案資料的切塊 (Chunk) ,例如將一個 10MB 的檔案,切割成 10 個 1MB 的塊。這樣的好處是我們可以將 Chunk 的 Offset 記錄在一個索引中,容器在請求檔案的部分資料時,我們可以只從遠端 Registry 拉取需要的一部分 Chunks,如此一來節省不必要的網路開銷。

另外,按 Chunk 切割的另外一個優勢是細化了去重粒度,Chunk 級別的去重讓層與層之間,映象與映象之間共享資料更容易。

最後,我們將後設資料和資料分離 這樣可以避免出現因後設資料更新導致的資料層更新的情況,通過這種方式來節省儲存和傳輸成本。

後設資料和 Chunk 的索引加在一起,就組成了上圖中的 Meta 層,它是所有映象層堆疊後容器能看到的整個 Filesystem 結構,包含目錄樹結構,檔案後設資料,Chunk 資訊等。

另外,Meta 層包含了 Hash 樹以及 Chunk 資料塊的 Hash,以此來保證我們可以在執行時對整顆檔案樹校驗,以及針對某個 Chunk 資料塊做校驗,並且可以對整個 Meta 層簽名,以保證執行時資料被篡改後依然能夠被檢查出來。

如上所述,我們在 Nydus 映象格式中引入了這些特性,總結下來如下:

- 映象後設資料和資料分離,使用者態按需載入與解壓;

- 更細粒度的塊級別資料切割與去重;

- 扁平化後設資料層 (移除中間層) ,直接呈現 Filesystem 檢視;

- 端到端的檔案系統後設資料樹與資料校驗。

PART. 2

Nydus 解決方案

映象加速框架

Nydus 映象加速框架是 Dragonfly[7] (CNCF 孵化中專案) 的子專案。它相容了目前的 OCI 映象構建、分發、執行時生態。Nydus 執行時由 Rust 編寫,它在語言級別的安全性以及在效能、記憶體和 CPU 的開銷方面非常有優勢,同時也兼具了安全和高可擴充套件性。

Nydus 預設使用使用者態檔案系統實現 FUSE[8]來做按需載入,使用者態的 Nydus Daemon 程式將 Nydus 映象掛載點作為容器 RootFS 目錄。當容器產生 read (fd, count) 之類的檔案系統 IO 時,核心態 FUSE 驅動將該請求加入處理佇列,使用者態 Nydus Daemon 通過 FUSE Device 讀取並處理該請求,從遠端 Registry 拉取 Count 對應數量的 Chunk 資料塊後,最終通過核心態 FUSE 回覆給容器。

Nydus 加速框架支援了三種執行模式,以支援不同場景下的映象按需載入:

1. 通過 FUSE 提供給 RunC 這類容器執行時的按需載入能力,也是 Nydus 目前最常用的模式;

2. 通過 VirtioFS[9]承載 FUSE 協議,讓基於 VM 的容器執行時,例如 Kata 等,為 VM Guest 裡的容器提供 RootFS 按需載入能力;

3. 通過核心態的 EROFS[10]只讀檔案系統提供 RootFS,目前 Nydus 的 EROFS 格式支援已經進入了 Linux 5.16 主線,其核心態快取方案 erofs over fscache 也已經合入 Linux 5.19-rc1 主線,核心態方案可以減少上下文切換及記憶體拷貝開銷,在效能有極致要求的情況下可以用這種模式。

在儲存後端側,Nydus 可以接各種 OCI Distribution 相容的 Registry,以及直接對接物件儲存服務例如 OSS,網路檔案系統例如 NAS 等。它也包含了本地 Cache 能力,在資料塊從遠端拉取下來後,它會被解壓並儲存到本地快取中,以便在下一次熱啟動時提供更好的效能。

另外除了近端本地 Cache,它也可以接 P2P 檔案分發系統 (例如 Dragonfly) 以加速塊資料的傳輸。同時,它也能夠最大程度降低大規模叢集下的網路負載以及 Registry 的單點壓力,實際場景測試在有 P2P 快取的情況下,網路延遲能夠降低 80%  以上。

從這張圖的基準測試可以看到,OCI 映象容器的端到端冷啟動時間 (從 Pod 建立到 Ready) 隨著映象尺寸增大,耗時越來越越長,但 Nydus 映象容器始終保持平穩,耗時在 2s 左右。

映象場景效能優化

目前僅在螞蟻的落地場景下,都有每日百萬級別的 Nydus 加速映象容器建立,它在生產級的穩定性和效能方面得到了保障。在如此大規模的場景考驗下,Nydus 在效能,資源消耗等方面做了諸多優化。

映象資料效能方面,Rust 實現的執行時 (nydusd) 本身已經在記憶體及 CPU 方面做到了低開銷。影響 Nydus 映象容器啟動效能的主要負載是來自網路,因此除了藉助 P2P 分發從就近節點拉取 Chunk 塊資料外,Nydus 還實現了一層本地 Cache,已經從遠端拉取的 Chunk 會解壓縮後快取在本地,Cache 可以做到以層為單位在映象之間共享,也可以做到 Chunk 級別的共享。

雖然 Nydus 可以配置叢集內 P2P 加速,但按需載入在拉取每個 Chunk 時都可能會發起一次網路 IO。因此我們實現了 IO 讀放大,將小塊請求合併在一起發起一次請求,降低連線數。同時 Dragonfly 也實現了針對 Nydus 的 Chunk 塊級別的 P2P 快取和加速。

另外,我們通過觀察容器啟動時讀取映象檔案的順序,能夠分析出訪問模式,從而在容器 IO 讀資料前預先載入這部分資料 (預取) ,能夠提高冷啟動效能。與此同時,我們通過在映象構建階段重新排布 Chunk 順序,能夠進一步降低啟動延遲。

映象後設資料效能方面,例如對於一個幾十 GB 大小且小檔案較多的 Nydus 映象,它的後設資料層可能會達到 10MB 以上,如果一次性載入到記憶體中會非常不合算。因此我們改造了後設資料結構,讓它也實現了按需載入 (ondisk mmap) ,對函式計算這種記憶體敏感場景非常有用。

除了在執行時優化效能以外,Nydus 在構建時還做了一些優化工作。在多數場景下相比 Tar Gzip 格式的 OCI 映象,Nydus 映象層匯出時間優化到比其快 30%,未來目標是優化到 50% 以上

不止於映象加速

這些優化手段足以應對映象加速場景,但 Nydus 不止能應用在映象的加速上,它也正在演進為一個可以在其他領域同樣適用的通用分發加速框架。總體呈現如下:

1. Nydus 除了原生整合 Kata 安全容器外,在函式計算場景,例如阿里雲的程式碼包加速以及 Serverless 場景,Runtime 映象準備的冷啟動耗時通過 Nydus 從 20s 降低到了 800ms

2. 軟體依賴包管理場景,例如前端 NPM 包,在安裝階段有大量的小檔案需要解壓落盤。但小檔案 IO 非常影響效能,通過 Nydus 可以實現免解壓,螞蟻的 TNPM 專案[11]為 Nydus 增加了 macOS 平臺支援,將原生 NPM 的安裝速度從 25s 降低到了 6s

3. 在映象資料化場景,我們通過演算法分析業務映象之間的 Chunk 相似度,通過構造 Nydus Chunk 字典映象,降低了業務快速迭代導致的 50% 以上的儲存消耗,未來還會通過機器學習,幫助業務進一步優化映象尺寸。

檔案系統可擴充套件性

業界也有基於使用者態塊裝置的映象加速方案設計 (自定義塊格式 > 使用者態塊裝置 > 檔案系統) 。通過上面的介紹可以發現,Nydus 無論是 FUSE 使用者態模式還是核心態 EROFS 模式,都是基於檔案系統而非塊裝置,這樣的設計使得 Nydus 無論是在構建還是執行時,都可以很容易地訪問到檔案級別的資料資訊。這種天然能力為許多其他場景提供了可能,例如:

1. 在安全掃描場景,無需把整個映象下載解壓,就能預先通過分析後設資料,發現其中的高危軟體版本,再通過按需讀取檔案內容,掃描發現敏感與不合規資料,極大提高映象掃描速度;

2. 映象檔案系統優化,通過 trace 執行時檔案訪問請求,告知使用者訪問過哪些檔案,執行過哪些程式,這些記錄可以提供給使用者幫助優化映象大小,提供給安全團隊幫助審計可疑操作,提供給映象構建階段優化排布,以提高執行時預讀效能等;

3. 執行時通過 hook 檔案訪問請求,攔截高危軟體執行,阻斷敏感資料讀取,實現業務無感的漏洞資源替換與熱修復;

端到端的核心態方案

Nydus 在早期完全是一個使用者態實現,但為了適應極致效能場景下的需求,例如函式計算與程式碼包場景,我們又將按需載入能力下沉到了核心態。相比於 FUSE 使用者態方案,核心態實現可以減少隨機小 I/O 訪問造成的大量系統呼叫開銷,減少 FUSE 請求處理的使用者態與核心態的上下文切換以及記憶體拷貝開銷。

依託於核心態 EROFS (始於 Linux 4.19) 檔案系統,我們對其進行了一系列的改進與增強,擴充其在映象場景下的能力,最終呈現為一個核心態的容器映象格式——Nydus RAFS (Registry Acceleration File System) v6,相比於此前的格式,它具備塊資料對齊,後設資料更加精簡,高可擴充套件性與高效能等優勢。

如上所述,在映象資料全部下載到本地的情況下,FUSE 使用者態方案會導致訪問檔案的程式頻繁陷出到使用者態,並涉及核心態/使用者態之間的記憶體拷貝。因此我們更進一步支援了 EROFS over fscache 方案  (Linux 5.19-rc1)

當使用者態 nydusd 從遠端下載 Chunk 後會直接寫入 fscache 快取,之後容器訪問時,能夠直接通過核心態 fscache 讀取資料,而無需陷出到使用者態,在容器映象的場景下實現幾乎無損的效能和穩定性。其表現優於 FUSE 使用者態方案,同時與原生檔案系統 (未使用按需載入) 的效能相近。

目前 Nydus 在構建、執行、核心態  (Linux 5.19-rc1)  均已支援了該方案,詳細用法可以參見 Nydus EROFS fscache user guide[12],另外想了解更多 Nydus 核心態實現細節,可以參見 Nydus 映象加速之核心演進之路[13]。

PART. 3

Nydus 生態系統與未來

Nydus 相容了目前的 OCI 映象構建、分發、執行時生態,除了提供自有的工具鏈外,Nydus 與社群主流生態做了相容與整合。

Nydus 工具鏈

- Nydus Daemon (nydusd[14]) :Nydus 使用者態執行時,支援 FUSE,FUSE on VirtioFS 模式以及 EROFS 只讀檔案系統格式,目前也已支援 macOS 平臺執行;

- Nydus Builder (nydus-image[15]) :Nydus 格式構建工具,支援從源目錄/eStargz TOC 等構建 Nydus 格式,可用於 OCI 映象分層構建,以及程式碼包構建等場景,支援 Nydus 格式檢查與校驗;

- Nydusify (nydusify[16]) :Nydus 格式映象轉換工具,支援從源 Registry 拉取映象並轉換為 Nydus 映象格式並 Push 到目標 Registry 或物件儲存服務,支援 Nydus 映象校驗和遠端快取加速轉換;

- Nydus Ctl (nydusctl[17]) :Nydus Daemon 管控 CLI,可用於查詢 Daemon 狀態,Metrics 指標以及執行時熱更新配置;

- Ctr Remote (ctr-remote[18]) :增強版 Contianerd CLI (Ctr) 工具以支援直接拉取與執行 Nydus 映象;

- Nydus Backend Proxy (nydus-backend-proxy[19]) :用於將本地目錄對映為 Nydus Daemon 儲存後端的 HTTP 服務,在沒有 Registry 或物件儲存服務的場景下可用;

- Nydus Overlayfs (nydus-overlayfs[20]) :Containerd Mount Helper 工具,它可以被用於基於 VM 的容器執行時,例如 Kata Containers 等。

Nydus 生態整合

- Harbor (acceld[21]) :由 Nydus 發起的映象轉換服務 Acceld,讓 Harbor 原生支援 eStargz, Nydus 等加速映象的轉換;

- Dragonfly (dragonfly) :P2P 檔案分發系統,為 Nydus 實現了塊級別的資料快取與分發能力;

- Nydus Snapshotter (nydus snapshotter[22]) :Containerd 的子專案,以 Remote 外掛機制為 Containerd 支援了 Nydus 容器映象;

- Docker (nydus graphdriver[23]) :以 Graph Driver 外掛機制為 Docker 支援了 Nydus 容器映象;

- Kata Containers (kata containers[24]) :Nydus 為 Kata 安全容器提供原生的映象加速方案;

- EROFS (nydus with erofs[25]) :Nydus 相容 EROFS 只讀檔案系統格式,可以核心態方式直接執行 Nydus 映象,提升極限場景下的效能;

- Buildkit (nydus compression type[26]) :從 Dockerfile 直接匯出 Nydus 格式映象。

Nydus 未來方向

在逐步推進上游生態,擴充套件應用領域的同時,Nydus 也在進一步從效能,安全等如下幾個方向上做了更多的探索:

1. Nydus 目前已經支援了核心態 EROFS 只讀檔案系統,我們將進一步在效能、原生整合方面做更多工作;

2. 目前 Nydus 在大部分場景下匯出速度比 OCIv1 Tar Gzip 更快,接下來我們會讓構建也實現按需載入,例如允許 Base 映象指定為 Nydus 映象,在做 Dockerfile 構建時就不需要先把整個 Base 映象拉取下來,進一步提高構建速度;

3. 我們在用機器學習方法分析映象與映象間乃至整個映象中心儲存,利用執行時訪問模式分析等手段進一步優化映象資料去重效率,降低儲存,提高執行時效能;

4. 與各大映象安全掃描框架合作,原生支援更快的映象掃描,支援在執行時攔截高危軟體執行,阻斷高危讀寫,業務無感的漏洞熱修復與資源替換;

5. 除了按需載入外,Nydus 還可以解決海量小檔案 IO 效能問題,螞蟻即將開源的前端 tnpm 專案已經實踐了方案,我們在考慮擴充到更多的場景。

Nydus 相較於社群其他按需載入方案,它在映象場景為效能優化與低資源開銷做了諸多工作,並且拓寬了按需載入技術在映象掃描與審計,以及在非映象場景下落地的可能性。

如標題所言,雖然它不一定代表了容器映象的未來,但想必它也能為未來容器映象在格式設計,優化方向,實踐思路等方面提供具備核心競爭力的參考。Nydus 秉承了開源也開放的理念,期待著有更多的社群一同參與,為容器技術的未來貢獻自己的力量。

Nydus 網站:https://nydus.dev/

深入 Nydus,與我們一起探索~

瞭解更多...

Nydus Star 一下✨:
https://github.com/dragonflyoss/image-service

【參考連結】

[1]Image Manifest V1:https://github.com/moby/moby/tree/master/image/spec

[2]V2 Scheme 1:https://docs.docker.com/registry/spec/manifest-v2-1/

[3]V2 Scheme 2:https://docs.docker.com/registry/spec/manifest-v2-2/

[4]Whiteout:https://github.com/opencontainers/image-spec/blob/main/layer.md#representing-changes

[5]Manifest:https://github.com/opencontainers/image-spec/blob/main/manifest.md

[6]《Slacker Fast Distribution with Lazy Docker Containers》:https://www.usenix.org/conference/fast16/technical-sessions/presentation/harter

[7]Drafonfly:https://d7y.io/

[8]FUSE:https://www.kernel.org/doc/html/latest/filesystems/fuse.html

[9]VirtioFS:https://virtio-fs.gitlab.io/

[10]EROFS:https://www.kernel.org/doc/html/latest/filesystems/erofs.html

[11]TNPM:https://dev.to/atian25/in-depth-of-tnpm-rapid-mode-how-could-we-fast-10s-than-pnpm-3bpp

[12]《Nydus EROFS fscache user guide》:https://github.com/dragonflyoss/image-service/blob/fscache/docs/nydus-fscache.md

[13]《Nydus 映象加速之核心演進之路》:https://mp.weixin.qq.com/s/w7lIZxT9Wk6-zJr23oBDzA

[14]Nydusd:https://github.com/dragonflyoss/image-service/blob/master/docs/nydusd.md

[15]Nydus Image:https://github.com/dragonflyoss/image-service/blob/master/docs/nydus-image.md

[16]Nydusify :https://github.com/dragonflyoss/image-service/blob/master/docs/nydusify.md

[17]Nydus Ctl:https://github.com/dragonflyoss/image-service/blob/master/docs/nydus-image.md

[18]Ctr Remote:https://github.com/dragonflyoss/image-service/tree/master/contrib/ctr-remote

[19]Nydus Backend Proxy:https://github.com/dragonflyoss/image-service/blob/master/contrib/nydus-backend-proxy/README.md

[20]Nydus Overlayfs:https://github.com/dragonflyoss/image-service/tree/master/contrib/nydus-overlayfs

[21]Acceld:https://github.com/goharbor/acceleration-service

[22]Nydus Snapshotter:https://github.com/containerd/nydus-snapshotter

[23]Nydus Graphdriver:https://github.com/dragonflyoss/image-service/tree/master/contrib/docker-nydus-graphdriver

[24]Kata Containers:https://github.com/kata-containers/kata-containers/blob/main/docs/design/kata-nydus-design.md

[25]Nydus with EROFS:https://static.sched.com/hosted_files/kccncosschn21/fd/EROFS_What_Are_We_Doing_Now_For_Containers.pdf

[26]Nydus Compression Type:https://github.com/imeoer/buildkit/tree/nydus-compression-type

本週推薦閱讀

GLCC 首屆程式設計夏令營 高校學生報名正式開始!

Nydus 映象加速外掛遷入 Containerd 旗下

技術人聊開源|這並不只是用愛發電

螞蟻集團 Service Mesh 進展回顧與展望

相關文章