使用 Job 在 Kubernetes 執行一次性任務

《Kubernetes Job 與 CronJob》最先發布在 blog.hdls.me/15454668441…html

若是說 Deployment、DaemonSet 等資源爲 Kubernetes 承擔了長時間、在線計算的能力,那麼定時、短時間、甚至一次性的離線計算能力,即是 Job 和 CronJob 所承擔的事情。node

Job

Job 其實就是根據定義起一個或多個 pod 來執行任務,pod 執行完退出後,這個 Job 就完成了。因此 Job 又稱爲 Batch Job ,即計算業務或離線業務。python

Job 使用方法

Job 的 YAML 定義與 Deployment 十分類似。與 Deployment 不一樣的是,Job 不須要定義 spec.selector 來指定須要控制的 pod,看個例子:ubuntu

apiVersion: batch/v1
kind: Job
metadata:
 name: date
spec:
 template:
 spec:
 containers:
 - name: date
 image: ubuntu:16.04
 command: ["sh", "-c", "date > /date/date.txt"]
 volumeMounts:
 - mountPath: /date
 name: date-volume
 restartPolicy: Never
 volumes:
 - name: date-volume
 hostPath:
 path: /date
複製代碼

在這個 Job 中,咱們定義了一個 Ubuntu 鏡像的容器,用於將當前時間輸出至宿主機的 /date/date.txt 文件中。將此 Job 建立好後,咱們能夠查看該 Job 對象:api

能夠看到,Job 在建立後被加上了 controller-uid=***** 的 Label,和與之對應的 Label Selector,從而保證了 Job 與它所管理的 Pod 之間的匹配關係。查看 pod 能夠看到相同的 Label:bash

pod 在執行完畢後,狀態會變成 Completed,咱們能夠去 pod 被調度的 node 上查看咱們掛載進去的 date.txt 文件:併發

[root@rancher-node3 ~]# cat /date/date.txt
Sat Dec 22 16:09:48 UTC 2018
複製代碼

pod 重啓策略

在 Job 中,pod 的重啓策略 restartPolicy 不容許被設置成 Always,只容許被設置爲 Never 或 OnFailure。這是由於 Job 的 pod 執行完畢後直接退出,若是 restartPolicy=Always,pod 將不斷執行計算做業,這可不是咱們指望的。app

Job 能夠設置 pod 的最長運行時間 spec.activeDeadlineSeconds,一旦超過了這個時間,這個 Job 的全部 pod 都會被終止。ui

那麼,若是 pod 的計算做業失敗了,在不一樣的重啓策略下會怎麼辦?url

restartPolicy=Never

若是設置了 restartPolicy=Never,那麼 Job Controller 會不斷的嘗試建立一個新的 pod 出來,默認嘗試 6 次。固然這個值能夠設置,即 Job 對象的 spec.backoffLimit 字段。

須要注意的是,從新建立 Pod 的間隔是呈指數增長的。

restartPolicy=OnFailure

若是設置了 restartPolicy=Never,那麼 Job Controller 會不斷的重啓這個 pod。

Job 工做原理

經過觀察 Job 的建立過程,不難看出 Job 維護了兩個值 DESIRED 和 SUCCESSFUL,分別表示 spec.completions 和 成功退出的 pod 數。

而在 Job 對象中有兩個參數意義重大,它們控制着 Job 的並行任務: spec.parallelism :定義一個 Job 在任意時間最多能夠啓動同時運行的 Pod 數; spec.completions :定義 Job 至少要完成的 Pod 數目,即 Job 的最小完成數。

弄清楚了這兩個參數,咱們再來看 Job 的工做原理。

首先,Job Controller 控制的直接就是 pod; 在整個 Job 的做業過程當中,Job Controller 根據實際在 Running 的 pod 數、已成功退出的 pod 數、parallelism 值、completions 值,計算出當前須要建立或刪除的 pod 數,去調用 APIServer 來執行具體操做。

就拿上面的例子說明,好比將 YAML 改爲:

apiVersion: batch/v1
kind: Job
metadata:
 name: date
spec:
 parallelism: 2
 completions: 3
 template:
 spec:
 containers:
 - name: date
 image: ubuntu:16.04
 command: ["sh", "-c", "date >> /date/date.txt"]
 volumeMounts:
 - mountPath: /date
 name: date-volume
 restartPolicy: Never
 volumes:
 - name: date-volume
 hostPath:
 path: /date
