解鎖 GitOps 效率:資源清單渲染模式
GitOps 原則的存在是為了解決在使用 Kubernetes 這樣復(fù)雜系統(tǒng)時(shí)可見性和協(xié)作方面的實(shí)際問題。這些原則強(qiáng)調(diào)聲明式期望狀態(tài)和持續(xù)調(diào)和的重要性。然而,它們?cè)诰唧w實(shí)施上留有很大的解釋空間。我們經(jīng)常收到關(guān)于如何構(gòu)建 Git 倉庫、使用什么配置管理工具以及如何使用分支等問題。
你使用的配置管理工具不會(huì)對(duì)實(shí)施 GitOps 的能力產(chǎn)生重大影響。Helm 和 Kustomize 都滿足了標(biāo)準(zhǔn)化清單集合的需求,并且能夠根據(jù)特定部署環(huán)境修改這些清單。這種抽象對(duì)于保持清單的 DRY[1](不重復(fù)原則)非常方便。
然而,這些抽象也帶來了新的問題。GitOps 工具通常直接引用它們來確定期望狀態(tài)。對(duì) Helm Chart 或 Kustomization 基礎(chǔ)的更改就是對(duì)抽象的更改;這種更改對(duì)部署到環(huán)境中的清單的真實(shí)影響并不明確。
Argo CD 在運(yùn)行時(shí)渲染清單的示意圖
核心前提
Kubernetes 集群的期望狀態(tài)不是一個(gè) Helm Chart 或 Kustomization;而是由這些配置管理工具渲染出的清單。遺憾的是,大多數(shù)組織在實(shí)踐 GitOps 時(shí)通常會(huì)將其抽象出來存儲(chǔ)在 Git 中。在 git 和 kubectl 之間的任何抽象都可能產(chǎn)生意外效果;你不應(yīng)該在應(yīng)用之前修改你的真實(shí)來源。
存儲(chǔ)在 Git 中并經(jīng)過審批的期望狀態(tài)不應(yīng)該包含任何與將要應(yīng)用到集群的內(nèi)容不同的抽象。它應(yīng)該像容器鏡像一樣被對(duì)待,是不可變的并按原樣應(yīng)用到集群中。讓 GitOps 工具在將清單應(yīng)用到集群時(shí)運(yùn)行 Helm 或 Kustomize,就像讓你的容器在啟動(dòng)時(shí)運(yùn)行 apt-get install 一樣。
解決方案就是渲染清單模式。對(duì) GitOps 倉庫主干(即 main 分支)的每次更改都會(huì)觸發(fā) CI 工作流來渲染清單,并將渲染后的清單按原樣存儲(chǔ)在 Git 中。這個(gè)制品代表了集群的期望狀態(tài),沒有任何混淆。
渲染后的清單應(yīng)該被分離到特定環(huán)境的分支中。當(dāng)這些分支發(fā)生更改時(shí),提交之間的清單差異將完全透明。你可以清楚地看到 main 分支上的更改對(duì)每個(gè)環(huán)境的影響。
Argo CD 應(yīng)用在 CI 中渲染的清單的示意圖
渲染清單模式的優(yōu)勢(shì)一目了然:
- 消除配置管理工具(如 Helm、Kustomize)引入的混淆,提高期望狀態(tài)的可見性。
- 通過真正不可變的期望狀態(tài)降低內(nèi)置工具帶來的風(fēng)險(xiǎn)。
- 通過一次性渲染清單提高 Argo CD 的性能。
- 基于環(huán)境設(shè)置部署和保護(hù)策略。
值得注意的是,這種模式有兩個(gè)缺點(diǎn):
- 將清單渲染轉(zhuǎn)移到 CI 引擎增加了復(fù)雜性。
- 它與渲染明文密鑰的工具(如 Sealed Secrets)配合不好。
你可能仍然持懷疑態(tài)度,讓我們?cè)敿?xì)分析一下。
這不就是 Gitflow 嗎?
讓我們先解決這個(gè)問題。表面上看,渲染清單模式可能聽起來像 Gitflow[2],但實(shí)際上并不是。你可以繼續(xù)在 GitOps 倉庫中使用短期特性分支或主干開發(fā)模式。
特定環(huán)境的分支并不用于環(huán)境之間的 promotion[3] - 一個(gè)環(huán)境分支中的更改不會(huì)被合并到另一個(gè)分支中。相反,這些分支的內(nèi)容由自動(dòng)化工作流維護(hù),并基于 main 分支的內(nèi)容生成。
可以將這些分支的內(nèi)容視為一個(gè)發(fā)布包,其中包含了環(huán)境期望狀態(tài)的純(渲染后的)清單。就像貢獻(xiàn)者永遠(yuǎn)不應(yīng)該拉取容器鏡像、修改內(nèi)容并推回注冊(cè)表一樣,任何人都不應(yīng)該直接提交到特定環(huán)境的分支。它們是基于 main 分支內(nèi)容生成的制品。
消除混淆以提高可見性
看看下面這個(gè)在 GitOps 倉庫中維護(hù)的 Helm Umbrella chart[4] 更改的例子:

看這個(gè)差異,很難直觀地知道結(jié)果會(huì)是什么。你只知道 Helm chart 依賴的版本在改變。它沒有提供渲染并應(yīng)用到集群的清單變更的上下文。
再看一個(gè)使用 Kustomize 的例子:
顯示 Kustomization 基礎(chǔ)更改的截圖
要了解會(huì)發(fā)生什么變化,你需要檢查使用這個(gè) Kustomize 基礎(chǔ)的每個(gè)環(huán)境。
如果你想確定最終的清單(實(shí)際部署到集群中的資源),你需要運(yùn)行 helm template 或 kustomize build。在 Argo CD 中,repo server 執(zhí)行這個(gè)功能。每當(dāng) Application 的源發(fā)生變化或緩存過期時(shí),它就會(huì)運(yùn)行 helm template、kustomize build 或使用 CMP 的任何自定義工具來生成清單。然后這些清單被傳遞給 application controller 執(zhí)行 kubectl apply。
清單的更改應(yīng)該被持續(xù)集成并產(chǎn)生不可變的制品,就像代碼一樣。將清單生成移到 CI 工作流中并存儲(chǔ)在特定環(huán)境的分支上,將提供一個(gè)不可變的期望狀態(tài),其中的更改清晰可見。
使用上面的 Helm chart 例子,這就是使用渲染清單模式時(shí)差異的樣子。chart 版本的一行更改導(dǎo)致生成的清單中有近 1,000 行的變化。
通過不可變期望狀態(tài)降低工具風(fēng)險(xiǎn)
GitOps 工具的升級(jí)可能會(huì)導(dǎo)致內(nèi)置工具鏈[5](即 Kustomize、Helm)的升級(jí),這可能會(huì)影響它管理的每個(gè)環(huán)境。記住 - git 和 kubectl 之間的任何抽象都可能產(chǎn)生意外效果。
當(dāng)升級(jí) Argo CD 的差異看起來是這樣的...
...你怎么能確定這對(duì)這個(gè) Argo CD 實(shí)例中每個(gè) Application 渲染的清單有什么影響?(在這個(gè)例子中,從 5.42.0 升級(jí)到 5.43.0 的 argo-cd Helm chart 將 Argo CD 升級(jí)到 v2.8.0,其中包括將 helm 版本升級(jí)到 3.12.0。)
持續(xù)集成的核心前提是快速反饋,在問題進(jìn)入生產(chǎn)系統(tǒng)之前發(fā)現(xiàn)它們。這個(gè)原則同樣適用于描述 Kubernetes 集群期望狀態(tài)的清單。當(dāng) GitOps 工具負(fù)責(zé)生成清單時(shí),問題直到更改被批準(zhǔn)、合并到 main 并嘗試應(yīng)用到集群后才會(huì)被發(fā)現(xiàn)。在這個(gè)時(shí)候,修復(fù)它需要重復(fù)整個(gè)過程。
通過將清單生成移到 CI 工作流中并將清單存儲(chǔ)在特定環(huán)境的分支中,你可以為集群的期望狀態(tài)獲得保證的穩(wěn)定性。原始 YAML 被捕獲在一個(gè)不可變的版本(提交)中。Argo CD 不再轉(zhuǎn)換 Git 中的內(nèi)容;它直接將其應(yīng)用到集群中而不做修改。
提升 Argo CD 的性能
在 Argo CD 中運(yùn)行 Kustomize 和 Helm 是很消耗資源的。每次 Application 的目標(biāo)版本發(fā)生變化、緩存過期或有人執(zhí)行強(qiáng)制刷新時(shí),repo server 都需要重新生成清單。
當(dāng)你查看大型 Argo CD 部署中 repo server 的資源請(qǐng)求時(shí),很容易看出清單生成有多消耗資源。我見過 repo server 被分配 32 個(gè) CPU 和 200GiB 內(nèi)存的情況!
這在單體倉庫中尤其明顯,其中一個(gè)文件夾中清單的更改可能會(huì)不必要地導(dǎo)致 Argo CD 為許多 Application 重新生成清單。
通過利用 CI 引擎生成原始清單并將其存儲(chǔ)在特定環(huán)境的分支上,這個(gè)過程只需要使用按需計(jì)算資源執(zhí)行一次(而不是持續(xù)為 repo server 預(yù)留資源)。
自動(dòng)化開發(fā)環(huán)境并保護(hù)生產(chǎn)環(huán)境
到目前為止,這種模式的優(yōu)勢(shì)主要可以歸因于"你應(yīng)該在 CI 中渲染清單"這一部分。第二部分"并將它們存儲(chǔ)在特定環(huán)境的分支中"現(xiàn)在發(fā)揮作用了。
將渲染后的清單分離到分支中,可以為每個(gè)環(huán)境設(shè)置不同的策略。
再次考慮顯示 Kustomize 基礎(chǔ)更改的差異。
如果不使用渲染清單模式并使用自動(dòng)同步策略,這個(gè)更改將同時(shí)應(yīng)用到每個(gè)環(huán)境。
使用渲染清單模式,在主分支上更改 kustomize 基礎(chǔ)將導(dǎo)致在 CI 中渲染清單。對(duì)于非生產(chǎn)環(huán)境,清單可能會(huì)被自動(dòng)部署,所以它們會(huì)直接推送到特定環(huán)境的分支。
大多數(shù)組織對(duì)生產(chǎn)環(huán)境做同樣的事情感到不舒服(很少有組織成熟到可以實(shí)踐持續(xù)部署)。相反,為生產(chǎn)環(huán)境渲染的清單可以推送到一個(gè)短期分支。然后,創(chuàng)建一個(gè) PR,提議對(duì)包含現(xiàn)有期望狀態(tài)的特定環(huán)境分支進(jìn)行更改。
因?yàn)榍鍐我呀?jīng)渲染完成,PR 中顯示的差異將代表沒有混淆或抽象的更改。那些審查和批準(zhǔn) PR 的人可以輕松理解更改的真實(shí)影響。
大多數(shù)源代碼管理(SCM)提供商都有廣泛的分支權(quán)限控制[6]。使用這些控制,你可以確保對(duì)生產(chǎn)環(huán)境期望狀態(tài)的任何更改都遵循了正確的流程。例如,代碼所有者已經(jīng)給出批準(zhǔn)審查,所有需要的檢查都已成功運(yùn)行,并且永遠(yuǎn)不會(huì)直接推送到生產(chǎn)分支。
你可能會(huì)想,"好吧,我可以在 Argo CD 中對(duì)生產(chǎn)環(huán)境的 Application 使用手動(dòng)同步來獲得類似的解決方案。"雖然這是對(duì)的,但缺點(diǎn)是當(dāng)你的 Application 處于未同步狀態(tài)時(shí),期望狀態(tài)是模糊的。Argo CD 顯示了差異,但在執(zhí)行同步時(shí)生成的清單可能會(huì)有所不同。
缺點(diǎn)
增加了 CI 復(fù)雜性
不可否認(rèn),這種模式增加了額外的 CI 自動(dòng)化要求。不應(yīng)低估使用 Argo CD 進(jìn)行清單生成的簡單性。Argo CD 在集成 Helm 和 Kustomize 并提供可靠的清單生成方面做了大量工作。
對(duì)于 Argo CD,已經(jīng)有幾次嘗試將 Application 渲染清單的差異添加到 pull request 中。我創(chuàng)建了一個(gè)名為 argocd-diff-action 的 GitHub Action 來解決這個(gè)問題(它目前已經(jīng)停止維護(hù),因?yàn)槲疫x擇使用渲染清單模式為我的 Kubernetes 集群的期望狀態(tài)生成不可變的制品,并提供清晰和信息豐富的差異)。Zapier 最近開源了他們的內(nèi)部工具 `kubechecks`[7],它執(zhí)行類似的功能,并添加了策略檢查等功能。
在 Akuity,我們創(chuàng)建了一個(gè)內(nèi)部使用的工具來采用渲染清單模式。雖然該項(xiàng)目仍在積極開發(fā)中,但目標(biāo)是將其開源,這樣 Argo CD 用戶就可以使用他們現(xiàn)有的清單渲染定義(即 Application 清單),并使用該工具在 CI 中而不是在 Argo CD 中渲染它們。
渲染明文密鑰
像 Kustomize + SOPS 這樣的 Kubernetes Secrets 管理工具與渲染清單模式不兼容。它們?cè)试S用戶在 Git 中存儲(chǔ)加密的密鑰,并依賴在集群中運(yùn)行的工具來渲染清單和解密密鑰。
這對(duì)于渲染清單模式來說并不理想,因?yàn)榻饷芎蟮?strong>密鑰會(huì)以明文形式出現(xiàn)在特定環(huán)境的分支中。
在采用這種模式之前,建議使用像 External Secrets Operator[8] 這樣的工具,它使用 ExternalSecret[9] 資源,這些資源包含對(duì) SecretStore 中數(shù)據(jù)的引用,可以安全地存儲(chǔ)在 Git 中。然后它使用集群內(nèi)控制器基于 ExternalSecrets 生成 Kubernetes Secret。在我們的博客文章 如何使用 GitOps 管理 Kubernetes 密鑰?[10] 中,我們解釋了為什么這是我們首選的方法,無論你是否使用渲染清單模式。
結(jié)論
雖然 GitOps 原則強(qiáng)調(diào)聲明式期望狀態(tài)和持續(xù)調(diào)和,但在構(gòu)建 Git 倉庫、選擇配置管理工具和管理分支方面留下了很大的解釋空間。渲染清單模式通過確保存儲(chǔ)在 Git 中的期望狀態(tài)保持清晰和不可變,并且在應(yīng)用到集群時(shí)不進(jìn)行轉(zhuǎn)換或抽象,解決了這些問題。
這種模式通過提高可見性、消除混淆和降低工具風(fēng)險(xiǎn),實(shí)現(xiàn)了更好的安全性和更安全的變更管理。它通過將清單生成過程轉(zhuǎn)移到 CI 工作流中,提高了性能,特別是在大型 Argo CD 部署中。再加上為每個(gè)環(huán)境設(shè)置不同策略的能力,為你的 GitOps 工作流增加了靈活性和控制,同時(shí)保護(hù)生產(chǎn)環(huán)境。
大多數(shù)源代碼管理(SCM)提供商都有廣泛的分支權(quán)限控制[11]。使用這些控制,你可以確保對(duì)生產(chǎn)環(huán)境期望狀態(tài)的任何更改都遵循了正確的流程。例如,代碼所有者已經(jīng)給出批準(zhǔn)審查,所有需要的檢查都已成功運(yùn)行,并且永遠(yuǎn)不會(huì)直接推送到生產(chǎn)分支。
你可能會(huì)想,"好吧,我可以在 Argo CD 中對(duì)生產(chǎn)環(huán)境的 Application 使用手動(dòng)同步來獲得類似的解決方案。"雖然這是對(duì)的,但缺點(diǎn)是當(dāng)你的 Application 處于未同步狀態(tài)時(shí),期望狀態(tài)是模糊的。Argo CD 顯示了差異,但在執(zhí)行同步時(shí)生成的清單可能會(huì)有所不同。
缺點(diǎn)
增加了 CI 復(fù)雜性
不可否認(rèn),這種模式增加了額外的 CI 自動(dòng)化要求。不應(yīng)低估使用 Argo CD 進(jìn)行清單生成的簡單性。Argo CD 在集成 Helm 和 Kustomize 并提供可靠的清單生成方面做了大量工作。
對(duì)于 Argo CD,已經(jīng)有幾次嘗試將 Application 渲染清單的差異添加到 pull request 中。我創(chuàng)建了一個(gè)名為 argocd-diff-action 的 GitHub Action 來解決這個(gè)問題(它目前已經(jīng)停止維護(hù),因?yàn)槲疫x擇使用渲染清單模式為我的 Kubernetes 集群的期望狀態(tài)生成不可變的制品,并提供清晰和信息豐富的差異)。Zapier 最近開源了他們的內(nèi)部工具 `kubechecks`[12],它執(zhí)行類似的功能,并添加了策略檢查等功能。
在 Akuity,我們創(chuàng)建了一個(gè)內(nèi)部使用的工具來采用渲染清單模式。雖然該項(xiàng)目仍在積極開發(fā)中,但目標(biāo)是將其開源,這樣 Argo CD 用戶就可以使用他們現(xiàn)有的清單渲染定義(即 Application 清單),并使用該工具在 CI 中而不是在 Argo CD 中渲染它們。
渲染明文密鑰
像 Kustomize + SOPS 這樣的 Kubernetes Secrets 管理工具與渲染清單模式不兼容。它們?cè)试S用戶在 Git 中存儲(chǔ)加密的密鑰,并依賴在集群中運(yùn)行的工具來渲染清單和解密密鑰。
這對(duì)于渲染清單模式來說并不理想,因?yàn)榻饷芎蟮?strong>密鑰會(huì)以明文形式出現(xiàn)在特定環(huán)境的分支中。
在采用這種模式之前,建議使用像 External Secrets Operator[13] 這樣的工具,它使用 ExternalSecret[14] 資源,這些資源包含對(duì) SecretStore 中數(shù)據(jù)的引用,可以安全地存儲(chǔ)在 Git 中。然后它使用集群內(nèi)控制器基于 ExternalSecrets 生成 Kubernetes Secret。在我們的博客文章 如何使用 GitOps 管理 Kubernetes 密鑰?[15] 中,我們解釋了為什么這是我們首選的方法,無論你是否使用渲染清單模式。
結(jié)論
雖然 GitOps 原則強(qiáng)調(diào)聲明式期望狀態(tài)和持續(xù)調(diào)和,但在構(gòu)建 Git 倉庫、選擇配置管理工具和管理分支方面留下了很大的解釋空間。渲染清單模式通過確保存儲(chǔ)在 Git 中的期望狀態(tài)保持清晰和不可變,并且在應(yīng)用到集群時(shí)不進(jìn)行轉(zhuǎn)換或抽象,解決了這些問題。
這種模式通過提高可見性、消除混淆和降低工具風(fēng)險(xiǎn),實(shí)現(xiàn)了更好的安全性和更安全的變更管理。它通過將清單生成過程轉(zhuǎn)移到 CI 工作流中,提高了性能,特別是在大型 Argo CD 部署中。再加上為每個(gè)環(huán)境設(shè)置不同策略的能力,為你的 GitOps 工作流增加了靈活性和控制,同時(shí)保護(hù)生產(chǎn)環(huán)境。
本文使用 Cursor 轉(zhuǎn)譯,原文地址:https://akuity.io/blog/the-rendered-manifests-pattern
參考資料
[1]DRY: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself。
[2]Gitflow: https://nvie.com/posts/a-successful-git-branching-model/。
[3]環(huán)境之間的 promotion: https://developers.redhat.com/articles/2022/07/20/git-workflows-best-practices-gitops-deployments。
[4]Helm Umbrella chart: https://www.youtube.com/watch?v=MlAWr8bVr0I&t=170s。
[5]內(nèi)置工具鏈: https://github.com/argoproj/argo-cd/blob/e37ff6f0ae02db0739bc480e987c19bd4573e082/hack/tool-versions.sh。
[6]分支權(quán)限控制: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/managing-a-branch-protection-rule。
[7]kubechecks
: https://github.com/zapier/kubechecks。
[8]External Secrets Operator: https://external-secrets.io/latest/。
[9]ExternalSecret: https://external-secrets.io/latest/api/externalsecret/。
[10]如何使用 GitOps 管理 Kubernetes 密鑰?: https://akuity.io/blog/how-to-manage-kubernetes-secrets-gitops/。
[11]分支權(quán)限控制: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/managing-a-branch-protection-rule。
[12]kubechecks: https://github.com/zapier/kubechecks。
[13]External Secrets Operator: https://external-secrets.io/latest/。
[14]ExternalSecret: https://external-secrets.io/latest/api/externalsecret/。
[15]如何使用 GitOps 管理 Kubernetes 密鑰?: https://akuity.io/blog/how-to-manage-kubernetes-secrets-gitops/。