k8s 深入篇———— Job與CronJob[十]

開篇

簡要演練一下job 和 cronjob

正文

實際上,它們主要編排的對象,都是“在線業務”,即:Long Running Task(長作業)。比如,我在前面舉例時常用的 Nginx、Tomcat,以及 MySQL 等等。這些應用一旦運行起來,除非出錯或者停止,它的容器進程會一直保持在 Running 狀態。

但是,有一類作業顯然不滿足這樣的條件,這就是“離線業務”,或者叫作 Batch Job(計算業務)。這種業務在計算完成後就直接退出了,而此時如果你依然用 Deployment 來管理這種業務的話,就會發現 Pod 會在計算結束後退出。

然後被 Deployment Controller 不斷地重啓;而像“滾動更新”這樣的編排功能,更無從談起了。

所以,早在 Borg 項目中,Google 就已經對作業進行了分類處理,提出了 LRS(Long Running Service)和 Batch Jobs 兩種作業形態,對它們進行“分別管理”和“混合調度”。

不過,在 2015 年 Borg 論文剛剛發佈的時候,Kubernetes 項目並不支持對 Batch Job 的管理。直到 v1.4 版本之後,社區才逐步設計出了一個用來描述離線業務的 API 對象,它的名字就是:Job。

練習一下:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: resouer/ubuntu-bc 
        command: ["sh", "-c", "echo 'scale=10000; 4*a(1)' | bc -l "]
      restartPolicy: Never
  backoffLimit: 4

查看一下job:

kubectl describe jobs/pi

可以看到,這個 Job 對象在創建後,它的 Pod 模板,被自動加上了一個 controller-uid=< 一個隨機字符串 > 這樣的 Label。而這個 Job 對象本身,則被自動加上了這個 Label 對應的 Selector,從而 保證了 Job 與它所管理的 Pod 之間的匹配關係。

而 Job Controller 之所以要使用這種攜帶了 UID 的 Label,就是爲了避免不同 Job 對象所管理的 Pod 發生重合。需要注意的是,這種自動生成的 Label 對用戶來說並不友好,所以不太適合推廣到 Deployment 等長作業編排對象上。

接下來,我們可以看到這個 Job 創建的 Pod 進入了 Running 狀態,這意味着它正在計算 Pi 的值。


這也是我們需要在 Pod 模板中定義 restartPolicy=Never 的原因:離線計算的 Pod 永遠都不應該被重啓,否則它們會再重新計算一遍。

事實上,restartPolicy 在 Job 對象裏只允許被設置爲 Never 和 OnFailure;而在 Deployment 對象裏,restartPolicy 則只允許被設置爲 Always。

此時,我們通過 kubectl logs 查看一下這個 Pod 的日誌,就可以看到計算得到的 Pi 值已經被打印了出來:

kubectl logs pi-v7gbf

這時候,你一定會想到這樣一個問題,如果這個離線作業失敗了要怎麼辦?

比如,我們在這個例子中定義了 restartPolicy=Never,那麼離線作業失敗後 Job Controller 就會不斷地嘗試創建一個新 Pod,如下所示:

可以看到,這時候會不斷地有新 Pod 被創建出來。

當然,這個嘗試肯定不能無限進行下去。所以,我們就在 Job 對象的 spec.backoffLimit 字段裏定義了重試次數爲 4(即,backoffLimit=4),而這個字段的默認值是 6。

需要注意的是,Job Controller 重新創建 Pod 的間隔是呈指數增加的,即下一次重新創建 Pod 的動作會分別發生在 10 s、20 s、40 s …後。

而如果你定義的 restartPolicy=OnFailure,那麼離線作業失敗後,Job Controller 就不會去嘗試創建新的 Pod。但是,它會不斷地嘗試重啓 Pod 裏的容器。

如前所述,當一個 Job 的 Pod 運行結束後,它會進入 Completed 狀態。但是,如果這個 Pod 因爲某種原因一直不肯結束呢?

在 Job 的 API 對象裏,有一個 spec.activeDeadlineSeconds 字段可以設置最長運行時間,比如:

spec:
 backoffLimit: 5
 activeDeadlineSeconds: 100

一旦運行超過了 100 s,這個 Job 的所有 Pod 都會被終止。並且,你可以在 Pod 的狀態裏看到終止的原因是 reason: DeadlineExceeded。

