文|餘碩
上海交通大學22屆畢業生阿里雲開發工程師
從事雲原生底層系統的開發和探索工作。
本文 6369 字 閱讀 16 分鐘
GitLink 程式設計夏令營是在 CCF 中國計算機學會指導下,由 CCF 開源發展委員會(CCF ODC)舉辦的面向全國高校學生的暑期程式設計活動。
這是今年的夏令營活動中,餘碩同學參加 Nydus 開源專案的總結,主要介紹了 Nydus 為支援映象掃描與修復所做的研究與相關工作。
PART. 1 課題背景
Nydus 開源映象加速框架
Nydus 是 CNCF 孵化專案 Dragonfly 的子專案,它提供了容器映象,程式碼包按需載入的能力。Nydus 應用時無需等待全部資料下載完成便可開始服務。
Nydus 在生產環境中已經支撐了每日百萬級別的加速映象容器建立。它在容器啟動效能、映象空間佔用、網路頻寬效率、端到端資料一致性等方面相比 OCI v1 格式有著巨大優勢,並可擴充套件至其它資料分發場景,比如 NPM 包懶載入等。
目前 Nydus 由螞蟻集團、阿里雲、位元組跳動聯合開發。Containerd、Podman 社群已經接受了 Nydus 執行時作為其社群子專案,它也是 Kata Containers 以及 Linux v5.19 核心態原生支援的映象加速方案。
有關 Nydus 映象加速開源專案的詳細介紹,可以參考:Nydus——下一代容器映象的探索實踐。
專案描述
為 Nydus 映象增加一個掃描和修復的命令列工具,包含以下功能:
- 提供一個 Nydus 映象 url 和需要替換的檔案列表;
- 使用工具拉取映象 Bootstrap;
- 找到映象中對應的檔案並替換;
- 打包成新的映象並上傳回 Registry。
概括來說,原有專案目標是為 Nydus 映象實現一個掃描和修復的命令列工具,其中這些功能是這個工具或者工具組的實現流程。
但此專案具有一定的實驗性,其核心是為 Nydus 格式的映象提供掃描和修復的功能或者指引。如果存在更好的方式,專案最終不一定要按照原有專案描述去實現。因此在接下來的課題完成過程中,我們首先對已有映象掃描/修復的工具與服務進行了調研,來確定此課題方案的最終形態。
映象掃描工具及服務
掃描和修復
映象掃描和修復可以被拆解為兩個過程。掃描不更改原有的映象內容,只需要尋找掃描目標是否存在某種缺陷;映象修復則需要根據缺陷改動映象,包括但不限於映象內容,映象層級組織等。我們調研發現,當前主流開源映象掃描引擎包括雲廠商映象安全服務都只涉及映象掃描的功能, 不會主動新增映象修復的功能。
我們分析主要的原因有:
- 直接進行映象修復可能引入新的安全問題;
- 映象掃描的內容存在不同種類,很可能需要為不同種類的映象安全問題設計不同的修復方式;
- 映象修復功能可以透過重新打包映象替代。
所以,映象安全服務暫時只是提供掃描結果的安全報告,具體的修復操作由使用者自行決定。我們也準備沿用這樣的思路:在本課題中,為映象實現安全掃描的功能,並提供報告作為使用者映象修復的參考依據。
我們也探索討論了映象修復的支援,或者 Nydus 特性在映象修復場景下的增強,比如映象內包替換後的去重與重組等,或許這些可以是未來 Nydus 中增加的功能特性。
映象掃描介紹
映象掃描的原因與內容
容器映象是當前容器/軟體分發的基礎,裡面包含了容器隔離環境以及軟體執行環境的相關內容。因此保障其安全性變得十分重要。映象掃描即是要掃描映象中的所有內容,及時發現可能包含的安全漏洞或者隱私洩露。
綜合來看,映象掃描需要關注映象的安全性與健壯性,掃描的內容主要分為三類:
1. 安全漏洞。 包括系統軟體包和應用軟體庫中可能存在的安全漏洞。可以透過比對映象中這些庫/軟體包的來源、版本等與 CVE 資料庫中報告的漏洞的包的來源、版本來定位可能存在的安全漏洞。
2. 配置。 包括映象執行的環境配置和映象中相關內容組合可能帶來的問題。幫助儘早定位配置錯誤以及配置錯誤可能帶來的安全風險。
3. 隱私。 需要掃描的是使用者指定的一些隱私資訊。比如使用者不小心將金鑰等資訊存入映象。如果在掃描配置中進行指定,掃描過程可能發現這些隱私資訊,避免隱私洩露以及可能帶來的安全問題。
掃描引擎
常見的掃描引擎有 Trivy、Synk 等。Docker 官方使用的是 Synk,但它比較商業化;Trivy 是 CNCF 的專案,開放性較好。
映象掃描的使用方式
在我們的調研中,映象掃描主要應用方式有三種:
1. 基礎使用。 映象掃描的過程可以直接透過整合了映象掃描引擎的容器執行時或者映象掃描引擎命令列觸發。比如執行 $ docker scan image-url
,可以掃描映象,並輸出相應的報告。
source:https://docs.docker.com/engine/scan/
2. 流程整合。 映象掃描的過程可以整合到映象中心或者 CI/CD 流程中。比如將映象掃描整合到資料中心,在每次映象上傳到資料中心時觸發映象掃描,可以保證從資料中心下載的映象總是經過安全掃描的;整合在 CI/CD 流程中,設定觸發條件,可以保證映象生成,映象部署等過程所使用的映象的安全性。
source: https://www.containiq.com/post/container-image-scanning
3. 掃描服務。 雲廠商提供了映象安全的服務。它們背後可能是基於前兩種使用方式實現,但是還可以有很多種功能的增強。使用者想使用映象掃描的功能也可以直接購買類似的安全服務,並進行靈活的配置。
source: https://cloud.google.com/container-analysis/docs/on-demand-scanning-howto
PART. 2 課題解決思路
基本思路
課題首先要解決的基本思路是,如專案描述一般為 Nydus 實現一個專屬的映象掃描工具;還是複用已有的映象掃描引擎,在 Nydus 側實現對接支援,從而完成 Nydus 映象掃描的功能實現。
我們最終選擇了結合已有映象掃描引擎的實現思路。儘管為 Nydus 實現一個專屬的映象掃描工具可以更好的利用 Nydus 的特性,但是從映象功能生態上考慮,這並不是一個很好的方式。複用或者整合到現有的映象掃描工具,一方面可以直接使用已實現的映象掃描引擎中全面的內容掃描能力;另一方面,與上層映象安全服務的對接也不用再重寫相關介面。這樣也可以減少一些功能定製,減少使用者使用的負擔。因此此課題選擇了後一種實現的基本思路。
掃描思路:FileSystem vs Image
Trivy 掃描功能實現的框架
1. 控制路徑。
Trivy 在映象掃描上由以下路徑控制,每觸發一次命令,會由一個 Scanner 控制。其中關鍵的是 artifact
和 driver
。掃描引擎一般可以支援多種格式的掃描,比如 OCI v1 映象或者映象 Rootfs,同時一般也支援本地或者遠端儲存資訊等。這些一般可由 ScannerConfig
配置或者自動解析。
atifact
儲存著映象 (包括檔案系統) 元資訊,如果已經掃描過其中的內容,還可以儲存部分解析後的資訊。另外,load 到本地的 CVE 資料等也可透過 Artifact 獲取。Driver 裡的 Scan 方法表示的則是應用在特定掃描過程中的檢查方法。
2. 關鍵動作。
- local.Scanner
- Applier
Trivy 中 Local Scanner 是前文提到的本地進行掃描的控制結構。可以看出 Scanner 裡定義的行為是 Apply Layer。也就是將對映象逐層進行掃描。Applier 儲存了 Artifact 資訊,是聯絡具體掃描方法和儲存資訊的結構體。
3. 解析映象。
上述兩個過程理解了總體 Scan 的過程控制,以及具體針對映象的掃描方法。與映象相關的還有一個關鍵過程是如何針對性的解析掃描映象資訊。這一過程實現在 Artifact 中。
- Artifact
- Walker-Analyzer
Artifact 中有映象元資訊,還有儲存解析映象資訊的 Cache 等。主要要考慮的是當前 Trivy 支援的不同種類映象而進行不同的設定。
另一關鍵的是 Analyzer。Analyzer 對應的是映象分層的操作。對不同種類的映象操作不同。比如對於 OCI v1 的映象,可能就是每層映象拉取的完整資料流進行分析。對於 FileSystem,就是層次遍歷檔案樹。
方案選擇
瞭解了 Trivy 的實現後,我們發現直接把 Nydus 打包成 OCI v1 類似的映象去掃描並不合適。Nydus 映象內容中層次組織已經發生了變化,也具有按需載入的特點。若直接拉取,不一定保證拉取資訊的完備,想要完備支援映象的拉取也會丟失 Nydus 的特性。
因此我們最終選擇在 FS Artifact 方式下優先擴充 Nydus 的映象掃描能力。
相關的指令是:
$trivy image nydus-image-url ✗
$trivy fs /path/to/nydus_imgae_mountpoint ✓
這樣做的好處一是可以利用 Nydus 按需載入的特性。我們發現對於很多軟體包的掃描並不需要完整的檔案內容的下載,很多時候一些區域性資訊甚至元資訊即可判斷。這一特點可以利用上 Nydus 的按需載入,從而加快整個映象掃描的過程。二是特殊格式的映象都會有掛載檔案系統這一操作。這樣的方式可以推廣到更多的特殊格式映象。
本課題最終是以工具形式為 Nydus 整合了加速映象檔案系統掛載的能力,這能夠適配主流的映象掃描框架,未來我們可以考慮為社群映象掃描方案做更深度的整合,比如 Trivy、Clair、Anchore Engine 等,支援直接指定 Nydus 映象 Reference 做掃描,最佳化端到端的使用者體驗。
方案實現
在 Nydus 側提供映象掃描的支援,簡單指令過程為:
$ nydusify view localhost:5000/ubuntu:latest-nydus
/path/to/root_path
[比起容器簡單,直接拿到檔案樹]
$ trivy fs /path/to/rootpath
Nydusify 是 Nydus 提供的映象轉換,校驗與映象檔案系統掛載工具,使用方式可以參考:https://github.com/dragonflyoss/image-service/blob/master/docs/nydusify.md。
上面實現在 Nydusify 中的核心功能由 FileSystemViewer
結構體控制:
此結構體需要儲存的成員變數有此過程的一些輸入資訊和配置資訊。SourceParser
用於解析映象 url。MountPath
是可以指定的檔案系統 Mount 的地址。NydusdConfig
是 Mount 過程 Nydus Daemon 的配置。image-url
是必須提供的輸入資訊。View()
是呼叫方法。
當 nydusify view
被呼叫時,主要將發生三個步驟:
- 解析
image-url
; - 根據解析資訊,拉取檔案系統後設資料 Bootstrap;
- 根據 Bootstrap,Nydusd Mount 檔案系統到指定路徑。
之後 trivy fs
被呼叫時,可能發生:
- 層次遍歷掛載的檔案系統;
- 開啟映象掛載點中某個檔案時,會觸發 FUSE 請求到 Nydus Daemon (Nydusd) ,Nydusd 會從遠端映象中心按需拉取檔案的 Chunk 資料,以提供給掃描引擎分析檔案內容。
PART. 3 課題展示
Demo 展示
我們實現了這一功能,如 demo 中演示:
效能測試
我們在 Ubuntu、Wordpress、Tensorflow 等映象上進行了測試。
測試結果如下:
以掃描時延作為衡量標準,可以看出 Nydus 的安全掃描時間要顯著少於基本 OCI v1 映象。這其中最佳化的幅度與映象檔案系統複雜程度,測試網路環境等因素有關。
我們還從實際映象掃描場景瞭解到,很多時候出現安全問題時,例如一些 0day 漏洞,我們需要對大批次映象進行特定的少量檔案後設資料或資料的偵測。這種情況就更能體現出 Nydus 映象懶載入的優勢,安全掃描速度能夠大幅度提高。
當然,Trivy 對 OCI v1 映象的掃描最佳化會影響對比結果。我們 Trivy Image 多次對同一映象進行掃描,可得到下面的結果:
可以看出,多次掃描之後花費的時延下降。如之前提到,掃描資訊匹配會存到 local Cache 中。這是會根據 Blob ID 做為 key 來儲存的。因此 Trivy 在掃描同一映象時可以直接去查詢 Cache 而不必重複拉取映象形成最佳化。我們在未來也想整合類似的最佳化,這也是之後我們也想在掃描引擎社群推動更好的支援的原因。
PART. 4 課題擴充
描述
除了掃描映象內容的安全性,Nydus 本身提供了指令工具 nydus-image inspect
對 Nydus 映象進行檢查。 nydus-image inspect
指令透過檢索 Nydus 映象中包含的檔案系統後設資料資訊,可以檢視 Nydus 映象的組織情況。進一步可以判斷 Nydus 映象是否損壞等。
隨著 Nydus 的發展,它支援檔案系統 Layout 也從 v5 擴充套件到 v6。RAFS v6 可以相容 EROFS,由此獲得效能上的進一步提升。 nydus-image inspect
在 RAFS v5 時已經設計,擴充套件到 RAFS v6 之後,一些子命令為了相容進行了擴充套件。但是這樣的擴充套件是為 v5/v6 分開寫成的,也就是需要執行子命令時還需要對檔案系統格式進行一次判斷。
此外,還有一些子命令沒有做到相容性擴充套件。這樣的不完全支援存在一定的遺留問題,沒有做到功能的完備和統一。這樣的程式碼組織也損失了易讀性,不利於後續的維護,不同 RAFS 格式相關功能的迭代升級中也比較容易產生問題。
事實上,Nydus 已經為這兩種格式的檔案系統後設資料實現了統一的 API 介面。
比如對 RafsSuperInodes
, RafsSuperBlock
等結構有統一的方法進行資訊的獲取,對不同的底層實現進行了遮蔽。因此在此基礎之上,有必要對 nydus-image inspect
指令的相關功能進行一次重構。
實現
原有的架構中,存在一個 Executor
結構根據子命令判斷呼叫哪一個具體的方法。比如 Executor
收到子命令 stats 時會呼叫 cmd_stats 函式,執行並完成結果的輸出顯示。
我們需要重構的是針對每一條子命令所呼叫的方法,上層的呼叫邏輯保持不變,具體而言子命令方法有:
- cmd_stats
- cmd_list_dir
- cmd_change_dir
- cmd_stat_file
- cmd_list_blobs
- cmd_list_prefetch
- cmd_show_chunk
- cmd_check_inode
這裡不再對每一條子命令呼叫方法的修改進行詳細描述。大概做法是使用 RAFS mod 中統一的 API 實現所有原本的功能邏輯。
測試
我們實現了兩種測試。兩種測試的目的都是為了保持重構前後功能實現的一致。
測試一: 互動形式比對輸出結果。
這是一種功能測試。針對每條指令每種可能出現的情況,選取代表性映象進行測試與比對。
測試二: 整合冒煙測試。
這是一項冒煙測試。nydus-image inspect
命令還有 request mode
可以將一條子命令的輸出序列化成 json 格式的結果。我們將原有程式碼的結果序列化成 json 格式作為參考輸出,再將重構後程式碼的輸出與之一一比對。選取的映象為 Nydus 倉庫中用於冒煙測試的映象。這一部分的測試也整合在 Nydus 倉庫中,在以後的 CI 裡也可以保證這項功能的正確性沒被破壞。
成果
Prompt Mode
Stats
ls
Cd
Stat File
Blobs
Prefetch
Chunk Offset
Icheck Index
Request Mode
Stats
Prefetch
Blobs
PART. 5 收益與展望
收益
我非常榮幸能參加這次專案的開發,也要向專案組織老師趙新、專案指導助理姚胤楠、嚴松老師以及 Nydus 社群表示衷心的感謝。
透過這次專案的開發,我收穫了許多:知識上,我複習並更深入瞭解了檔案系統,學習了容器映象格式的組織,還首次嘗試了 Go 語言開發,也開始瞭解軟體測試;技能上,我鍛鍊了協調時間,開放討論,問題定位的能力。
更重要的是,我認為參與這次專案增加了我對容器場景應用的見識,也提升完整解決方案與系統設計的能力。當然,可能最大的收益是收穫了一次快樂的體驗!體會了開源合作的快樂,也體會到了開源價值產生的快樂。
展望
此次專案主要是為 Nydus 新增了映象掃描功能的支援,另外重構了 Nydus 的 Inspect 分析工具。如前文所說,後續還要繼續探索 Nydus 映象掃描整合到掃描引擎的方式,映象掃描和修復的最佳化也值得探索。關於 Nydus 映象格式細節,使用者態檔案系統,EROFS 等,我還有很多需要學習。
希望能一直參與社群,不斷豐富自己容器儲存方面的知識,同時也能為社群做出更大的貢獻。
瞭解更多...
Nydus Star 一下✨:
https://github.com/dragonflyoss/image-service
本週推薦閱讀