一. 簡介
Deployment、StatefulSet,以及 DaemonSet 這三個(gè)主要編排的對(duì)象,都是“在線業(yè)務(wù)”,都屬于Long Running Task(長作業(yè))。
但是有一類作業(yè)顯然不滿足這樣的條件,這就是“離線業(yè)務(wù)”,叫作 Batch Job(計(jì)算業(yè)務(wù))。針對(duì)這種在計(jì)算完成后就直接退出的業(yè)務(wù),可以使用我們今天的重點(diǎn):Job來解決。
關(guān)于本文的項(xiàng)目的代碼,都放于鏈接:GitHub資源
二. Job
2.1 案例
如下,我們創(chuàng)建一個(gè)計(jì)算pi后面100位的案例,需要執(zhí)行10個(gè)這樣的任務(wù)。
demo-job.yaml 文件如下:
apiVersion: batch/v1
kind: Job
metadata:
name: demo-job
spec:
parallelism: 3
completions: 10
template:
spec:
containers:
- name: pi
image: resouer/ubuntu-bc
command: ["sh", "-c", "echo 'scale=100; 4*a(1)' | bc -l "]
restartPolicy: Never
backoffLimit: 4
activeDeadlineSeconds: 300
2.2 檢查Selector
運(yùn)行完畢后,我們將看到如下的狀態(tài):
kubectl describe jobs/demo-job
# result
Name: demo-job
Namespace: default
Selector: controller-uid=bde35f54-a1f8-4f1b-845f-d42e9c570f23
Labels: controller-uid=bde35f54-a1f8-4f1b-845f-d42e9c570f23
job-name=demo-job
Annotations: <none>
Parallelism: 3
Completions: 10
Start Time: Sat, 27 Mar 2021 23:58:51 +0000
Completed At: Sat, 27 Mar 2021 23:59:11 +0000
Duration: 20s
Active Deadline Seconds: 300s
Pods Statuses: 0 Running / 10 Succeeded / 0 Failed