複製代碼

第一步:判斷當前沒有 pod 在 Running,且成功退出 pod 數爲 0,當前最多容許 2 個 pod 並行。向 APIServer 發起建立 2 個 pod 的請求。此時 2 個 pod Running,當這 2 個 pod 完成任務併成功退出後,進入第二步;

第二步:當前 Running pod 數爲 0,成功退出數爲 2,當前最多容許 2 個 pod 並行,Job 最小完成數爲 3。則向 APIServer 發起建立 1 個 pod 的請求。此時 1 個 pod Running,當這個 pod 完成任務併成功退出後,進入第三步;

第三步:當前成功退出 pod 數爲 3,Job 最小完成數爲 3。判斷 Job 完成做業。

批處理調度

根據 Job 的這些特性,咱們就能夠用以實現批處理調度,也就是並行啓動多個計算進程去處理一批工做項。根據並行處理的特性,每每將 Job 分爲三種類型,即 Job 模板拓展、固定 completions 數的 Job、固定 parallelism 數的 Job。

Job 模板拓展

這種模式最簡單粗暴,即將 Job 的 YAML 定義成外界可以使用的模板,再由外部控制器使用這些模板來生成單一無並行任務的 Job。好比,咱們將上面的例子改寫成模板:

apiVersion: batch/v1
kind: Job
metadata:
 name: date-$ITEM
spec:
 template:
 spec:
 containers:
 - name: date
 image: ubuntu:16.04
 command: ["sh", "-c", "echo item number $ITEM; date >> /date/date.txt; sleep 5s"]
 volumeMounts:
 - mountPath: /date
 name: date-volume
 restartPolicy: Never
 volumes:
 - name: date-volume
 hostPath:
 path: /date
複製代碼

而在使用的時候,只需將 $ITEM 替換掉便可:

cat job.yml | sed "s/\$ITEM/1/" > ./job-test.yaml
複製代碼

除了上面這張簡單的基礎模板使用,Kubernetes 官網還提供了一種以 jinja2 模板語言實現的多模板參數的模式:

{%- set params = [{ "name": "apple", "url": "http://www.orangepippin.com/apples", },
                  { "name": "banana", "url": "https://en.wikipedia.org/wiki/Banana", },
                  { "name": "raspberry", "url": "https://www.raspberrypi.org/" }]
%}
{%- for p in params %}
{%- set name = p["name"] %}
{%- set url = p["url"] %}
apiVersion: batch/v1
kind: Job
metadata:
 name: jobexample-{{ name }}
 labels:
 jobgroup: jobexample
spec:
 template:
 metadata:
 name: jobexample
 labels:
 jobgroup: jobexample
 spec:
 containers:
 - name: c
 image: busybox
 command: ["sh", "-c", "echo Processing URL {{ url }} && sleep 5"]
 restartPolicy: Never
---
{%- endfor %}
複製代碼

在使用這種模式須要確保已經安裝了 jinja2 的包:pip install --user jinja2

再執行一條 Python 命令便可替換:

alias render_template='python -c "from jinja2 import Template; import sys; print(Template(sys.stdin.read()).render());"'
cat job.yaml.jinja2 | render_template > jobs.yaml
複製代碼

或者直接進行 kubectl create:

alias render_template='python -c "from jinja2 import Template; import sys; print(Template(sys.stdin.read()).render());"'
cat job.yaml.jinja2 | render_template | kubectl create -f -
複製代碼

固定 completions 數的 Job

這種模式就真正實現了並行工做模式,且 Job 的完成數是固定的。

在這種模式下,須要一個存放 work item 的隊列,好比 RabbitMQ,咱們須要先將要處理的任務變成 work item 放入任務隊列。每一個 pod 建立時,去隊列裏獲取一個 task,完成後將其從隊列裏刪除,直到完成了定義的 completions 數。

上圖描述了一個 completions=6,parallelism=2 的 Job 的示意圖。選擇 RabbitMQ 來充當這裏的工做隊列;外部生產者產生 6 個 task ,放入工做隊列中;在 pod 模板中定義 BROKER_URL,來做爲消費者。一旦建立了這個 Job,就會以併發度爲 2 的方式,去消費這些 task,直到任務所有完成。其 yaml 文件以下:

apiVersion: batch/v1
kind: Job
metadata:
 name: job-wq-1
spec:
 completions: 6
 parallelism: 2
 template:
 metadata:
 name: job-wq-1
 spec:
 containers:
 - name: c
 image: myrepo/job-wq-1
 env:
 - name: BROKER_URL
 value: amqp://guest:guest@rabbitmq-service:5672
 - name: QUEUE
 value: job1
 restartPolicy: OnFailure
複製代碼

固定 parallelism 數的 Job

最後一種模式是指定並行度(parallelism),但不設置固定的 completions 的值。

每一個 pod 去隊列裏拿任務執行,完成後繼續去隊列裏拿任務,直到隊列裏沒有任務,pod 才退出。這種狀況下,只要有一個 pod 成功退出,就意味着整個 Job 結束。這種模式對應的是任務總數不固定的場景。

上圖描述的是一個並行度爲 2 的 Job。RabbitMQ 不能讓客戶端知道是否沒有數據,所以這裏採用 Redis 隊列;每一個 pod 去隊列裏消費一個又一個任務,直到隊列爲空後退出。其對應的 yaml 文件以下:

apiVersion: batch/v1
kind: Job
metadata:
 name: job-wq-2
spec:
 parallelism: 2
 template:
 metadata:
 name: job-wq-2
 spec:
 containers:
 - name: c
 image: myrepo/job-wq-2
 restartPolicy: OnFailure
複製代碼

CronJob

Kubernetes 在 v1.5 開始引入了 CronJob 對象,顧名思義,就是定時任務,相似 Linux Cron。先看個例子:

apiVersion: batch/v1beta1
kind: CronJob
metadata:
 name: cron-date
spec:
 schedule: "*/1 * * * *"
 jobTemplate:
 spec:
 template:
 spec:
 containers:
 - name: date
 image: ubuntu:16.04
 command: ["sh", "-c", "date >> /date/date.txt"]
 volumeMounts:
 - mountPath: /date
 name: date-volume
 nodeSelector:
            kubernetes.io/hostname: rancher-node3
 volumes:
 - name: date-volume
 hostPath:
 path: /date
 restartPolicy: OnFailure
複製代碼

CronJob 其實就是一個 Job 對象的控制器,須要定義一個 Job 的模板,即 jobTemplate 字段;另外,其定時表達式 schedule 基本上照搬了 Linux Cron 的表達式:

# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# │ │ │ │ │ 7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * 
複製代碼

建立出該 CronJob 對象後,CronJob 會記錄下最近一次 Job 的執行時間:

[root@rancher-node1 jobs]# kubectl get cronjob cron-date
NAME        SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cron-date   */1 * * * *   False     0        22s             15m
[root@rancher-node1 jobs]# kubectl get job
NAME                   DESIRED   SUCCESSFUL   AGE
cron-date-1545584220   1         1            2m
cron-date-1545584280   1         1            1m
cron-date-1545584340   1         1            23s
[root@rancher-node1 jobs]# kubectl get po
NAME                         READY   STATUS      RESTARTS   AGE
cron-date-1545584220-gzmzw   0/1     Completed   0          2m
cron-date-1545584280-bq9nx   0/1     Completed   0          1m
cron-date-1545584340-84tf2   0/1     Completed   0          27s
複製代碼

若是某些定時任務比較特殊,某個 Job 尚未執行完,下一個新的 Job 就產生了。這種狀況能夠經過設置 spec.concurrencyPolicy 字段來定義具體策略:

  1. concurrencyPolicy=Allow,這也是默認狀況,這意味着這些 Job 能夠同時存在;
  2. concurrencyPolicy=Forbid,這意味着不會建立新的 Pod,該建立週期被跳過;
  3. concurrencyPolicy=Replace,這意味着新產生的 Job 會替換舊的、沒有執行完的 Job。

Kubernetes 所能容忍的 Job 建立失敗數爲 100,可是其失敗時間窗口能夠自定義。即經過字段 spec.startingDeadlineSeconds 能夠用來設定這個時間窗口,單位爲秒,也就是說在這個時間窗口內最大容忍數爲 100,若是超過了 100,這個 Job 就不會再被執行。

相關文章
相關標籤/搜索