锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

深入剖析Kubernetes--第五章:Kubernetes编排原理_控制器_Job与CronJob

时间:2023-03-01 05:30:00 sp6t中等功率射频继电器

Job与CronJob

创建一个Job API对象

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 
kubectl create -f job.yaml 

查看这个Job

[root@master zztK8s]# kubectl describe jobs pi Name:           pi Namespace:      default Selector:       controller-uid=6d16e85e-9b7c-474a-af93-d85ddbbda5be Labels:         controller-uid=6d16e85e-9b7c-474a-af93-d85ddbbda5be                 job-name=pi Annotations:     Parallelism:    1 Completions:    1 Start Time:     Mon, 07 Mar 2022 22:09:14  0800 Pods Statuses:  1 Running / 0 Succeeded / 0 Failed Pod Template:   Labels:  controller-uid=6d16e85e-9b7c-474a-af93-d85ddbbda5be            job-name=pi   Containers:    pi:     Image:      resouer/ubuntu-bc     Port:            Host Port:       Command:       sh       -c       echo 'scale=10000; 4*a(1)' | bc -l     Environment:       Mounts:          Volumes:         Events:   Type    Reason            Age   From            Message   ----    ------            ----  ----            -------   Normal  SuccessfulCreate  35s   job-controller  Created pod: pi-cdccb  

查看这个Pod:

Name:         pi-cdccb Namespace:    default Priority:     0 Node:         node1/192.168.88.101 Start Time:   Mon, 07 Mar 2022 22:09:14  0800 Labels:       controller-uid=6d16e85e-9b7c-474a-af93-d85ddbbda5be               job-name=pi Annotations:   Status:       Succeeded IP:           10.244.2.55 IPs:   IP:           10.244.2.55 Controlled By:  Job/pi Containers:   pi:     Container ID:  docker://29a089e89234d2ef9266e75c05541b30e2b77d4b663d67763dfcffcec88a7942     Image:         resouer/ubuntu-bc     Image ID:      docker-pullable://resouer/ubuntu-bc@sha256:3aff2cb1513375dc4ec42b80e8694cd1f9a8970fa5a55ebff98e1b85fe241d7f     Port:               Host Port:          Command:       sh       -c       echo 'scale=10000; 4*a(1)' | bc -l     State:          Terminated       Reason:       Completed       Exit Code:    0       Started:      Mon, 07 Mar 2022 22:09:32  0800       Finished:     Mon, 07 Mar 2022 22:10:48  0800     Ready:          False     Restart Count:  0     Environment:         Mounts:       /var/run/secrets/kubernetes.io/serviceaccount from default-token-z8n96 (ro) Conditions:   Type              Status   Initialized       True   Ready             False   ContainersReady   False   PodScheduled      True Volumes:   default-token-z8n96:     Type:        Secret (a volume populated by a Secret)     SecretName:  default-token-z8n96     Optional:    false QoS Class:       BestEffort Node-Selectors:   Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s                  node.kubernetes.io/unreachable:NoExecute for 300s Events:   Type    Reason     Age        From               Message   ----    ------     ----       ----               -------   Normal  Scheduled    default-scheduler  Successfully assigned default/pi-cdccb to node1   Normal  Pulling    4m20s      kubelet, node1     Pulling image "resouer/ubuntu-bc"   Normal  Pulled     4m3s       kubelet, node1     Successfully pulled image "resouer/ubuntu-bc"   Normal  Created    4m3s       kubelet, node1     Created container pi   Normal  Started    4m3s       kubelet, node1     Started container pi  

这个 Job 对象创建后,它的 Pod 自动添加模板 controller-uid=< 随机字符串 > 这样的 Label。而这个 Job 对象本身就是自动添加的 Label 对应的 Selector,从而 保证了 Job 它管理的 Pod 匹配关系。(Job Controller 之所以要使用这种携带了 UID 的 Label,避免不同 Job 对象管理 Pod 发生重合。)

[root@master zztK8s]# kubectl get  pods pi-cdccb NAME       READY   STATUS      RESTARTS   AGE pi-cdccb   0/1     Completed   0          6m35s 
[root@master zztK8s]# kubectl logs   pi-cdccb 3.141592653589793238462643383279502884197169399375105820974944592307\ 81640628620899862803482534211706798214808651328230664709384460955058\ 22317253594081284811174502841027019385211055596446229489549303819644\ 28810975665933446128475648233786783165271201909145648566923460348610\ 

离线作业失败

离线作业失败后 Job Controller 会不断尝试创造新的 Pod

Job 对象的 spec.backoffLimit 字段定义了重试次数 4(即,backoffLimit=4),这个字段的默认值是 6。

如果指定为 OnFailure,则job会在pod出现故障时重启容器,而不是创建pod,failed次数不变
如果指定为 Never,则job会在pod出现故障时创建新的pod,并且故障pod不会消失,也不会重启,failed次数加1

Always:在任何情况下,只要容器不在运行状态,就自动重启容器;

最长运行时间

在 Job 的 API 对象里,有一个 spec.activeDeadlineSeconds 字段可以设置最长运行时间,比如:

spec:
 backoffLimit: 5
 activeDeadlineSeconds: 100

一旦运行超过了 100 s,这个 Job 的所有 Pod 都会被终止。并且,你可以在 Pod 的状态里看到终止的原因是 reason: DeadlineExceeded。

并行作业

在 Job 对象中,负责并行控制的参数有两个:

  1. spec.parallelism,它定义的是一个 Job 在任意时间最多可以启动多少个 Pod 同时运行;
  2. spec.completions,它定义的是 Job 至少要完成的 Pod 数目,即 Job 的最小完成数。

小例子

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

指定了这个 Job 最大的并行数是 2,而最小的完成数是 4。

创建这个对象,查看Pod对象

[root@master zztK8s]# kubectl get pods pi-qlqc5
NAME       READY   STATUS      RESTARTS   AGE
pi-qlqc5   0/1     Completed   0          33s
[root@master zztK8s]# kubectl get pods
pi-bvv9j                     0/1     Completed          0          63s
pi-qlqc5                     0/1     Completed          0          111s
pi-sp6kq                     0/1     Completed          0          111s
pi-v2j8v                     0/1     Completed          0          80s

每当有一个 Pod 完成计算进入 Completed 状态时,就会有一个新的 Pod 被自动创建出来,并且快速地从 Pending 状态进入到 ContainerCreating 状态

[root@master zztK8s]# kubectl get jobs
NAME   COMPLETIONS   DURATION   AGE
pi     4/4           101s       3m8s

Job Controller 控制的对象,直接就是 Pod

三种常用的、使用 Job 对象的方法。

第一种:外部管理器 +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"]
      restartPolicy: Never

在这个 Job 的 YAML 里,定义了 $ITEM 这样的“变量”。

所以,在控制这种 Job 时,我们只要注意如下两个方面即可:

  1. 创建 Job 时,替换掉 $ITEM 这样的变量;
  2. 所有来自于同一个模板的 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 的 yaml 就生成了。接下来,就可以通过一句 kubectl create 指令创建这些 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 即可。

这种模式最典型的应用,就是 TensorFlow 社区的 KubeFlow 项目。

很容易理解,在这种模式下使用 Job 对象,completions 和 parallelism 这两个字段都应该使用默认值 1,而不应该由我们自行设置。而作业 Pod 的并行控制,应该完全交由外部工具来进行管理(比如,KubeFlow)。

第二种:拥有固定任务数目的并行 Job

这种模式下,只关心最后是否有指定数目(spec.completions)个任务成功退出。至于执行时的并行度是多少,并不关心。

比如,计算 Pi 值的例子,就是这样一个典型的、拥有固定任务数目(completions=4)的应用场景。 它的 parallelism 值是 2;或者,你可以干脆不指定 parallelism,直接使用默认的并行度(即:1)。

此外,你还可以使用一个工作队列(Work Queue)进行任务分发。这时,Job 的 YAML 文件定义如下所示:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-wq-1
spec:
  completions: 8
  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

可以看到,它的 completions 的值是:8,这意味着我们总共要处理的任务数目是 8 个。也就是说,总共会有 8 个任务会被逐一放入工作队列里(你可以运行一个外部小程序作为生产者,来提交任务)。

在这个实例中,我选择充当工作队列的是一个运行在 Kubernetes 里的 RabbitMQ。所以,我们需要在 Pod 模板里定义 BROKER_URL,来作为消费者。

所以,一旦你用 kubectl create 创建了这个 Job,它就会以并发度为 2 的方式,每两个 Pod 一组,创建出 8 个 Pod。每个 Pod 都会去连接 BROKER_URL,从 RabbitMQ 里读取任务,然后各自进行处理。这个 Pod 里的执行逻辑,我们可以用这样一段伪代码来表示:

/* job-wq-1 的伪代码 */
queue := newQueue($BROKER_URL, $QUEUE)
task := queue.Pop()
process(task)
exit

可以看到,每个 Pod 只需要将任务信息读取出来,处理完成,然后退出即可。而作为用户,我只关心最终一共有 8 个计算任务启动并且退出,只要这个目标达到,我就认为整个 Job 处理完成了。所以说,这种用法,对应的就是“任务总数固定”的场景。

第三种用法:指定并行度(parallelism),但不设置固定的 completions 的值。

决定什么时候启动新 Pod,什么时候 Job 才算执行完成。在这种情况下,任务的总数是未知的,所以你不仅需要一个工作队列来负责任务分发,还需要能够判断工作队列已经为空(即:所有的工作已经结束了)

Job 的定义基本上没变化,只不过是不再需要定义 completions 的值了而已:

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
        - 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

由于任务数目的总数不固定,所以每一个 Pod 必须能够知道,自己什么时候可以退出。比如,在这个例子中,我简单地以“队列为空”,作为任务全部完成的标志。所以说,这种用法,对应的是“任务总数不固定”的场景。

不过,在实际的应用中,你需要处理的条件往往会非常复杂。比如,任务完成后的输出、每个任务 Pod 之间是不是有资源的竞争和协同等等。

CronJob。

CronJob 描述的,正是定时任务。它的 API 对象,如下所示:

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

最重要的关键词就是jobTemplate。看到它,你一定恍然大悟,原来 CronJob 是一个 Job 对象的控制器(Controller)!

没错,CronJob 与 Job 的关系,正如同 Deployment 与 Pod 的关系一样。CronJob 是一个专门用来管理 Job 对象的控制器。只不过,它创建和删除 Job 的依据,是 schedule 字段定义的、一个标准的Unix Cron格式的表达式。

比如,"*/1 * * * *"。

这个 Cron 表达式里 */1 中的 * 表示从 0 开始,/ 表示“每”,1 表示偏移量。所以,它的意思就是:从 0 开始,每 1 个时间单位执行一次。

那么,时间单位又是什么呢?

Cron 表达式中的五个部分分别代表:分钟、小时、日、月、星期。

所以,上面这句 Cron 表达式的意思是:从当前开始,每分钟执行一次。

而这里要执行的内容,就是 jobTemplate 定义的 Job 了。

所以,这个 CronJob 对象在创建 1 分钟后,就会有一个 Job 产生了,如下所示:

$ 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 字段来定义具体的处理策略。比如:

  1. concurrencyPolicy=Allow,这也是默认情况,这意味着这些 Job 可以同时存在;
  2. concurrencyPolicy=Forbid,这意味着不会创建新的 Pod,该创建周期被跳过;
  3. concurrencyPolicy=Replace,这意味着新产生的 Job 会替换旧的、没有执行完的 Job。

而如果某一次 Job 创建失败,这次创建就会被标记为“miss”。当在指定的时间窗口内,miss 的数目达到 100 时,那么 CronJob 会停止再创建这个 Job。

这个时间窗口,可以由 spec.startingDeadlineSeconds 字段指定。比如 startingDeadlineSeconds=200,意味着在过去 200 s 里,如果 miss 的数目达到了 100 次,那么这个 Job 就不会被创建执行了。

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章