【Kubernetes】離線業務:Job與CronJob


  Deployment、StatefulSet和DaemonSet這三個編排概念編排的對象主要都是在線業務(Long Running Task,這些應用一旦運行起來,除非出錯或者停止,它的容器進程會一直保持在Running狀態)。

  但是對於離線業務(Batch Job,計算業務)在計算完成后就直接退出了,如果依然使用Deployment來管理,就會發現Pod會在計算結束后退出,然后被Deployment Controller不斷重啟。

  在Kubernetes v1.4版本之后,設計出來一個用來描述離線業務的API對象:Job。

舉個Job的栗子

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: resouer/ubuntu-bc   #安裝了ba明了的Ubuntu鏡像
        command: ["sh", "-c", "echo 'scale=10000; 4*a(1)' | bc -l "] #bc命令是Linux里的計算器,; -l表示使用標准數學課; a(1)調用arctangent函數,scale指定小數點后的位數
      restartPolicy: Never
  backoffLimit: 4

  創建這個Pod

$ kubectl create -f job.yaml
$ kubectl describe jobs/pi
Name:             pi
Namespace:        default
Selector:         controller-uid=c2db599a-2c9d-11e6-b324-0209dc45a495
Labels:           controller-uid=c2db599a-2c9d-11e6-b324-0209dc45a495
                  job-name=pi
Annotations:      <none>
Parallelism:      1
Completions:      1
..
Pods Statuses:    0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:       controller-uid=c2db599a-2c9d-11e6-b324-0209dc45a495
                job-name=pi
  Containers:
   ...
  Volumes:              <none>
Events:
  FirstSeen    LastSeen    Count    From            SubobjectPath    Type        Reason            Message
  ---------    --------    -----    ----            -------------    --------    ------            -------
  1m           1m          1        {job-controller }                Normal      SuccessfulCreate  Created pod: pi-rq5rl

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

   Job Controller之所以要使用這種攜帶了UID的Label,就是為了避免不同Job對象管理的Pod重合

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

$ kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
pi-rq5rl                            1/1       Running   0          10s

###計算結束后,Pod會進入Completed狀態,這也是需要在Pod模板中定義restartPolicy=Never的原因:離線計算的Pod永遠不應該被重啟

$ kubectl get pods
NAME                                READY     STATUS      RESTARTS   AGE
pi-rq5rl                            0/1       Completed   0          4m

### 通過查看log可以看到計算的值

  $ kubectl logs pi-rq5rl
  3.141592653589793238462643383279...

  那如果這個離線作業失敗了怎么辦?因為這個例子中定義了restartPolicy=Never,那離線作業失敗后Job Controller就會不斷地嘗試創建一個新Pod

  Job對象的spec.backoffLimit字段限制嘗試的次數,在例子中定義為4,默認值為6。Job Controller重新創建Pod的間隔是呈倍數增加的,即下一次重新創建Pod的動作會分別發生在10s,20s,40s……

  那如果定義的restartPolicy=OnFailure呢?離線作業失敗后,Job Controller就不會去嘗試創建新的Pod,但是它會不斷嘗試重啟Pod里的容器。

  那如果這個Pod一直不肯結束呢?spec.activeDeadlineSeconds字段可以設置最長運行時間

spec:
 backoffLimit: 5
 activeDeadlineSeconds: 100   # 運行超過100s,這個Job的所有Pod都會被終止

  

 Job Controller 對並行作業的控制方法

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

spec.parallelism:定義一個Job在任意時間最多可以啟動多少個Pod同時運行

spec.completions:定義Job至少要玩的Pod的數目

  再舉個栗子

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
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

  指定了最大並行數是2,最小完成數是4

### 創建Job對象
$ kubectl create -f job.yaml


### Job維護兩個字段,DESIRED即最小完成數
$ kubectl get job
NAME      DESIRED   SUCCESSFUL   AGE
pi        4         0            3s

### Job首先創建兩個並行運行的Pod計算PI
$ kubectl get pods
NAME       READY     STATUS    RESTARTS   AGE
pi-5mt88   1/1       Running   0          6s
pi-gmcq5   1/1       Running   0          6s

### 當一個Pod完成計算會進入Completed狀態,就會有一個新的Pod被創建出來,並且快速地從Pending狀態進入ContainerCreating狀態,再到Running狀態,最后完成
$ kubectl get pods
NAME       READY     STATUS    RESTARTS   AGE
pi-gmcq5   0/1       Completed   0         40s
pi-84ww8   0/1       Pending   0         0s
pi-5mt88   0/1       Completed   0         41s
pi-62rbt   0/1       Pending   0         0s

$ kubectl get pods
NAME       READY     STATUS    RESTARTS   AGE
pi-gmcq5   0/1       Completed   0         40s
pi-84ww8   0/1       ContainerCreating   0         0s
pi-5mt88   0/1       Completed   0         41s
pi-62rbt   0/1       ContainerCreating   0         0s

$ kubectl get pods 
NAME       READY     STATUS      RESTARTS   AGE
pi-5mt88   0/1       Completed   0          54s
pi-62rbt   1/1       Running     0          13s
pi-84ww8   1/1       Running     0          14s
pi-gmcq5   0/1       Completed   0          54s

$ kubectl get pods 
NAME       READY     STATUS      RESTARTS   AGE
pi-5mt88   0/1       Completed   0          5m
pi-62rbt   0/1       Completed   0          4m
pi-84ww8   0/1       Completed   0          4m
pi-gmcq5   0/1       Completed   0          5m

