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

Kubernetes 1.28:改進了 Job 的故障處理

這篇部落格討論了 Kubernetes 1.28 中為改進批處理使用者的 Job 而引入的兩個新特性:Pod 替換策略逐索引的回退限制

這些特性延續了Pod 故障策略所開啟的努力,旨在改進 Job 中 Pod 故障的處理方式。

Pod 替換策略

預設情況下,當一個 Pod 進入終止(terminating)狀態(例如,由於搶佔或驅逐)時,Kubernetes 會立即建立一個替換 Pod。因此,兩個 Pod 會同時執行。在 API 術語中,當 Pod 具有 deletionTimestamp 並且其階段(phase)為 PendingRunning 時,該 Pod 就被視為正在終止。

在給定時間點有兩個 Pod 正在執行的場景對於一些流行的機器學習框架來說是有問題的,例如 TensorFlow 和 JAX,它們要求對於給定的索引,在同一時間最多隻有一個 Pod 在執行。如果某個索引有兩個 Pod 在執行,TensorFlow 會給出以下錯誤。

 /job:worker/task:4: Duplicate task registration with task_name=/job:worker/replica:0/task:4

更多詳情請參見 (issue)。

在前一個 Pod 完全終止之前建立替換 Pod,也可能在資源稀缺或預算緊張的叢集中引起問題,例如:

  • 對於等待排程的 Pod 來說,叢集資源可能很難獲取,因為在現有 Pod 完全終止之前,Kubernetes 可能需要很長時間才能找到可用的節點。
  • 如果啟用了叢集自動擴縮器,替換的 Pod 可能會產生不希望的擴容。

如何使用它?

這是一個 Alpha 特性,你可以透過在叢集中啟用 JobPodReplacementPolicy 特性門控來開啟它。

一旦在你的叢集中啟用了該特性,你就可以透過建立一個新的 Job 來使用它,其中指定了 podReplacementPolicy 欄位,如下所示:

kind: Job
metadata:
  name: new
  ...
spec:
  podReplacementPolicy: Failed
  ...

在該 Job 中,Pod 只有在達到 Failed 階段時才會被替換,而不是在它們正在終止時。

此外,你可以檢查 Job 的 .status.terminating 欄位。該欄位的值是 Job 擁有的當前正在終止的 Pod 的數量。

kubectl get jobs/myjob -o=jsonpath='{.items[*].status.terminating}'
3 # three Pods are terminating and have not yet reached the Failed phase

這對於外部排隊控制器(例如 Kueue)特別有用,它可以跟蹤一個 Job 正在執行的 Pod 的配額,直到從當前正在終止的 Job 中回收資源為止。

請注意,當使用自定義的Pod 故障策略時,podReplacementPolicy: Failed 是預設設定。

逐索引的回退限制

預設情況下,Indexed Job 的 Pod 故障會計入全域性重試限制,由 .spec.backoffLimit 表示。這意味著,如果有一個持續失敗的索引,它會反覆重啟,直到耗盡限制。一旦達到限制,整個 Job 就會被標記為失敗,並且某些索引可能從未啟動過。

這對於希望獨立處理每個索引的 Pod 故障的用例是有問題的。例如,如果你使用 Indexed Job 來執行整合測試,其中每個索引對應一個測試套件。在這種情況下,你可能希望考慮到可能的不穩定測試,允許每個套件重試 1 或 2 次。可能會有一些有問題的套件,導致相應的索引持續失敗。在這種情況下,你可能更傾向於限制有問題的套件的重試次數,同時允許其他套件完成。

該特性允許你:

  • 儘管某些索引失敗,但仍能完成所有索引的執行。
  • 透過避免對持續失敗的索引進行不必要的重試,更好地利用計算資源。

如何使用它?

這是一個 Alpha 特性,你可以透過在叢集中啟用 JobBackoffLimitPerIndex 特性門控來開啟它。

一旦在你的叢集中啟用了該特性,你就可以建立一個指定了 .spec.backoffLimitPerIndex 欄位的 Indexed Job。

示例

以下示例演示瞭如何使用此特性來確保 Job 執行所有索引(前提是沒有其他原因導致 Job 提前終止,例如達到 activeDeadlineSeconds 超時或被使用者手動刪除),並且失敗次數是按每個索引控制的。

apiVersion: batch/v1
kind: Job
metadata:
  name: job-backoff-limit-per-index-execute-all
spec:
  completions: 8
  parallelism: 2
  completionMode: Indexed
  backoffLimitPerIndex: 1
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: example # this example container returns an error, and fails,
                      # when it is run as the second or third index in any Job
                      # (even after a retry)        
        image: python
        command:
        - python3
        - -c
        - |
          import os, sys, time
          id = int(os.environ.get("JOB_COMPLETION_INDEX"))
          if id == 1 or id == 2:
            sys.exit(1)
          time.sleep(1)          

現在,在 Job 完成後檢查 Pod:

kubectl get pods -l job-name=job-backoff-limit-per-index-execute-all

返回類似以下的輸出:

NAME                                              READY   STATUS      RESTARTS   AGE
job-backoff-limit-per-index-execute-all-0-b26vc   0/1     Completed   0          49s
job-backoff-limit-per-index-execute-all-1-6j5gd   0/1     Error       0          49s
job-backoff-limit-per-index-execute-all-1-6wd82   0/1     Error       0          37s
job-backoff-limit-per-index-execute-all-2-c66hg   0/1     Error       0          32s
job-backoff-limit-per-index-execute-all-2-nf982   0/1     Error       0          43s
job-backoff-limit-per-index-execute-all-3-cxmhf   0/1     Completed   0          33s
job-backoff-limit-per-index-execute-all-4-9q6kq   0/1     Completed   0          28s
job-backoff-limit-per-index-execute-all-5-z9hqf   0/1     Completed   0          28s
job-backoff-limit-per-index-execute-all-6-tbkr8   0/1     Completed   0          23s
job-backoff-limit-per-index-execute-all-7-hxjsq   0/1     Completed   0          22s

此外,你可以檢視該 Job 的狀態:

kubectl get jobs job-backoff-limit-per-index-fail-index -o yaml

輸出以類似以下的 status 結尾:

  status:
    completedIndexes: 0,3-7
    failedIndexes: 1,2
    succeeded: 6
    failed: 4
    conditions:
    - message: Job has failed indexes
      reason: FailedIndexes
      status: "True"
      type: Failed

在這裡,索引 12 都各自重試了一次。在它們各自第二次失敗後,指定的 .spec.backoffLimitPerIndex 被超過,因此重試被停止。作為比較,如果停用了逐索引的回退,那麼有問題的索引會一直重試,直到超過全域性的 backoffLimit,然後整個 Job 會被標記為失敗,而一些更高編號的索引可能還未啟動。

如何瞭解更多?

參與其中

這些特性由 SIG Apps 贊助。在批處理工作組中,我們正積極為 Kubernetes 使用者改進批處理用例。工作組是專注於特定目標的相對短期的倡議。WG Batch 的目標是改善批處理工作負載使用者的體驗,為批處理用例提供支援,並針對常見用例增強 Job API。如果你對此感興趣,請透過訂閱我們的郵件列表或在 Slack 上加入我們的工作組。

致謝

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

如果沒有 Aldo Culquicondor (Google) 在整個 Kubernetes 生態系統中提供卓越的領域知識和專業技術,我們無法實現這兩項特性。