可以看到,這個(gè) Job 對(duì)象在創(chuàng)建后,它的 Pod 模板,被自動(dòng)加上了一個(gè) controller-uid=< 一個(gè)隨機(jī)字符串 > 這樣的 Label。而這個(gè) Job 對(duì)象本身,則被自動(dòng)加上了這個(gè) Label 對(duì)應(yīng)的 Selector,從而 保證了 Job 與它所管理的 Pod 之間的匹配關(guān)系。之所以要使用這種攜帶了 UID 的 Label,就是為了避免不同 Job 對(duì)象所管理的 Pod 發(fā)生重合。
2.3 參數(shù)
關(guān)于上面的YAML參數(shù),詳細(xì)分析如下:
- spec.backoffLimit
指定在標(biāo)記此作業(yè)失敗之前重試的次數(shù),默認(rèn)為6。并且,Job Controller 重新創(chuàng)建 Pod 的間隔是呈指數(shù)增加的,即下一次重新創(chuàng)建 Pod 的動(dòng)作會(huì)分別發(fā)生在 10 s、20 s、40 s …后。 - spec.activeDeadlineSeconds
指定相對(duì)于startTime的持續(xù)時(shí)間(以秒為單位),在系統(tǒng)嘗試終止該作業(yè)之前,該作業(yè)可能處于活動(dòng)狀態(tài);值必須為正整數(shù)。例如,一旦運(yùn)行超過了 300 s,這個(gè) Job 的所有 Pod 都會(huì)被終止。并且,可以在 Pod 的狀態(tài)里看到終止的原因是reason: DeadlineExceeded。 - restartPolicy
restartPolicy 在 Job 對(duì)象里只允許被設(shè)置為 Never 和 OnFailure,關(guān)于restartPolicy有如下倆種選項(xiàng):- Never
配置為當(dāng)前參數(shù)后,那么離線作業(yè)失敗后Job Controller就會(huì)不斷地嘗試創(chuàng)建一個(gè)新 Pod。
當(dāng)然,這個(gè)嘗試肯定不能無限進(jìn)行下去,這個(gè)與spec.backoffLimit參數(shù)是配合使用的。 - OnFailure
配置為當(dāng)前參數(shù)后,那么離線作業(yè)失敗后,Job Controller就不會(huì)去創(chuàng)建新的 Pod,而是會(huì)不斷地嘗試重啟 Pod 里的容器。
- Never
三. Job Controller
3.1 原理
Job Controller 控制的對(duì)象就是 Pod。
其次,Job Controller 在控制循環(huán)中進(jìn)行的調(diào)諧(Reconcile)操作,是根據(jù)實(shí)際在 Running 狀態(tài) Pod 的數(shù)目、已經(jīng)成功退出的 Pod 的數(shù)目,以及 parallelism、completions 參數(shù)的值共同計(jì)算出在這個(gè)周期里,應(yīng)該創(chuàng)建或者刪除的 Pod 數(shù)目,然后調(diào)用 Kubernetes API 來執(zhí)行這個(gè)操作。
如果在這次調(diào)諧周期里,Job Controller 發(fā)現(xiàn)實(shí)際在 Running 狀態(tài)的 Pod 數(shù)目,比 parallelism 還大,那么它就會(huì)刪除一些 Pod,使兩者相等。
Job Controller 實(shí)際上控制了作業(yè)執(zhí)行的并行度,以及總共需要完成的任務(wù)數(shù)這兩個(gè)重要參數(shù)。而在實(shí)際使用時(shí),我們需要根據(jù)作業(yè)的特性,來決定并行度(parallelism)和任務(wù)數(shù)(completions)的合理取值。
3.2 參數(shù)
在 Job 對(duì)象中,負(fù)責(zé)并行控制的參數(shù)有兩個(gè):
- spec.parallelism
指定作業(yè)在任何給定時(shí)間應(yīng)運(yùn)行的Pod的最大期望數(shù)目。
當(dāng)((.spec.completions-.status.successful)<.spec.parallelism),即當(dāng)要做的工作小于最大并行度時(shí),穩(wěn)定狀態(tài)下運(yùn)行的Pod的實(shí)際數(shù)量將小于此數(shù)量。 - spec.completion
指定與作業(yè)一起運(yùn)行所需的成功完成的Pod數(shù)量。
設(shè)置為nil表示任何Pod的成功都表示所有Pod的成功,并允許并行性具有任何正值。設(shè)置為1意味著并行度被限制為1,并且pod的成功標(biāo)志著工作的成功。
3.3 場景
3.3.1 parallelism和completions都確定
在這種模式下使用 Job 對(duì)象,completions 和 parallelism 這兩個(gè)字段都應(yīng)該使用默認(rèn)值 1,而不應(yīng)該由我們自行設(shè)置。
一般實(shí)際落地方案就是:外部管理器 +Job 模板。作業(yè) Pod 的并行控制,應(yīng)該完全交由外部工具來進(jìn)行管理(比如,KubeFlow)。
3.3.2 parallelism不確定,completions確定
當(dāng)只關(guān)心最后是否有指定數(shù)目(spec.completions)個(gè)任務(wù)成功退出,而不關(guān)系任務(wù)并行度時(shí)。
一般采用:外部MQ+Job。利用確定的Pod數(shù),主動(dòng)去消費(fèi)MQ里面的消息達(dá)到目標(biāo)數(shù)量即可。
例如,每個(gè) Pod 只需要將任務(wù)信息讀取出來,處理完成,然后退出即可。而作為用戶,只關(guān)心最終一共有 10 個(gè)計(jì)算任務(wù)啟動(dòng)并且退出,只要這個(gè)目標(biāo)達(dá)到,就認(rèn)為整個(gè) Job 處理完成了。
3.3.3 parallelism確定,completions不確定
由于任務(wù)數(shù)目的總數(shù)不固定,所以每一個(gè) Pod 必須能夠知道,什么時(shí)候可以退出。比如,借助上面例子,簡單地以“隊(duì)列為空”,作為任務(wù)全部完成的標(biāo)志。
在這種情況下,難點(diǎn)在于任務(wù)的總數(shù)是未知的,所以不僅需要一個(gè)工作隊(duì)列來負(fù)責(zé)任務(wù)分發(fā),還需要能夠判斷工作隊(duì)列已經(jīng)為空。
四. Cron Job
4.1 原理
此處先看demo-cronjob.yaml文件:
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: demo-cronjob
spec:
schedule: "*/5 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello World
restartPolicy: OnFailure
concurrencyPolicy: Replace
startingDeadlineSeconds: 500
在這個(gè) YAML 文件中,最重要的關(guān)鍵詞就是 jobTemplate,CronJob 是一個(gè) Job 對(duì)象的控制器。CronJob 與 Job 的關(guān)系,正如同 Deployment 與 ReplicaSet 的關(guān)系一樣。
4.2 Unix Cron
CronJob 是一個(gè)專門用來管理 Job 對(duì)象的控制器。只不過,它創(chuàng)建和刪除 Job 的依據(jù),是 schedule 字段定義的、一個(gè)標(biāo)準(zhǔn)的Unix Cron格式的表達(dá)式。
Cron 表達(dá)式中的五個(gè)部分分別代表:分鐘、小時(shí)、日、月、星期。
*/5 * * * * 這個(gè) Cron 表達(dá)式里 */5 中的 * 表示從 0 開始,/ 表示“每”,1 表示偏移量。含義就是:從 0 開始,每 5 個(gè)時(shí)間單位執(zhí)行一次。
關(guān)于上面的任務(wù),執(zhí)行如下倆條指令可以查看結(jié)果(等待5min):
kubectl get jobs
kubectl get cronjob demo-cronjob
結(jié)果如下:

4.3 并發(fā)策略
由于定時(shí)任務(wù)的特殊性,很可能某個(gè) Job 還沒有執(zhí)行完,另外一個(gè)新 Job 就產(chǎn)生了。這時(shí)候,你可以通過 spec.concurrencyPolicy字段來定義具體的處理策略。
concurrencyPolicy 有如下三種配置參數(shù):
- Allow
允許CronJobs并發(fā)運(yùn)行,默認(rèn)值。 - Forbid
禁止同時(shí)運(yùn)行,如果前一個(gè)運(yùn)行尚未完成,則跳過下一個(gè)運(yùn)行。 - Replace
取消當(dāng)前正在運(yùn)行的作業(yè),并將其替換為新作業(yè)。
如果由于任何原因錯(cuò)過了計(jì)劃的時(shí)間,則以秒為單位的可選截止期限,用于開始工作。當(dāng)在指定的時(shí)間窗口內(nèi),miss 的數(shù)目達(dá)到 100 時(shí),那么 CronJob 會(huì)停止再創(chuàng)建這個(gè) Job。這個(gè)時(shí)間窗口,可以由 spec.startingDeadlineSeconds 字段指定。
在上面例子中, startingDeadlineSeconds=500,意味著在過去 500 s 里,如果 miss 的數(shù)目達(dá)到了 100 次,那么這個(gè) Job 就不會(huì)被創(chuàng)建執(zhí)行了。
四. 總結(jié)
關(guān)于Job,Cron Job 和 Job Controller 這三者,都是采用了控制器模式設(shè)計(jì),利用對(duì)象管理對(duì)象,是一種樹狀的層級(jí)管理。這點(diǎn)與Deployment,ReplicaSet和Deployment Controller之間的設(shè)計(jì)理念一致。
用一個(gè)對(duì)象控制另一個(gè)對(duì)象,是 Kubernetes 編排的精髓所在!
歡迎收藏個(gè)人博客: Wyatt's Blog ,非常感謝~
Reference
https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy