3. 容器映象構建與分發

Docker 能引爆容器,主要原因是它帶來了映象和映象分發技術。容器執行時是 Kubernetes 平臺的基礎元件,映象則是容器的靜態檔案,容器由映象建立。在 Kubernetes 平臺中,我們交付的所有應用,最終都是以映象形式交付的,映象的構建與分發,直接影響 CI/CD 的完成度。所以,映象也是 Kubernetes 平臺的重要基礎。

映象的來源一般有兩種形式。一種是透過構建生成映象,在基礎映象上新增使用者自定義的內容及配置,製作出業務映象,如基於 JDK 基礎映象構建 Java 程式的應用映象。另一種是從映象倉庫獲取別人製作好的映象,很多常用軟體或系統都會有官方製作好的映象,儲存在公開的映象倉庫中,如 rocky、nginx、redis 等,你可以在 Docker Hub 搜尋並下載它們。

透過映象倉庫獲取映象的過程比較簡單,這裡就不佔用篇幅討論了。在下一節中,我們詳細討論構建映象時的注意事項。

3。1 映象構建模式

在使用容器前,我們一般透過原始碼、二進位制檔案或壓縮檔案部署應用程式。在這種情況下,除了應用程式自身,還需要確保它依賴的環境得到了正確配置。

在容器環境中,部署的物件變成了容器映象。映象包含了應用程式二進位制檔案,還包含應用程式的執行環境和依賴。映象本身是一組壓縮的檔案系統層和一些元資料,遵循了 OCI 映象規範。這是雲原生社群協定的標準,以確保能夠使用不同的方式構建映象,並且構建後的映象可以被不同容器執行時執行。

構建容器映象時通常需要建立一個描述映象的檔案 —— Dockerfile,並使用 Docker 引擎對 Dockerfile 進行構建。借用 BuildKit (Docker 建立的新一代構建工具) 的概念,我們可以從前端和後端來考慮構建。前端是定義用於構建映象的高階流程的方法,例如 Dockerfile 或 Buildpack。後端是實際的構建引擎,它接收前端生成的定義,並在檔案系統上執行命令構建映象。

常見的後端是 Docker 守護程序,但它並不能適合所有場景。例如,如果在 Kubernetes 叢集中執行構建操作,我們就需要在容器內執行 Docker daemon (Docker in Docker),或者將 Docker Unix socket 從主機掛載到構建容器中。這兩種方法都有缺點,而且後一種情況會暴露出潛在的安全問題。針對這些問題,出現了新的構建後端,如 Kaniko、BuildKit,它們使用相同的前端 (Dockerfile),但使用不同的技術在後臺構建映象,這也使其成為了在 Kubernetes Pod 中構建映象的可靠選擇之一。

下一節我們討論雲原生環境中的兩種主流構建後端,但在確定以何種方式構建映象前,應該先回答以下問題:

能以 root 使用者執行構建嗎? 可以安裝 Docker socket 嗎? 可以執行守護程序嗎? 構建操作是否在容器中執行? 是否要在 Kubernetes 的工作負載中執行構建? 多大程度的利用快取? 工具選擇將如何影響構建的分發? 想使用什麼前端或映象定義機制?支援哪些機制?

企業中該由誰 (哪個部門) 負責構建映象,是一個經常被討論的問題。Docker 流行初期,基本是開發人員使用居多。根據我的經驗,較小的公司仍然是開發人員負責編寫 Dockerfile,並定義應用程式映象構建的過程。隨著企業大規模容器化和使用 Kubernetes,開發人員或者開發團隊自己建立、維護 Dockerfile 變得不可持續。首先,它給開發人員帶來了額外的工作量,使他們不能專注於核心工作;其次,這種方式生成的映象存在巨大差異,幾乎沒有標準化可言。

因此,企業開始將構建過程從開發團隊剝離,交由運維或平臺團隊,以實現從原始碼到映象的模式和工具,這些模式和工具,以程式碼倉庫作為輸入,透過管道產出容器映象 (我會在“Cloud Native Buildpacks”小節進一步討論這種模式)。但在此之前,也會經常出現平臺團隊舉辦分享會、以及協助開發團隊建立 Dockerfile 和映象的模式。這也是一種臨時性方案,因為隨著企業規模擴大,考慮到開發人員和平臺人員的人數比例,這種模式也是不可持續的。

3。1。1 反模式之黃金基礎映象

名詞解釋:

黃金基礎映象 (golden base image):我們預先配置好的映象,作為構建應用程式映象的基礎映象。 反模式 (antipattern) :在實踐中經常出現但低效或是有待最佳化的設計模式。

在構建映象時,我遇到的一些反模式,通常是團隊沒有及時調整思維,接受容器和雲原生環境中出現的模式造成的。最常見的莫過於黃金映象的概念。這種情況是在構建應用程式映象時,必須使用企業規定的基礎映象 (例如預先配置好的 CentOS 映象),所有應用程式的映象都要基於該映象構建。使用這種模式通常是出於安全考慮,因為映象中的工具和庫都經過了嚴格的稽核。然而,當重心轉到容器,團隊發現自己不得不重新造輪子,從第三方或官方獲取需要的上游映象,並在這些映象上重新構建他們的應用程式和配置。

這會引入新的相關問題。首先,從上游映象到內部定製版映象的初始轉換涉及額外工作。其次,運維團隊有責任儲存和維護這些內部映象。在典型環境中,使用的映象數量會越來越多,涉及的額外工作也隨之增加,最終導致更嚴峻的安全狀況,如映象的更新操作,即使有,頻率也會很低。

這方面的建議是與安全團隊合作,確定黃金映象的具體要求,包括:

確保安裝了特定的軟體 確保不存在易受攻擊的庫 確保使用者具有正確的許可權

在瞭解了這些限制背後的原因,我們可以將要求編碼到管道中,拒絕或警告不符合要求的映象,維護所需的安全態勢,並在此基礎上允許團隊使用上游社群的映象。我會在“登錄檔”部分深入討論這樣的工作流示例。

指定基礎作業系統的另一個讓人信服的理由是,如果遇到了需要排障的情況,團隊有相應技能和經驗的人才儲備。然而,細細思量,這並沒有想象中的香。很少有必要

exec

到容器內解決特定問題,即便如此,基於 Linux 作業系統之間所需的支援型別的差異也相當微不足道。此外,為減少容器內部開銷,越來越多的應用程式被打包在超輕量級的 distroless 映象中,這些映象並不包含額外的工具 。

由於本節所描述的原因,應避免將所有上游或者官方映象重構到自己的基礎映象上。我並不是說維護一套內部精選的基礎映象是一個壞主意。它們可以作為應用程式的基礎,我會在下一節討論構建內部基礎映象的注意事項。

3。1。2 選擇基礎映象

基礎映象很重要,它是構建應用程式容器映象的底層,包含了部分系統庫和工具。如果不重視基礎映象的選擇,很可能引入不必要的庫和工具,使容器映象臃腫甚至包含安全漏洞。

根據企業的成熟度和安全要求,不是每個人都有選擇基礎映象的許可權。很多企業有專門的團隊負責製作、維護基礎映象。如果你恰巧有選擇或審查基礎映象的許可權,在評估基礎映象時應遵循以下原則:

確保映象由信譽良好的組織釋出。不能隨便從 DockerHub 或其他公開的映象登錄檔的某個倉庫下載映象,因為這些映象將是構建應用程式映象的基礎。 優先選擇持續更新的映象。基礎映象通常包含工具和庫,發現漏洞時,必須打補丁。 首選開源了構建過程或規範的映象。通常是 Dockerfile 檔案,你可以透過它瞭解映象是如何構建的。 避免使用包含不必要工具或庫的映象。首選最小化映象,這些映象佔用的空間很小,必要時開發人員可以在其上進行二次構建。

構建應用程式映象時,個人認為 distroless 可以作為可靠的基礎映象,因為它體現了上述原則,包含預先建立的合理使用者 (nonroot/nobody 等) 和最少的所需庫。並且 distroless 有幾個特定於程式語言的變體基礎映象供你選擇。

3。1。3 執行時使用者

由於容器的隔離模式 (主要是容器共享底層的 Linux 核心),容器的執行時使用者具有一些開發人員容易忽略的重要含義。在多數情況下,如果未指定容器的執行時使用者,程序將以 root 使用者執行。這是有問題的,它增加了容器的受攻擊面。假如攻擊者破壞了應用程式並跳出容器,很有可能獲得宿主機的 root 許可權。

在構建容器映象時,需要考慮容器的執行時使用者。應用程式是否需要以 root 使用者執行?應用程式是否依賴 的內容?是否需要將非 root 使用者新增到容器映象?在回答這些問題時,請確保在容器映象的配置中指定了執行時使用者。如果使用 Dockerfile 構建映象,則可以透過

USER

命令指定執行時使用者。示例演示了以 使用者和組 (預設配置為 distroless 映象) 執行“my-app”二進位制檔案:

儘管可以在 Kubernetes 的部署清單中指定執行時使用者,但將其定義為容器映象規範的一部分更有意義,因為它會生成自文件化的容器映象,並能夠確保開發人員在本地或開發環境中使用了相同的使用者和組。

3。1。4 明確安裝包的版本

我們通常使用包管理器 (如 dnf、yum、apk) 安裝應用程式依賴的外部軟體包。在構建容器映象時,固定或指定依賴包的版本也很有必要。下面的例子顯示了依賴 imagemagick 的應用程式。Dockerfile 中的 apk 命令明確了與應用相容的 imagemagick 版本:

如果未指定軟體包版本,可能會安裝與應用程式不相容的版本。因此,在容器中安裝軟體時,一定要養成指定軟體包版本的良好習慣。這樣做,既保證了容器映象構建是可重複的,也確保了生成的映象包含應用程式相容的軟體。

3。1。5 構建映象與執行時映象

開發團隊除了使用容器打包部署應用程式外,還可以使用容器構建應用程式。透過 Dockerfile,容器提供了定義明確的構建環境。開發人員也因此不需要在他們的系統中安裝任何構建工具。更重要的是,容器可以為整個開發團隊和他們的持續整合 (CI) 系統提供標準化的構建環境。

想利用好這種模式,需要區分“構建映象”和“執行時映象”。“構建映象”包含編譯應用程式所需的工具和庫,而“執行時映象”則包含待部署的應用程式。例如,在 Java 應用中,我們可能有一個包含 JDK、Gradle/Maven 以及所有編譯和測試工具的“構建映象”。我們的“執行時映象”可以只包含 Java 執行時和構建好的應用程式。

鑑於執行應用程式時不需要構建工具,因此“執行時映象”中不應該包含這些工具。這麼做進一步精簡了容器映象,可以更快地分發,並且暴露的攻擊面也更窄。如果使用 Docker/BuildKit 構建映象,可以利用它的“多階段構建”功能,將“構建映象”和“執行時映象”分開。下面是一個 Go 應用程式的 Dockerfile 示例。“構建映象”使用 golang 基礎映象,包含了 Go 工具鏈,“執行時映象”則使用 distroless base 映象,只包含了應用程式的二進位制檔案:

程式碼釋義:

golang:1。14。2 作為基礎映象,包含了構建 Go 程式的所有工具,這些工具在執行時並不被需要。

複製 go。mod 檔案並執行下載,以便在程式碼更改但依賴項未改變時快取此步驟。

使用 distroless 作為執行時映象,最小化基礎映象,不包含額外依賴。

以 使用者和組執行程式

把編譯後的檔案 (my-app) 從”構建階段“複製到”部署階段“。

容器內執行單個程序,通常沒有 supervisor 或 init system。出於這個原因,你需要確保正確處理訊號並且正確地重新生成和回收孤立程序。有幾個最小的 init 指令碼能夠滿足這些要求並充當應用程式例項的載入程式,如 dump-init。

3。2 構建工具

本節我們討論兩種滿足以上容器映象構建模式、且比較流行的容器映象構建工具。

3。2。1 BuildKit

BuildKit 是 Moby (Docker) 專案提供的第二代映象構建工具。作為通用的映象構建工具,除了可以整合在 Docker (Docker CE v18。09+) 中使用,還可以作為獨立的二進位制檔案和庫使用。

BuildKit 由 守護程序和 客戶端組成。 目前僅能執行在 Linux 上,並且支援非特權使用者執行; 支援 Linux、macOS 和 Windows。

Buildkitd 守護程序支援兩種工作後端:OCI (runc) 和 containerd。預設使用 OCI (runc),可以設定 改用 containerd。

3。2。1。1 部署 buildkitd

Buildkitd 守護程序可以透過二進位制、Docker 的方式執行,也可以透過 Kubernetes 的 StatefulSet 或者 Deployment manifest 部署。如果使用二進位制的方式,需要先安裝 runc、crun 或者 containerd 元件中的一個。

使用 docker 的方式執行 :

在 Kubernetes 中使用 Deployment 部署 (下一章我會詳細討論生產級高可用 Kubernetes 叢集的規劃與部署):

3。2。1。2 buildctl client

映象中包含了客戶端二進位制檔案,我們可以進入到容器中,執行客戶端命令。也可以從官方下載對應的二進位制包檔案,解壓到 $PATH 路徑下,並新增可執行許可權。

可以透過

buildctl help

檢視幫助資訊:

從幫助資訊中可以看到,

buildctl

提供了五個子命令和一些全域性選項。

子命令介紹:

du:磁碟使用情況 prune:清除本地構建資料 build, b:構建 debug:除錯工具 help, h:顯示命令列表或命令的幫助資訊

du

語法:

檢視本地構建快取的磁碟使用情況:

使用 ——verbose (-v) 選項,列印更詳細的資訊:

使用 ——filter value (-v value) 選項,可以對輸出進行過濾:

-f value 的運算子可以是 “==”|“!=”|“~=”。-v -f value 引數也可以同時使用。

prune

語法:

清空本地構建的資料:

命令選項:

——keep-storage value:保持資料量低於指定的值,單位是 MB,預設不限制。示例,映象儲存不超過 1024MB:

——keep-duration value:保持儲存的資料比指定的值新,預設單位 s。示例,刪除 5分鐘前的資料:

——filter value, -f value,功能和 du 中的 -f value 一樣,刪除過濾欄位映象

——verbose, -v 輸出詳細資訊:

——all 刪除所有資料,包括內部和前端的引用,也可以使用 -f value,過濾後再刪除。

build

最常用的子命令,構建 artifacts,常用來構建映象(artifacts的一種)。

語法:

命令選項及相關常用引數:

——local value:允許構建訪問的本地目錄,向構建器公開客戶機的本地原始檔。

context:context=/dir/work。Dockerfile 的構建環境路徑。 dockerfile:dockerfile=/dir/work。Dockerfile 路徑。

——output value, -o value:定義匯出構建結果。預設構建結果和相關快取儲存在 BuildKit 中。需要指定一個輸出檢索結果。輸出型別包括 image/registry、local directory、Docker tarball、OCI tarball,最常見的是輸出為 image/registry:

輸出為映象,映象名稱是 docker。io/username/image,並推送到該映象倉庫。

在推送的過程中,可以使用 ——export-cache type=inline 選項,把快取和映象一起推送到登錄檔中。——import-cache type=registry,可以匯入快取。

輸出為 image 型別時,還支援幾個引數,常用的是:

name=[value] 設定映象名,包括登錄檔地址、倉庫名稱和映象名稱。例如:hub。aiops。red/weiwendi/testimage:v1 push=true 推送映象到 name 中定義的映象倉庫

3。2。2 Cloud Native Buildpacks

另一種構建容器映象的工具,能夠透過分析應用的原始碼自動生成容器映象。與特定應用的構建工具類似,這種方法大大簡化了開發者的體驗,因為開發者不必建立和維護 Dockerfiles。Cloud Native Buildpacks 是這種方法的一個實現,其流程如圖3-1所示:

3. 容器映象構建與分發

3-1 Buildpac flow

Cloud Native Buildpacks (CNB) 是 Buildpacks 以容器為中心的實現,Heroku 和 Cloud Foundry 一直使用該技術打包應用。就 CNB 而言,它將應用程式打包到 OCI 容器映象中,以便在 Kubernetes 上執行。為了構建映象,CNB 會分析應用程式原始碼並執行相應的 buildpacks。例如,如果原始碼中存在 Go 檔案,則會執行 Go buildpack;如果原始碼包含 pom。xml 檔案,則執行 Maven (Java) buildpack 。這一切都發生在幕後,開發人員可以使用

pack

CLI 工具啟動此過程。這種方法的優點是 buildpacks 有嚴格的限定範圍,使得構建遵循最佳實踐的高質量映象成為可能。

除了改善開發者體驗和降低平臺採用門檻外,平臺團隊還可以透過自定義的 buildpacks 執行策略,確保合規性,並對平臺中執行的容器映象進行標準化。

總之,提供一個從原始碼構建容器映象的解決方案是值得努力的方向。此外,這種解決方案的價值會隨著企業規模的擴大而增加。歸根結底,開發團隊希望專注於在應用程式中創造價值,而不是如何將應用程式容器化。

3。3 登錄檔

容器映象登錄檔是使用 Docker 和 Kubernetes 的核心要求之一,因為需要一個地方儲存我們在某處構建並希望在其他機器上執行的映象。與映象一樣,OCI 也定義了登錄檔的標準規範,以確保互操作性。

映象登錄檔有多種解決方案,包括閉源和開源。大多數映象登錄檔由三個主要部分組成:Server (提供使用者介面和 API 邏輯),blob 儲存 (用於映象自身),以及資料庫 (儲存使用者和映象元資料)。通常,儲存後端是可配置的,這可能會影響登錄檔的部署架構,稍後討論這個問題。

本節中我會討論登錄檔的主要特性,以及將它們整合到管道中的一些模式。

如果你已經在使用 Artifactory 或 Nexus,可能傾向於它們提供的映象託管功能以簡化管理。如果你的基礎設施基於公有云,使用雲平臺提供的登錄檔可能更有成本優勢,如阿里雲容器映象服務 (ACR)、騰訊雲容器映象服務 (TCR) 或 AWS 彈性容器登錄檔 (ECR) 等。當然,你也可以部署其他映象登錄檔,我在下節會介紹 Harbor 的部署與使用。

選擇登錄檔的另一個主要因素是環境和叢集的拓撲、架構及故障域。可以選擇在每個故障域中放置登錄檔,以保障高可用性。當這麼部署時,需要確定使用一個集中的 blob 儲存,還是每個區域一個 blob 儲存並在登錄檔間設定映象複製。大多數登錄檔都提供了複製功能,你可以將映象推送到某個登錄檔,映象會被自動複製到其他登錄檔中。即使你選擇的登錄檔沒有複製功能,也可以使用管道工具 (如 Jenkins) 和在每次推送映象時觸發的 webhooks 實現映象的基本複製。

