為什麼我們需要一個容器映象的包管理器

語言: CN / TW / HK

TL;DR

  • 我們需要對 container 供應鏈進行更好的元資料管理,以便更好地進行分析;

  • OCI 規範目前沒有辦法打包容器映象工件或一組容器映象。但他們會慢慢做到這一點;

  • 同時,我們需要一個用於容器映象的包管理器;

一些背景

我維護著一個叫做 Tern [1] 的開源專案,這個專案是為容器映象生成一個軟體材料清單(SBOM)。很多安裝在容器映象中的元件都是獨立安裝的,而非通過包管理器。這使得我們很難弄清楚建立這個容器映象的作者的意圖。它也沒有提供更多關於容器映象貢獻者的資訊。大多數容器映象都是基於之前已有的容器映象,通過客戶端工具或者映象倉庫都很難看到這些資訊。

我想如果有一個“容器映象”的包管理器,應該能解決這個問題。因此,我早在 KubeCon 2018 [2] 的時候就提出了 "打包" 的想法,我問容器映象的 manifest 是否可以儲存這些資訊,以便工具可以根據容器映象的供應鏈來進行分析。

事實證明,社群已經在考慮如何管理 helm chart、OPA 策略、檔案系統和簽名等事情了。這就是我參與 開放容器計劃(OCI)組織 的原因(我還欠 @vbatts 一個介紹我的人情)。當時的理解是,容器映象除了需要通過摘要來進行識別外,不需要進行其他管理。也不需要管理依賴,因為所有的依賴都被打包進了容器映象中。通過再次重建容器和保持一個下游消費者可以 pull 的滾動標籤來處理更新。

然而,容器生態除了可移植性外,並沒有提供太多東西。和容器映象一起管理容器元資料可以為使用者和貢獻者提供更多關於供應鏈的寶貴資訊。經過了兩年時間和幾次供應鏈攻擊之後,我們仍然在討論,如何最好的做到這一點。在這裡,我試圖將一些提議的概念歸納起來,看看它們如何滿足我們對元資料管理的要求。

回到起點

我們寫一個包管理器主要有以下三個原因:

  1. 標識 - 為你的新檔案或者包提供一個名字和其他唯一可識別的特徵;

  2. 上下文 - 瞭解你的包和其他包的關係(即,依賴性管理);

  3. 新鮮度 - 確保你的包在其生態系統中可維護並保持更新;

實際上還有第4個原因,“構建的可重複性” - 我認為它屬於“上下文”,但在這裡,它確實值得一提,因為了解你的包在特定時間的狀態是很必要的。這樣你才能分析出過去的“好”狀態,和現在“不那麼好”的狀態,即:使用二等分的方法分析構建。

由於一些(善意的)假設,容器映象生態並沒有這些功能。一個容器可以通過它的摘要(digest)被標識。你不需要管理生態,因為整個生態已經存在於一個單元中了。你不需要更新容器 - 只需要構建一個新的映象,所有需要更新的內容都將被更新。只要你的應用程式沒問題,那它便可以正常工作。然而,如果你打算長期維護你的應用程式,你必須準備好處理堆疊更新和重大版本。我們當前除了“下載最新版本”外沒有其他好的辦法來管理堆疊更新(一個值得注意的例外是 Cloud Native Buildpacks ,但我們此處將專注於通用案例)。堆疊的破壞性變更可能會阻塞你重新構建映象,這迫使你需要保留一箇舊版本的映象,因為你已經知道這個映象可以工作。你可以想象到,維護一組容器映象將變得更加費力。如果維護一組容器映象所需的資訊是內建的,並在需要時可用,那就真的太好了。

用於管理元資料的映象倉庫

我們可以建立一個單獨的元資料儲存解決方案,但現在我們已經有映象倉庫了。通過一些改進,它們可以被用來和容器映象一起儲存補充元資料。組織已經對包含源 tarball 的源映象和簽名 payload(正如 cosign 所做的那樣) 執行此操作。

使用映象倉庫的好處在於元資料可以和目標映象一起儲存。對 OCI 規範的建議主要涉及結構化和引用這些資料。這基本上是服務端的包管理,讓這些建議被合併是很困難的,因為目前存在的客戶端-服務端關係是很緊密的,對服務端的任何改變都會影響到客戶端,對客戶端打包機制的任何變更也都會影響到服務端。因此,目前的 OCI 規範除了擴充套件與當前 Docker 客戶端和 Docker registry 工作方式保持同步的規範外,還不能包括增強功能。

這篇文章並不是要批評當前的生態,而是想要把對話引到一個社群可以更可持續的維護它們的容器映象的位置。就我個人而言,我也想證明在容器映象領域是需要一個包管理器的,儘管映象倉庫可以支援相關 artifacts 和容器映象的連結,也可以支援在容器映象之間進行連結。

其他生態系統中的包管理器也有客戶端和服務端的關係,所以在客戶端和服務端之間分攤壓力的架構並不新鮮。只不過在當前場景下,這種關係略有不同。

Identification (識別)

容器映象的工作方式有點像 Merkle Tree [3] 有一個映象配置的資料塊,以及代表容器檔案系統的一個或多個數據塊。每個資料塊都經過了雜湊處理,並且放在了經過雜湊處理過的 Image Manifest 中。

image-manifest

現在可以很好的專門識別這個包,但它缺少其他可識別的特徵,最明顯的是名稱和版本。在容器世界裡,名字通常是一個映象倉庫的名稱,而版本則通常是一個 tag 。

image-manifest-with-tag

就像 Git 一樣,所有對檔案集的引用都是雜湊值,其他引用可以指向它們,例如 HEADFETCH_HEAD 或 tag。tag 可以遵循語義版本控制,可以被移到另一個提交中。在開源的世界裡沒有人這樣做,因為這會破壞專案維護者和社群其他成員間的固有契約。容器映象的 tag 並沒有需要遵循語義版本控制的規則,但很多語言包管理器都依賴它,因此使用語義化版本控制容器映象是有一定的希望的。

無論如何,雜湊值對容器映象而言是相當好的一個識別符號。

那麼其他可識別的特徵呢?以下是一些其他包管理器使用的特徵:

  • 許可證和版權資訊

  • 作者/供應商

  • 釋出日期

  • 指向原始碼的連結

  • 第三方元件的列表(使用的作業系統,需要預安裝的包等)

最後一項可能會變得相當長,因為建立一個容器映象實際需要的第三方元件可能達到數百個。最近我一直主張將這些資訊跟隨容器映象放入一個 SBOM(軟體材料清單)中,容器映象簽名是另一個可以和容器映象一起傳播的工件,例如映象清單的分離式前面,或者一個簽名荷載。

我們現在有多個容器映象的識別工件,我們希望將它們與容器映象聯絡起來。當前的 OCI 建議使用 references (引用),一個引用是包含了 blob 雜湊和其引用清單的雜湊組成的清單。在我們的例子中,引用是影象清單的雜湊值。

imgage-reference

這種佈局還存在一些問題:

  1. registry 可能只識別被標記的工件,因此會刪除任何沒有被標記的東西;

  2. 刪除映象會導致空引用;

一個快速的解決方案可能是標記所有的清單。

image-reference-with-tag

這意味著需要有東西能夠跟蹤標籤和它們之間的關係,同時跟蹤所有工件的版本。

一個長期的解決方案可能是定義一個規範的工件清單,registry 將識別並將其視為特殊的存在。如果是這樣的話,那就需要計算或者跟蹤與每個清單關聯的引用數量了。

img

registry 實現者可以按照他們想要的任何方式來跟蹤圖中的連結。例如,在這個圖中,對每個清單的引用數量都會被跟蹤(減去雜湊),但映象清單被刪除時,操作將會沿著樹向下走到每個引用的末端,並按照一定的順序去刪除它們,直到引用數為 0。

但在這裡,我們為了追蹤所有的相關物件,正在進入一些複雜的追蹤系統。這是在 registry 端完成的包管理。可以提出一個論點,實現一箇中立的垃圾收集器,它可以被 registry 或客戶端使用,但我們現在講的有點超前了。

我希望這足以說明有必要對工件的集合進行追蹤,無論是在客戶端還是服務端,或是兩者都有。

Context

據瞭解,一個容器在執行時沒有外部依賴性。但是在構建時,最終的容器映象確實取決於初始容器映象的狀態,通常是 Dockerfile 中的 FROM 語句所定義的映象。由於 Merkle Trees 的魔力,衍生的映象與之前的映象之間沒有任何聯絡。因此,所有對舊映象的引用都需要為新映象建立一次,同時需要新增一些額外的工件。

img

與普通的引用機制相比,工件清單機制可能有一個優勢,因為在工件元資料被更新的同時,引用的數量被保持在最低水平。

img

這兩種機制都支援供應鏈安全,監管鏈和系譜檢查等要求。這兩種機制都需要 引用管理 。在前者中,客戶端將會拷貝原始映象的 SBOM 和簽名清單,更新它的引用,和增加新的清單。在後者中,客戶端必須下載工件清單,對其進行補充,並與新的容器映象一起推送。無論哪種方式,客戶端都需要理解一些語法,無論是 tag 名稱,工件結構或者工件型別,更不用說對工件來自的生態系統的一些理解。(SBOMs 和簽名只是其中兩種常見的工作型別,可以包括在內)。

我一直在考慮的一個用例是如何將一系列的映象連結到一起,來描述一個雲原生應用。例如,jaeger 應用程式實際上是有自己依賴關係圖的容器集合,

如果能以一種可以上傳到 registry 的格式來描述這些連結,這樣整個映象就可以和它的補充工件,一起在 registry 之間進行轉移。

multiple-images

這是處於我想象力邊緣的部分(我的圖太複雜了),但我希望這些用例能說明,如果現在不需要包管理器,那麼很快就需要它來管理這些高層次的關係。

更新

這是我希望語義版本控制能夠得到更認真對待的一個地方。軟體包管理器使用語義版本控制來允許一系列在整個堆疊中相容的版本。這使得下游消費者能以最小的干擾來適應更新。目前,由於容器映象只能通過其摘要進行識別,tag 是標識容器映象處於什麼版本的唯一辦法,這就是包管理器真正有用的地方。

通常情況下,客戶端查詢伺服器,看看它們應用程式所需的任何包是否有更新。然後客戶端告訴使用者它們想下載的更新是可用的,如果使用者有一個他們的應用程式相容的版本範圍,客戶端就會在這個範圍內下載新版本。

當前的 distribution SPEC 支援列出 tags 。除此之外,registry 沒有提供任何管理更新的方式,也許這就是 registry 端所需要的一切。這也意味著,檢查和更新的工作,主要落在了客戶端。

下一步

社群一致認為,這些提議對於當前的 SPEC 專案而言,要接受的話是過於大了 。然而(希望)能有一場公開的對話,試圖將這些變化分解為可吸納的部分,從而允許向後相容當前的狀態,並推動規範向前發展。

可能在將來,並不需要有一個包管理器,因為 registry ,映象和 artifacts 格式,將負責提供推理供應鏈所需的所有資訊。但那是一個遙遠的未來,在此期間,我們需要一個東西來填補空白,也就是一個包管理器。

原作者:nishakm 經授權翻譯釋出, 原文地址:https://nishakm.github.io/code/metadata/

歡迎訂閱我的文章公眾號【MoeLove】

TheMoeLove

參考資料

[1]

Tern: https://github.com/vmware/tern

[2]

KubeCon 2018: https://youtu.be/WY3s_cG9ia8

[3]

Merkle Tree: https://en.wikipedia.org/wiki/Merkle_tree