以上,就是一個 Job API 對象最主要的概念和用法了。不過,離線業務之所以被稱爲 Batch Job,當然是因爲它們可以以“Batch”,也就是並行的方式去運行。

接下來,我就來爲你講解一下Job Controller 對並行作業的控制方法。

在 Job 對象中,負責並行控制的參數有兩個:

  1. spec.parallelism,它定義的是一個 Job 在任意時間最多可以啓動多少個 Pod 同時運行;

  2. spec.completions,它定義的是 Job 至少要完成的 Pod 數目,即 Job 的最小完成數。

這兩個參數聽起來有點兒抽象,所以我準備了一個例子來幫助你理解。

現在,我在之前計算 Pi 值的 Job 裏,添加這兩個參數:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi1
spec:
  parallelism: 2
  completions: 4
  template:
    spec:
      containers:
      - name: pi
        image: resouer/ubuntu-bc
        command: ["sh", "-c", "echo 'scale=5000; 4*a(1)' | bc -l "]
      restartPolicy: Never
  backoffLimit: 4

這樣,我們就指定了這個 Job 最大的並行數是 2,而最小的完成數是 4。

接下來,我們來創建這個 Job 對象:

其中,DESIRED 的值,正是 completions 定義的最小完成數。

然後,我們可以看到,這個 Job 首先創建了兩個並行運行的 Pod 來計算 Pi:

$ kubectl get pods
NAME       READY     STATUS    RESTARTS   AGE
pi1-5mt88   1/1       Running   0          6s
pi1-gmcq5   1/1       Running   0          6s

而在 40 s 後,這兩個 Pod 相繼完成計算。

這時我們可以看到,每當有一個 Pod 完成計算進入 Completed 狀態時,就會有一個新的 Pod 被自動創建出來,並且快速地從 Pending 狀態進入到 ContainerCreating 狀態:

最終,後面創建的這兩個 Pod 也完成了計算,進入了 Completed 狀態。

這時,由於所有的 Pod 均已經成功退出,這個 Job 也就執行完了,所以你會看到它的 SUCCESSFUL 字段的值變成了 4:

通過上述 Job 的 DESIRED 和 SUCCESSFUL 字段的關係,我們就可以很容易地理解Job Controller 的工作原理了。

首先,Job Controller 控制的對象,直接就是 Pod。

其次,Job Controller 在控制循環中進行的調諧(Reconcile)操作,是根據實際在 Running 狀態 Pod 的數目、已經成功退出的 Pod 的數目,

以及 parallelism、completions 參數的值共同計算出在這個週期裏,應該創建或者刪除的 Pod 數目,然後調用 Kubernetes API 來執行這個操作。

以創建 Pod 爲例。在上面計算 Pi 值的這個例子中,當 Job 一開始創建出來時,實際處於 Running 狀態的 Pod 數目 =0,已經成功退出的 Pod 數目 =0,而用戶定義的 completions,也就是最終用戶需要的 Pod 數目 =4。

所以,在這個時刻,需要創建的 Pod 數目 = 最終需要的 Pod 數目 - 實際在 Running 狀態 Pod 數目 - 已經成功退出的 Pod 數目 = 4 - 0 - 0= 4。也就是說,Job Controller 需要創建 4 個 Pod 來糾正這個不一致狀態。

可是,我們又定義了這個 Job 的 parallelism=2。也就是說,我們規定了每次併發創建的 Pod 個數不能超過 2 個。所以,Job Controller 會對前面的計算結果做一個修正,修正後的期望創建的 Pod 數目應該是:2 個。

這時候,Job Controller 就會併發地向 kube-apiserver 發起兩個創建 Pod 的請求。

類似地,如果在這次調諧週期裏,Job Controller 發現實際在 Running 狀態的 Pod 數目,比 parallelism 還大,那麼它就會刪除一些 Pod,使兩者相等。

綜上所述,Job Controller 實際上控制了,作業執行的並行度,以及總共需要完成的任務數這兩個重要參數。而在實際使用時,你需要根據作業的特性,來決定並行度(parallelism)和任務數(completions)的合理取值。

下一節pv 和 pvc, 關於持久化存儲。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章