部署一個或是多個登錄檔,還受吞吐量的影響。在有成千上萬名開發人員的企業中,每次程式碼提交都會觸發映象構建,併發操作 (推和拉取映象) 的數量可能非常大。因此,最重要的是要明白,雖然映象登錄檔在管道中只起到有限的作用,但它不僅是生產部署的關鍵步驟,也是開發活動的關鍵步驟。它是一個核心元件,我們必須以與其他核心元件相同的方式監控和維護它們,實現更高的服務可用性。

許多登錄檔設計之初就考慮了在 Kubernetes 叢集或容器化環境中執行的便利性。這種執行方式有很多優點。首先,我們能夠利用 Kubernetes 中的原語和約定保持服務執行、服務發現和易於配置的特性。這種方式的缺點是,我們要依賴叢集內的服務提供映象,以在該叢集啟動新服務。更常見的情況是,登錄檔執行在共享服務叢集上,並能夠實現故障切換,以確保登錄檔始終可用。

我還經常見到登錄檔部署在 Kubernetes 叢集外部,並被視為所有叢集都需要的獨立輔助元件。使用雲登錄檔也是一種常見模式,儘管還需要了解它們的可用性保證和潛在的額外延遲。

下面小節我們討論選擇和使用登錄檔的一些常見問題。這些問題都和安全相關,因為應用程式是基於映象啟動的,保障映象安全,就是在保護應用程式的安全。首先,我們會討論漏洞掃描,以及如何確保映象不包含已知的安全漏洞。然後,我會介紹常見的隔離流程,它可以有效地將外部/官方的映象引入到我們的環境中。最後,我們將討論映象信任和簽名。這是許多企業都感興趣的領域,但上游的工具和方法仍在成熟中。

3。3。1 漏洞掃描

對映象進行已知漏洞掃描是登錄檔的主要功能。通常,掃描以及通用漏洞列表 (CVEs) 資料庫由第三方元件提供。一個流行的開源選擇是 Trivy,當然,它是可插拔的,你也可以選擇使用其他元件。

當考慮 CVE 分數時,每個企業都有自己的風險接受範圍。登錄檔通常會提供一些控制元件,禁止拉取超過定義的 CVE 分數的映象。此外,將 CVEs 新增到白名單的功能,對於繞過被標記但與你的環境無關的問題、或者對於那些被認為是可接受的風險或沒有釋出和提供修復的 CVEs 非常有用。

靜態掃描用在拉取映象時,但隨著時間推移,如果在已執行的映象中發現了漏洞呢?掃描可以定期檢測這些變化,但隨後我們需要制定更新和替換映象的計劃。自動修復和推送更新後的映象的功能很誘人,而且有一些解決方案總是試圖保持映象是最新的。然而,更新映象可能會引入相容性問題,影響正在執行的應用程式。映象自動更新系統可能在你指定的部署更改流程之外,並且難以在環境中進行審計。即使是映象拉取時被阻塞,也會導致問題。如果核心應用程式的映象發現了新的 CVE,會被禁止拉取,而這些工作負載又剛好排程到了新的 Node 上,映象不能拉取,可能會導致應用程式的可用性問題。正如我們在本書中多次討論的那樣,必須理解在實現每個解決方案 (在本例中是安全性與可用性) 時背後的利弊權衡,並做出正確的、有良好文件說明的決策。

比自動修復更常見的模式是對映象漏洞掃描結果進行監控和告警,並將這些漏洞傳送給運維和安全團隊。不同的登錄檔,告警配置也不同。一些登錄檔可以配置為在完成掃描時觸發一個 webhook 呼叫,有效載荷包括漏洞映象和 CVE 詳細資訊。其他登錄檔可能會暴露一組帶有映象和 CVE 資訊的度量標準,可以使用標準工具進行告警 (如 Prometheus,後面章節會有介紹)。雖然這種方法需要更多的人工干預,但它提供了映象安全狀況更好的可見性,同時還可以更好地控制何時、如何打補丁。

一旦我們獲取了映象的 CVE 資訊,就可以根據漏洞的影響範圍決定是否修補、以及何時修補映象。如果我們需要修補和更新映象,可以透過常規的部署管道觸發更新、測試和部署。這保證了透明度和可稽核性,並且這些更改都要經過我們的常規流程。我會在後面章節詳細討論 CI/CD 和部署模型。

雖然本小節中涉及的靜態映象漏洞掃描是企業軟體供應鏈的一個常見實現部分,但它只是容器安全深度防禦策略的一層。映象可能會在部署後下載惡意內容,或者容器化的應用程式可能會在執行時被劫持。因此,實現某種型別的容器執行時掃描至關重要。比較簡單的方法是對容器的檔案系統定期掃描,確保部署後不會引入脆弱的二進位制或庫檔案。然而,為了實現更安全的保護,有必要限制容器能夠執行的操作和行為。這避免了發現和修補 CVE 時的打地鼠遊戲 (中過挖礦木馬的小夥伴可以自行腦補該畫面),而是將重點放在容器化應用程式應該具備的功能上。執行時掃描是一個很大的話題,我會在後面章節介紹相關工具和方法。

3。3。2 工作流隔離

如前所述,大多數登錄檔都提供了掃描映象中的已知漏洞並限制映象拉取的機制。然而,在使用映象之前,可能還需要滿足一些額外要求。一些企業不允許從網際網路拉取映象,只能使用內部登錄檔。這種情況可以透過使用下面描述的隔離工作流管道的多登錄檔設定來解決。

首先,我們可以為開發人員提供一個自助門戶下載映象,如 Jenkins Job、ServiceNow 等。一旦映象拉取完成,就將它推送到“隔離登錄檔“中,在那裡對映象進行檢查,管道可以啟動環境拉取並驗證映象是否符合特定標準。

檢查通過後,可以對映象進行簽名,並將其推送到內部登錄檔。還可以透過管道通知開發人員映象已被批准或拒絕 (以及拒絕原因)。整個流程如圖3-2所示。

3. 容器映象構建與分發

3-2 隔離工作流

可以將此工作流與准入控制器結合使用,以確保僅允許簽名的映象或來自特定登錄檔的映象在叢集中執行。

3。3。3 映象簽名

隨著應用程式對外部依賴 (如程式碼庫或容器映象) 的增多,保障供應鏈的安全成了一個普遍問題。

簽名是映象的安全特性之一。映象釋出者可以透過生成映象的雜湊值對映象進行加密簽名,並在推送到登錄檔前將兩者進行關聯。然後,使用者根據釋出者的公鑰驗證簽名的雜湊,從而確定映象的真實性。

在這個工作流程中,我們可以在軟體供應鏈的開始階段建立映象,並在管道各階段完成後進行簽名。我們也可以在測試完成後簽名,並在釋出團隊批准部署後再次簽名。而在部署時,我們就可以根據映象被我們指定的各方的簽名情況,再決定是否將其部署到生產中。我們不僅要確保映象已經通過了這些審批,還要確保生產環境中拉取的映象和它完全相同。流程如圖3-3所示。

3. 容器映象構建與分發

3-3 簽名流程

Notary 是實現映象簽名的主要專案,最初由 Docker 開發,基於 The Update Framework (TUF,該系統旨在促進軟體更新的安全分發)。

儘管有這些好處,但由於以下幾個原因,目前還沒有太多企業採用映象簽名。首先,Notary 有多個元件,包括一個 server 和多個數據庫。這些都是需要安裝、配置和維護的額外元件。不僅如此,由於簽名和驗證映象的功能通常位於軟體部署的關鍵路徑中,因此必須將 Notary 系統配置為高可用和可彈性的。

其次,Notary 要求每個映象都使用一個全域性唯一名稱 (GUN) 進行識別,登錄檔 URL 也被作為名稱的一部分。如果有多個登錄檔 (例如快取、邊緣位置),使用簽名會更麻煩,因為簽名與登錄檔繫結,不能移動或複製。

最後,Notary 和 TUF 需要在整個簽名過程中使用不同的金鑰對。每個金鑰都有不同的安全要求,在出現安全漏洞的情況下,修改秘鑰的難度也很大。雖然它提供了一個學術上設計良好的解決方案,但目前的 Notary/TUF 實施對許多組織來說門檻太高,這些組織也才剛剛適應他們正在使用的一些基礎設施。因此,許多人還沒有準備好用更多的便利和知識來換取簽名工作流程所提供的額外安全收益。

寫作本文時,Notary 已是 CNCF 中的專案了,希望它的新版本能夠解決剛才討論的許多問題,提升使用者體驗,早日成為 CNCF 畢業專案。目前已經有幾個專案實現了准入控制 webhook,在 Kubernetes 叢集啟動這些映象前,檢查映象以確保它們已經被簽署。一旦這些問題得到解決,我們預計簽名將成為軟體供應鏈中一個經常實施的屬性,並且這些簽名准入控制 webhook 也會進一步成熟。

3。4 Harbor 映象登錄檔

透過前面章節的介紹,你對容器映象構建和登錄檔都有了一定的瞭解,在這一節中,我們討論生產級映象登錄檔的實現。

我選擇了 Harbor 作為映象登錄檔,它是雲原生環境下主流的映象登錄檔,已從 CNCF 畢業。Harbor 透過策略和 RBAC (基於角色的訪問控制) 保護儲存的映象,並對映象進行掃描,將透過掃描的映象標記為可信映象。

在前面討論過,登錄檔可以作為 Kubernetes 叢集所需的獨立服務,部署在 Kubernetes 叢集外。我採用的就是這種方式。