### 所有Pod均已成功退出,SUCCESSFUL字段值變成4
$ kubectl get job
NAME      DESIRED   SUCCESSFUL   AGE
pi        4         4            5m

  通過上述DESIRED和SUCCESSFUL字段的關系,可以看出Job Controller控制的直接對象是Pod,Job Controller在控制循環中進行的調諧(Reconcile)操作,是根據實際在Running狀態Pod數目、已經成功推出的Pod數目,以及parallelism、completions參數的值共同計算在這個周期里,應該創建或刪除的Pod數目,然后調用Kubernetes API來執行這個操作 

 

Job對象使用方法

1、外部管理器 + Job模板

  把Job的YAML文件定義為一個模板,然后用一個外部工具控制這些模板來生成Job 。Job的定義方式如下所示:

apiVersion: batch/v1
kind: Job
metadata:
  name: process-item-$ITEM
  labels:
    jobgroup: jobexample
spec:
  template:
    metadata:
      name: jobexample
      labels:
        jobgroup: jobexample
    spec:
      containers:
      - name: c
        image: busybox
        command: ["sh", "-c", "echo Processing item $ITEM && sleep 5"]  ### 定義了$ITEM變量
      restartPolicy: Never

  在控制這種Job時,只要注意如下兩個方面即可

    • 創建Job時,替換掉$ITEM這樣的變量
    • 所有來自同一個目標的Job,都有一個jobgroup: jobexample標簽,也就是說這一組Job使用這樣一個相同的標識
### 第一點可以通過shell把¥ITEM替換掉
$ mkdir ./jobs
$ for i in apple banana cherry
do
  cat job-tmpl.yaml | sed "s/\$ITEM/$i/" > ./jobs/job-$i.yaml
done


### 創建Job
$ kubectl create -f ./jobs
$ kubectl get pods -l jobgroup=jobexample
NAME                        READY     STATUS      RESTARTS   AGE
process-item-apple-kixwv    0/1       Completed   0          4m
process-item-banana-wrsf7   0/1       Completed   0          4m
process-item-cherry-dnfu9   0/1       Completed   0          4m

  這種模式雖然看起很傻,但卻很普遍,因為大多數用戶在需要管理Batch Job時,都已經有自家的一套方案,需要做的往往就是集成工作。這時候Kubernetes對這些方案最有價值的就是Job這個API對象,因此只需要編寫一個外部工具(如上面for循環)來管理這些Job即可。

  這種模式下使用Job對象,completions和parallelism這兩個字段都應該使用默認值1,作業Pod的並行控制,應該完全交由外部工具來進行管理

 

2、擁有固定任務數目的並行Job

   只關心最后是否有指定數目(spec.completions)個任務成功推出,不關心執行時的並行度多少。

  比如上面計算pi的例子,或者可以不指定parallelism

 

 3、指定並行度(parallelism),不設置固定的completions的值

   任務總數未知,需要決定什么時候啟動Pod,什么時候Job才算執行完成。因此需要一個工作隊列來負責任務分發,還需要能夠判斷工作隊列已經為空(所有工作已經結束)

apiVersion: batch/v1
kind: Job
metadata:
  name: job-wq-2
spec:
  parallelism: 2
  template:
    metadata:
      name: job-wq-2
    spec:
      containers:
      - name: c
        image: gcr.io/myproject/job-wq-2
        env:
        - name: BROKER_URL
          value: amqp://guest:guest@rabbitmq-service:5672  ##工作隊列采用RabbitMQ
        - name: QUEUE
          value: job2
      restartPolicy: OnFailure

 

  這個Pod執行邏輯為

/* job-wq-2 的偽代碼 */
for !queue.IsEmpty($BROKER_URL, $QUEUE) {
  task := queue.Pop()
  process(task)
}
print("Queue empty, exiting")
exit

 

 

CronJob

  CronJob描述的是定時任務,舉個栗子

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            args:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

  在這個YAML文件中,最重要的關鍵詞就是jobTemplate,即一個Job對象的控制器(Controller)。如Deployment和Pod的關系一樣,CronJob是一個專門用來管理Job對象的控制器。

  CronJob創建和刪除Job的依據是schedule字段定義的、一個標准的Unix Cron格式的表達式

  如,“*/1****”,這個Cron表達式里*/1中的*表示從0開始,/表示每,1表示偏移量,它的意思就是從零開始,每1個時間單位執行一次

  那時間單位又是什么意思?Cron表達式中的五個部分分別代表:分鍾、小時、日、月、星期。因此上面的意思就是每分鍾執行一次。

$ kubectl create -f ./cronjob.yaml
cronjob "hello" created

# 一分鍾后
$ kubectl get jobs
NAME               DESIRED   SUCCESSFUL   AGE
hello-4111706356   1         1         2s

##CronJob對象會記錄下這次Job的執行時間
$ kubectl get cronjob hello
NAME      SCHEDULE      SUSPEND   ACTIVE    LAST-SCHEDULE
hello     */1 * * * *   False     0         Thu, 6 Sep 2018 14:34:00 -070

  另外由於定時任務的特殊性,很可能某個Job還沒有執行完,另外一個新的Job就產生了,這時候可以通過spec.concurrencyPolicy字段來定義具體的處理策略

    • concurrencyPolicy=Allow:默認情況,Job可以同時存在;
    • concurrencyPolicy=Forbid:不會創建新的Jod,該創建周期被跳過
    • concurrencyPolicy=Replace:新產生的Job會替換舊的、沒有執行完的Job

  如果一個Job創建失敗,這次創建就會被標記為“miss”,當在指定的時間窗口內,miss的數目達到100時,那么CronJob會停止創建這個Job。這個時間窗口可以由spec.startingDeadlineSeconds字段指定。

  例如,startingDeadlineSeconds=200,意味着在過去的200s里,如果miss的數目到達了100次,那么這個Job就不會被創建執行了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM