「批次工作」模式適合管理隔離的原子工作單元。它建立在 Job 抽象之上,能在分散式環境中可靠地執行短命 Pod,直到工作完成。
問題#
Kubernetes 中管理與執行容器的主要原語是 Pod,而建立 Pod 的方式有好幾種,各有特性:
- 裸 Pod(Bare Pod):手動建立 Pod 來執行容器。但當所在節點失效時,Pod 不會被重啟。除了開發或測試用途外,不建議這樣執行 Pod。這種機制也被稱為「未受管(unmanaged)」或「裸露(naked)」Pod。
- ReplicaSet:用來建立與管理「預期持續執行」之 Pod 的生命週期(例如網頁伺服器容器)。它在任何時刻維持一組穩定的複本 Pod,保證指定數量的相同 Pod 可用。
- DaemonSet:在每個節點上執行單一 Pod 的控制器。通常用於管理監控、日誌彙整、儲存容器等平台能力(詳見「常駐服務」)。
這些 Pod 的共通點是:它們代表長時間執行的行程,本來就不打算在一段時間後停止。但有些情況下,我們需要可靠地執行一個預先定義好的有限工作單元,然後關閉容器——這就是 Kubernetes Job 資源的用武之地。
解法#
Kubernetes Job 與 ReplicaSet 相似:它建立一個或多個 Pod 並確保它們成功執行。差別在於,一旦預期數量的 Pod 成功終止,Job 就被視為完成,不會再啟動額外的 Pod。
apiVersion: batch/v1
kind: Job
metadata:
name: random-generator
spec:
completions: 5 # 要跑完五個 Pod,且全部必須成功
parallelism: 2 # 可有兩個 Pod 平行執行
template:
metadata:
name: random-generator
spec:
restartPolicy: OnFailure # Job 必須指定 restartPolicy
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
command: ["java", "-cp", "/", "RandomRunner", "/numbers.txt", "10000"]Job 與 ReplicaSet 定義的一個關鍵差異在
.spec.template.spec.restartPolicy。ReplicaSet 的預設值是Always,這對「必須永遠保持運行」的長時間行程很合理;但 Job 不允許Always,只能是OnFailure或Never。
為什麼不用裸 Pod 就好?#
用 Job 帶來許多可靠性與可擴展性上的好處,因此是首選:
- Job 不是短暫的記憶體內任務,而是持久化的,能撐過叢集重啟。
- Job 完成後不會被刪除,而是保留下來供追蹤。Job 建立的 Pod 同樣保留,可供檢查(例如查看容器日誌)。裸 Pod 也有這個特性,但僅限
restartPolicy: OnFailure。 - Job 可能需要執行多次。透過
.spec.completions欄位,可指定 Pod 要成功完成幾次,Job 本身才算完成。 - 當 Job 需要完成多次時,還可以同時啟動多個 Pod 來擴展執行,透過
.spec.parallelism指定。 - 若節點失效、或 Pod 執行中因故被逐出,排程器會把 Pod 放到新的健康節點上重跑。裸 Pod 則會停留在失敗狀態,因為既有 Pod 永遠不會被搬到其他節點。
主導 Job 行為的兩個欄位是:
.spec.completions:指定要跑完幾個 Pod,Job 才算完成。.spec.parallelism:指定可平行執行的 Pod 複本數。
把
parallelism設得很高不保證真的有高平行度:實際 Pod 數可能仍少於期望值(在某些邊角情況下甚至更多),原因包括節流、資源配額、剩餘完成次數不足等。把這個欄位設為0等於暫停 Job。

圖 7-1:completion 為 5、parallelism 為 2 的平行批次工作處理過程
三種 Job 型別#
依這兩個參數的組合,Job 可分為三類:
- 單一 Pod Job:
.spec.completions與.spec.parallelism兩者都省略、或都設為預設值 1。這種 Job 只啟動一個 Pod,該 Pod 成功終止(結束碼 0)時即告完成。 - 固定完成次數 Job:
.spec.completions指定為大於 1 的數字,就必須有這麼多 Pod 成功。.spec.parallelism可選填,或維持預設值 1。當我們事先知道工作項目的數量、且單一工作項目的處理成本足以正當化「配一個專屬 Pod」時,這是最佳選擇。 - 工作佇列 Job:省略
.spec.completions,並把.spec.parallelism設為大於 1 的整數。這種 Job 在「至少一個 Pod 成功終止、且其他所有 Pod 也都終止」時視為完成。這種配置要求 Pod 之間自行協調、決定各自負責什麼,才能協同收工。例如佇列中存放著數量固定但未知的工作項目,平行的 Pod 逐一取出處理;第一個偵測到佇列已空並成功退出的 Pod,就代表 Job 完成,Job 控制器接著等待其餘 Pod 一併終止。由於一個 Pod 會處理多個工作項目,這種型別很適合粒度很細的工作項目——也就是「一個工作項目配一個 Pod」的額外開銷不划算的場合。
若你要處理的是無上限的工作項目串流,那麼 ReplicaSet 這類控制器才是管理這些處理 Pod 的較佳選擇。
討論#
Job 抽象相當基礎,卻也是根本性的原語——CronJob 等其他原語就建立在它之上。Job 幫助把隔離的工作單元轉化為可靠、可擴展的執行單元。
但 Job 並沒有規定你該如何把可獨立處理的工作項目對應到 Job 或 Pod,這需要你權衡兩種做法:
- 一個工作項目一個 Job:代價是建立 Kubernetes Job 的額外開銷,以及平台要管理大量消耗資源的 Job。當每個工作項目都是需要獨立記錄、追蹤或擴縮的複雜任務時,這個選項有用。
- 所有工作項目共用一個 Job:適合數量龐大、且不需要由平台獨立追蹤與管理的工作項目。這種情境下,工作項目必須由應用內部透過批次框架來管理。
Job 原語只提供工作項目排程最基本的功能。任何複雜的實作,都必須把 Job 原語與批次應用框架結合才能達成目標(Java 生態系中,Spring Batch 與 JBeret 是標準實作)。
並非所有服務都得一直執行:有些必須按需執行,有些在特定時間,有些週期性執行。用 Job 可以只在需要時、且只在任務執行期間執行 Pod。Job 會被排程到有足夠容量、滿足 Pod 配置政策與其他容器依賴考量的節點上。
用 Job 處理短命任務,而非使用 ReplicaSet 這類長期執行的抽象,能為平台上的其他工作負載節省資源。這讓 Job 成為一個獨特的原語,也讓 Kubernetes 成為一個支援多樣化工作負載的平台。
更多資訊#
- Batch Job Example
- Run to Completion Finite Workloads
- Parallel Processing Using Expansions
- Coarse Parallel Processing Using a Work Queue
- Fine Parallel Processing Using a Work Queue
- Indexed Job Created with Metacontroller
- Java Batch Processing Frameworks and Libraries