3。4。1 Harbor 架構介紹

從 Harbor V2。0 開始,Harbor 已經發展為完全符合 OCI 的雲原生 Artifact 登錄檔。這也就意味著,我們使用任何構建工具構建出的符合 OCI 的映象,都可以儲存到 Harbor 登錄檔中。在 Harbor V2。0 中,可以管理的 Artifact 包括符合 OCI 映象規範的映象、manifest 列表、Helm Chart (Helm 最佳實踐)、CNABs、OPAs 等。並提供了拉、推、刪除、標記、複製、掃描的功能,還可以對映象和 manifest 簽名。

Harbor 的整體架構如圖3-4:

3. 容器映象構建與分發

3-4 harbor architecture。 圖片來自 Harbor 官網

Harbor 架構分為三層:資料訪問層,用於儲存 Harbor 產生的資料;基本服務層包含了 Harbor 的相關服務;消費層可以理解為 Harbor 的客戶端。

3。4。1。1 資料訪問層

k-v storage

:Redis 服務。提供資料快取功能,並支援臨時持久化 Job 服務的元資料。

data storage

:支援多種儲存作為登錄檔和 chartMuseum 的後端儲存。

Database

:PostgreSQL 服務。儲存 Harbor 模型的相關元資料,如專案、使用者、角色、複製策略、tag 保留策略、scanners、charts、映象。

3。4。1。2 基本服務層

Proxy

:Nginx 反向代理,提供 API 路由功能。後端包括 Harbor 的核心服務、registry、web 介面和 token 服務等。Proxy 將客戶端的請求轉發到後端服務。

Core

:Harbor 的核心服務,主要提供以下功能:

API Server:接受 REST API 請求的 HTTP 服務,包括“Authentication & Authorization”、“Middleware”、“API Handlers”等子模組。 Authentication & Authorization: 請求受認證服務的保護,認證服務可以由本地資料庫、AD/LDAP 或 OIDC 提供。 RBAC 機制用於對相關操作執行授權,例如:push/pull 映象。 Token 服務的目的是根據使用者在專案中的角色,為每個 docker pull/push 命令發放一個令牌。如果從 Docker 客戶端傳送的請求中沒有令牌,Registry 會將請求重定向到 Token 服務。 Middleware:提前預處理一些請求,以確定它們是否符合所需的標準,並可以轉發給後端元件進行進一步處理。一些功能被實現為各種中介軟體,如 “配額管理”、“簽名檢查”、“漏洞嚴重程度檢查 ”和 “機器人賬戶解析 ”等。 API Handlers:處理相應的 REST API 請求,主要側重於解析和驗證請求引數,在相關的 API 控制器上完成業務邏輯,並返回生成的響應。 Config Manager:涵蓋所有系統配置的管理,如認證型別設定、電子郵件設定和證書等。 Project Management:管理專案的基礎資料和相應的元資料,隔離 artifacts。 Quota Manager:管理專案的配額設定,並在發生新推送時執行配額驗證。 Chart Controller:將圖表相關的請求代理到後端 chartmuseum,並提供幾個擴充套件來改善圖表管理體驗。 Retention Manager:管理 tag 保留策略,執行和監控 tag 保留過程。 Content Trust:擴充套件後端 Notary 的功能,以支援流暢的內容信任過程。目前只支援對容器映象進行簽名。 Replication Controller:管理複製策略和登錄檔介面卡,觸發和監控併發的複製過程。以下是適配的登錄檔: Distribution (docker registry) Docker Hub Ali ACR Amazon ECR Azure ACR Helm Hub Huawei SWR GitLab Registry Google GCR Quay Scan Manager:管理配置的掃描器 (支援不同的掃描器),為指定的 artifacts 提供掃描摘要和報告。 支援 Trivy scanner (預設)、Clair scanner、DoSec scanner。 目前只支援對容器映象或構建在映象之上的軟體集 (如 OCI manifest 列表索引、CNAB) 的掃描 Notification Manager (webhook):在 Harbor 中配置的一種機制,以便 Harbor 中的 artifact 狀態變化可以被填充到 Harbor 中配置的 Webhook 端點。相關方可以透過監聽 webhook 事件來觸發一些後續操作。目前支援兩種方式: HTTP Post request Slack channel OCI Artifact Manager:管理整個 Harbor 登錄檔中所有 OCI artifacts 的生命週期的核心元件。它提供了CRUD操作來管理元資料和相關的附加物,如掃描報告、容器映象的構建歷史和 readme、依賴、Helm 圖示的值檔案等,它還支援管理 artifact tags 和其他有用的操作。 Registry Driver:作為一個登錄檔客戶端 SDK 來實現與底層登錄檔 (目前是 Docker distribution) 的通訊。“OCI Artifact Manager”依賴該驅動從底層登錄檔的指定 artifact 的 manifest 甚至是 config JSON 中獲取額外資訊。

