本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
與 Kluctl 和伺服器端應用共存
這篇博文的靈感來自之前的一篇關於高階伺服器端應用(Advanced Server Side Apply)的 Kubernetes 部落格文章。該博文的作者列出了應用程式和控制器在切換到伺服器端應用(下文簡稱 SSA)時的多個好處。特別是關於 CI/CD 系統的章節,激勵我寫下我的想法和經驗作為回應。
這些想法和經驗是我過去兩年在 Kluctl 專案上工作的結果。我將 Kluctl 描述為“將由多個較小部分(Helm/Kustomize/...)組成的大型 Kubernetes 部署以可管理和統一的方式組合在一起的缺失的粘合劑”。
為了對 Kluctl 有一個基本的瞭解,我建議訪問 kluctl.io 網站並閱讀文件和教程,例如微服務演示教程。作為替代方案,你可以觀看 Rawkode Academy YouTube 頻道的《Kluctl 實戰介紹》,其中展示了一個動手演示環節。
還有一個針對 podtato-head 演示專案的 Kluctl 交付場景。
互不干擾
Kluctl 遵循的主要哲學之一是“互不干擾”,這意味著它會盡最大努力與在叢集內外執行的任何其他工具或控制器協同工作。Kluctl 不會覆蓋任何它已失去所有權的欄位,除非你明確指示它這樣做。
如果不使用 SSA,實現這一點是不可能的(或者至少要困難幾個數量級)。伺服器端應用允許 Kluctl 檢測欄位的所有權何時丟失,例如當另一個控制器或 Operator 將該欄位更新為另一個值時。然後,Kluctl 可以在欄位級別上決定是否需要在基於這些決策重試之前強制應用。
SSA 之前的日子
Kluctl 的最初版本是基於呼叫 kubectl
來實現的,因此隱式地依賴於客戶端應用。那時,SSA 仍處於 alpha 階段且相當不穩定。老實說,我當時甚至不知道有這麼個東西。
客戶端應用的工作方式有一些嚴重的缺點。最明顯的一個(如果你有足夠的時間,你肯定會自己遇到這個問題)是它依賴於一個被新增到物件上的註解(kubectl.kubernetes.io/last-applied-configuration
),這帶來了所有與巨大註解值相關的限制和問題。這類問題的一個很好的例子是CRD 太大,以至於它們無法再放入註解的值中。
另一個缺點可以從它的名字(**客戶端**應用)中看出。作為**客戶端**側意味著每個客戶端都必須自己提供應用邏輯,而當時只有 kubectl
內部正確地實現了這一點,這使得在控制器內部複製它變得困難。
這使得所有希望利用應用邏輯的控制器都將 kubectl
新增為依賴(無論是作為可執行檔案還是以 Go 包的形式)。
然而,即使設法從控制器內部執行客戶端應用,你最終得到的解決方案也無法控制其內部工作方式。例如,無法單獨決定在發生外部更改時覆蓋哪些欄位,以及放過哪些欄位。
發現 SSA apply
我一直對上述解決方案不滿意,然後不知何故偶然發現了伺服器端應用,當時它仍處於 beta 階段。透過 kubectl apply --server-side
對其進行實驗後,我立即發現,透過呼叫 kubectl
無法輕易利用 SSA 的真正威力。
kubectl
中 SSA 的實現方式不允許對沖突解決進行足夠的控制,因為它只能在“不強制應用任何東西並報錯”和“不留情面地強制應用所有東西!”之間切換。
然而,API 文件明確指出,SSA 能夠在欄位級別上控制衝突解決,只需選擇在提供的物件中包含哪些欄位和省略哪些欄位即可。
告別 kubectl
這意味著 Kluctl 必須首先放棄呼叫 kubectl
。只有在完成之後,我才能正確地實現 SSA 及其強大的衝突解決功能。
為了實現這一點,我首先透過 Kubernetes 客戶端庫實現了對目標叢集的訪問。這還有一個很好的副作用,就是極大地加快了 Kluctl 的速度。它還透過確保正在執行的 Kluctl 命令不會被外部修改 kubeconfig 的操作所幹擾,從而提高了 Kluctl 的安全性和可用性。
實現 SSA
切換到 Kubernetes 客戶端庫後,利用 SSA 變得很容易。Kluctl 現在需要將每個清單作為 PATCH
請求的一部分發送到 API 伺服器,這表示 Kluctl 想要執行 SSA 操作。然後,API 伺服器會響應一個 OK 響應(HTTP 狀態碼 200),或者一個 Conflict 響應(HTTP 狀態碼 409)。
在出現 Conflict 響應的情況下,該響應的主體包含有關衝突的機器可讀的詳細資訊。然後,Kluctl 可以使用這些詳細資訊來確定哪些欄位存在衝突,以及哪些參與者(欄位管理器)擁有衝突欄位的所有權。
然後,對於每個欄位,Kluctl 將決定是應該忽略衝突還是應該強制應用。如果任何欄位需要強制應用,Kluctl 將重試應用操作,省略被忽略的欄位,並在 API 呼叫上設定 force
標誌。
如果忽略了某個衝突,Kluctl 會向用戶發出警告,以便使用者可以正確地做出反應(或者永遠忽略它……)。
基本上就是這樣了。這就是利用 SSA 所需要的一切。非常感謝併為實現這一點的 Kubernetes 開發者們點贊!
衝突解決
Kluctl 有幾條簡單的規則來判斷一個衝突是應該被忽略還是強制應用。
它首先檢查欄位的參與者(欄位管理器)是否在已知欄位管理器字串列表中,這些字串來自經常用於執行手動修改的工具。例如 kubectl
和 k9s
。使用這些工具執行的任何修改都被認為是“臨時的”,並將被 Kluctl 覆蓋。
如果你將 Kluctl 與 kubectl
一起使用,且不希望 kubectl
的更改被覆蓋(例如,在指令碼中使用),那麼你可以在 kubectl
的命令列中指定 --field-manager=<manager-name>
,這樣 Kluctl 就不會應用其特殊的啟發式規則。
如果 Kluctl 不知道該欄位管理器,它將檢查是否請求對該欄位進行強制應用。強制應用可以透過不同的方式請求:
- 透過向 Kluctl 傳遞
--force-apply
。這將導致所有欄位在發生衝突時被強制應用。 - 透過向相關物件新增
kluctl.io/force-apply=true
註解。這將導致該物件的所有欄位在發生衝突時被強制應用。 - 透過向相關物件新增
kluctl.io/force-apply-field=my.json.path
註解。這僅導致與 JSON 路徑匹配的欄位在發生衝突時被強制應用。
當某個其他參與者已知會錯誤地宣告欄位時(例如,ECK operator 會對 nodeSets 欄位執行此操作),需要將欄位標記為強制應用,這樣你可以確保 Kluctl 始終將這些欄位覆蓋為原始值或新值。
未來,Kluctl 將允許對沖突解決進行更多控制。例如,CLI 將允許在欄位級別控制強制應用。
DevOps 與控制器
那麼,Kluctl 中的 SSA 是如何實現“互不干擾”的呢?
它允許經典流水線(例如 Github Actions 或 Gitlab CI)、控制器(例如 HPA 控制器或 GitOps 風格的控制器)甚至管理員從本地機器執行部署共存。
無論你的基礎架構自動化之旅進行到哪個階段,Kluctl 都能為你提供一席之地。從使用 PC 上的指令碼執行部署,到流水線本身在程式碼中定義的完全自動化的 CI/CD,Kluctl 旨在補充適合你的工作流程。
即使在完全自動化一切之後,你也可以在需要時使用管理員許可權進行干預,執行一個 kubectl
命令來修改欄位並阻止 Kluctl 覆蓋它。你只需要切換到一個不會被 Kluctl 覆蓋的欄位管理器(例如“admin-override”)即可。
一些要點
伺服器端應用是一項很棒的功能,對於 Kubernetes 中控制器和工具的未來至關重要。涉及的控制器數量只會越來越多,而正確的協同工作模式是必須的。
我相信與 CI/CD 相關的控制器和工具應該利用 SSA 來執行適當的衝突解決。我也相信其他控制器(例如 Flux 和 ArgoCD)將從欄位級別的同類衝突解決控制中受益。
甚至可以考慮共同努力,為與 CI/CD 相關的工具制定一套標準化的註解來控制衝突解決。
另一方面,與 CI/CD 無關的控制器應確保在修改物件時不會引起不必要的衝突。根據伺服器端應用文件,強烈建議控制器始終執行強制應用。在遵循此建議時,控制器應確保僅將與控制器相關的欄位包含在應用的物件中。否則,不必要的衝突是必然的。
在許多情況下,控制器只應修改它們所管理物件的 status 子資源。在這種情況下,控制器應該只修補 status 子資源,而不觸及實際物件。如果遵循這一點,衝突就不可能發生。
如果你是此類控制器的開發者,並且不確定你的控制器是否遵守上述規定,只需嘗試檢索由你的控制器管理的物件,並檢視 managedFields
(你需要向 kubectl get
傳遞 --show-managed-fields -oyaml
),看看是否有某個欄位被意外聲明瞭所有權。