本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

Kubernetes 1.26:支援大規模並行批處理工作負載的 Job 跟蹤功能已正式可用

Kubernetes 1.26 版本包含了一個穩定的 Job 控制器實現,能夠可靠地跟蹤大量具有高並行度的 Job。自 Kubernetes 1.22 以來,SIG AppsWG Batch 一直在致力於這一基礎性改進。經過多次迭代和規模驗證,這現在已成為 Job 控制器的預設實現。

與 Indexed 完成模式配合使用,Job 控制器可以處理大規模並行的批處理 Job,支援多達 10 萬個併發 Pod。

新的實現還使得Pod 失敗策略的開發成為可能,該功能在 1.26 版本中處於 Beta 階段。

我如何使用這個功能?

要使用帶 finalizer 的 Job 跟蹤功能,請升級到 Kubernetes 1.25 或更高版本並建立新的 Job。如果您能夠啟用 JobTrackingWithFinalizers 特性門控,也可以在 v1.23 和 v1.24 中使用此功能。

如果您的叢集執行的是 Kubernetes 1.26,帶 finalizer 的 Job 跟蹤是一個穩定功能。對於 v1.25,它受該特性門控控制,您的叢集管理員可能已明確停用它——例如,如果您的策略是不使用 Beta 功能。

升級前建立的 Job 仍將使用舊的行為進行跟蹤。這是為了避免對正在執行的 Pod 追溯性地新增 finalizer,這可能會引入競爭條件。

對於大型 Job,為了獲得最佳效能,Kubernetes 專案建議使用Indexed 完成模式。在這種模式下,控制平面能夠以更少的 API 呼叫來跟蹤 Job 進度。

如果您是批處理、HPCAIML 或相關工作負載的 Operator 開發人員,我們鼓勵您使用 Job API 將精確的進度跟蹤委託給 Kubernetes。如果 Job API 中缺少某些功能,導致您不得不管理普通的 Pod,Working Group Batch 歡迎您的反饋和貢獻。

棄用通知

在該功能開發期間,控制平面為啟用該功能時建立的 Job 添加了註解 batch.kubernetes.io/job-tracking。這為舊的 Job 提供了一個安全的過渡,但它從未打算永久保留。

在 1.26 版本中,我們棄用了註解 batch.kubernetes.io/job-tracking,控制平面將在 Kubernetes 1.27 中停止新增它。與此同時,我們將移除舊的 Job 跟蹤實現。因此,Job 控制器將使用 finalizer 跟蹤所有 Job,並忽略沒有上述 finalizer 的 Pod。

在將叢集升級到 1.27 之前,我們建議您驗證沒有正在執行的、不帶該註解的 Job,或者等待這些 Job 完成。否則,您可能會觀察到控制平面重新建立一些 Pod。我們預計這不會影響任何使用者,因為該功能自 Kubernetes 1.25 起已預設啟用,為舊 Job 的完成提供了足夠的緩衝時間。

新實現解決了什麼問題?

通常,Kubernetes 的工作負載控制器,如 ReplicaSet 或 StatefulSet,依賴於 API 中 Pod 或其他物件的存在來確定工作負載的狀態以及是否需要替換。例如,如果屬於 ReplicaSet 的 Pod 終止或不再存在,ReplicaSet 控制器需要建立一個替換 Pod 來滿足期望的副本數(.spec.replicas)。

自誕生以來,Job 控制器也依賴於 API 中 Pod 的存在來跟蹤 Job 狀態。Job 具有完成失敗處理策略,需要已完成 Pod 的最終狀態來決定是建立替換 Pod 還是將 Job 標記為已完成或失敗。因此,Job 控制器依賴於 Pod(即使是已終止的 Pod)保留在 API 中,以便跟蹤狀態。

這種依賴性使得 Job 狀態的跟蹤不可靠,因為 Pod 可能因多種原因從 API 中被刪除,包括:

  • 當節點宕機時,垃圾收集器移除孤立的 Pod。
  • 當已終止的 Pod 數量達到閾值時,垃圾收集器將其移除。
  • Kubernetes 排程器為了容納更高優先順序的 Pod 而搶佔一個 Pod。
  • 汙點管理器驅逐一個不容忍 NoExecute 汙點的 Pod。
  • 外部控制器(不屬於 Kubernetes 的一部分)或人為刪除 Pod。

新的實現

當控制器需要在物件被移除前對其執行操作時,它應該為其管理的物件新增一個 finalizer。finalizer 會阻止物件從 API 中被刪除,直到 finalizer 被移除。一旦控制器完成了對被刪除物件的清理和統計,它就可以從物件中移除 finalizer,然後控制平面會從 API 中移除該物件。

這就是新的 Job 控制器所做的事情:在 Pod 建立期間新增 finalizer,並在 Pod 終止並已在 Job 狀態中被統計後移除 finalizer。然而,這並非那麼簡單。

主要挑戰在於至少涉及兩個物件:Pod 和 Job。finalizer 存在於 Pod 物件中,而統計資訊則存在於 Job 物件中。沒有機制可以原子地移除 Pod 中的 finalizer 並更新 Job 狀態中的計數器。此外,在任何給定時間可能有一個以上的已終止 Pod。

為了解決這個問題,我們實現了一個三階段的方法,每個階段都對應一次 API 呼叫。

  1. 對於每個已終止的 Pod,將其唯一 ID(UID)新增到所屬 Job 的 .status 中儲存的短期列表中(.status.uncountedTerminatedPods)。
  2. 從 Pod 中移除 finalizer。
  3. 原子地執行以下操作:
    • 從短期列表中移除 UID
    • 在 Job 的 status 中增加總體的 succeededfailed 計數器。

額外的複雜性來自於 Job 控制器可能會亂序接收到第 1 步和第 2 步中 API 更改的結果。我們透過新增一個記憶體中的快取來儲存已移除的 finalizer,解決了這個問題。

儘管如此,我們在 Beta 階段仍然遇到了一些問題,導致在某些情況下一些 Pod 的 finalizer 卡住無法移除(#108645#109485#111646)。因此,我們決定在 1.23 和 1.24 版本中將該特性門控預設設定為停用。

問題解決後,我們在 1.25 版本中重新啟用了該功能。從那時起,我們收到了客戶的報告,他們透過 Job API 在其叢集中同時執行數萬個 Pod。看到這一成功,我們決定在 1.26 版本中將該功能升級為穩定版,這是我們長期承諾的一部分,即讓 Job API 成為在 Kubernetes 叢集中執行大型批處理 Job 的最佳方式。

要了解有關該功能的更多資訊,您可以閱讀 KEP

致謝

與任何 Kubernetes 特性一樣,許多人都為完成這項工作做出了貢獻,從測試、提交錯誤到程式碼審查。

我謹代表 SIG Apps,特別感謝 Jordan Liggitt (Google) 幫助我除錯併為不止一個競爭條件構思解決方案,以及 Maciej Szulik (Red Hat) 的詳盡審查。