Job Service

:通用 Job 執行佇列服務,讓其他元件/服務透過簡單的 restful APIs 提交併發運行非同步任務的請求。

Log collector

:日誌收集器,負責將其他模組的日誌收集到一個地方。

GC Controller

:管理線上 GC 計劃設定並啟動和跟蹤 GC 進度。

Chart Museum

:第三方圖表儲存庫服務,提供圖表管理和 API 訪問。

Docker Registry

:第三方登錄檔服務,負責儲存 Docker 映象,處理 Docker push/pull 命令。由於 Harbor 需要對映象實施訪問控制,登錄檔將引導客戶到 Token 服務,以便每個 pull/push 請求獲得一個有效的令牌。

Notary

:第三方內容信任服務,負責安全地釋出和驗證內容,在上面小節已經做過介紹。

3。4。1。3 消費層

作為標準的雲原生 artifact 登錄檔,自然支援相關的客戶端 (如 Docker CLI、Notary client、Oras 和 Helm)。除了這些客戶端,Harbor 還提供了一個 Web 管理介面,方便管理和監控 artifacts。

Web Portal

:使用者圖形介面,幫助使用者管理登錄檔上的映象。

3。4。2 部署架構

映象登錄檔是軟體交付過程中的關鍵元件,如果登錄檔出現異常,會導致我們 pull/push 映象失敗,最終影響應用程式的構建和部署。所以我們採用雙節點部署,並配置雙向複製的方式實現 Harbor 的高可用性。部署架構圖如3-5:

3. 容器映象構建與分發

3-5 harbor deploy architecture

3。4。3 部署環境介紹

假設你已經有了高可用的 Nginx 或其他 LB 服務,在這個示例中,我側重介紹實現 Harbor 的高可用。

準備兩個節點部署 Harbor 服務,節點的資源要求:CPU >= 2C;記憶體 >= 4G;磁碟應根據實際的儲存需求提供。

3。4。4 部署 Harbor

以下操作需要在兩臺 Harbor 節點上分別執行:

我們使用 docker-compose 安裝 Harbor,所以需要先安裝 docker 和 docker-compose 工具。有關 docker 的安裝,請參考上一章《容器執行時》。

在 https://github。com/docker/compose/releases 頁面下載與作業系統版本對應的 docker-compose,移動到 目錄下,並新增執行許可權。

Harbor 的部署過程比較簡單,首先下載並解壓縮最新版的 offline 安裝包:

進入解壓縮後的目錄,透過 Harbor 模板配置檔案生成配置檔案:

編輯 配置檔案,修改以下欄位內容,其他選項可以先保持預設設定,有需要時再調整:

準備並安裝:

3。4。5 配置 Harbor

在瀏覽器中透過這兩個節點的 IP,分別登入到兩個 Harbor web 控制檯。使用者名稱是 ,密碼是配置檔案中 欄位的值。

在兩個 Harbor web 控制檯建立相同的專案和使用者,並在專案成員中新增新建立的使用者為專案管理者角色。

3。4。5。1 配置雙向複製

本文介紹的高可用方式,是一種熱備的形式,只有一個登錄檔為客戶端提供服務,當這個登錄檔故障時,Nginx 會將客戶端請求轉發到另外一個登錄檔。為了使兩個登錄檔保持一致,我們需要配置倉庫的雙向同步機制。

在系統管理,倉庫管理中,點選新建目標,根據提示填寫相關資訊,目標URL填寫另一個 Harbor 的地址 (http:ip),把“驗證遠端證書”的鉤子去掉。點選測試連線,通過後點選確定儲存即可。

在複製管理中點選新建規則,填寫規則名稱,目標倉庫選擇上一步新建的目標,觸發模式選擇為事件驅動,並勾選“刪除本地資源時同時也刪除遠端的資源”和“覆蓋”框。儲存即可。

3。4。5。2 配置 Nginx

Nginx 的配置檔案如下:

在 Nginx 中新增以上配置檔案,並重新載入 Nginx 的配置。

3。4。6 驗證

在瀏覽器中,透過 Nginx 中 指定的域名登入 Harbor web 控制檯;在終端,透過 docker CLI 登入 Harbor 並上傳映象到新建的倉庫。

3。5 小結

可以把映象理解為靜態的容器檔案。在構建容器映象時,我們應該遵循一些最佳實踐,並透過管道將這些實踐程式碼化。BuildKit 和 Cloud Native Buildpacks 是主流的容器映象構建工具,很符合雲原生環境下的容器構建理念,可以把它們與 CI 整合。在選擇映象登錄檔時,除了基本功能外,還要考慮它的可靠性,因為映象登錄檔是很重要的基礎服務。

TAG: 映象登錄檔構建容器HARBOR