3.9 Jobs、CronJobs

在 Kubernetes 中,常用的工作负载/控制器,有 Deployment、ReplicaSet、StatefulSet、DaemonSet、Job、Cronjob、ReplicationController 等,在前面,我们已经学习了 Deloyment、DaemonSet、ReplicationController,本章将学习 Job 和 Cronjob。

我们知道,Pod 是一种临时性的对象,我们单独创建 Pod,一般用于临时测试调试,在生产中我们使用 Deployment 等对 Pod 进行管理。而 Job、Cronjob 它们主要用于创建一个或多个 Pod,来完成某些任务,它们创建的 Pod 不会长久的运行在节点中。

Job

Job 是用来只运行一次任务的对象,Job 对象以一种可靠的方式运行某 Pod 直到完成,适合用于批处理,例如编译程序、执行运算任务。Job 适合一次到位或者一次完整的流程,完成后即可抛弃的任务。

控制器异同点

前面我们已经学习到了 Deployment 、ReplicaSet,Job 跟它们一些异同点,这里列举一些它们各自的特点。

  • Pod 生命期

    和一个个独立的应用容器一样,Pod 也被认为是相对临时性(而不是长期存在)的实体。 Pod 会被创建、赋予一个唯一的 ID, 并被调度到节点,并在终止(根据重启策略)或删除之前一直运行在该节点。

  • Pod 是临时的

    Pod 自身不具有自愈能力。如果 Pod 被调度到某节点 而该节点之后失效,或者调度操作本身失效,Pod 会被删除;与此类似,Pod 无法在节点资源耗尽或者节点维护期间继续存活。Deployment、ReplicaSet、Job 都会重新创建新的 Pod 来替代已终止的 Pod。

  • Deployment/ReplicaSet、Job

    Deployment/ReplicaSet 控制器管理的是那些不希望被终止的 Pod (例如,Web 服务器), Job 管理的是那些希望被预期终止的 Pod( 例如,批处理作业)。

  • 为什么不用 Pod

    Pod 是临时性的,有 Always、OnFailure、Never 等选项,设置在容器无法启动时的重试机制;而 Job 只有 OnFailure 、Never ,Job 最多只能重试一次。Pod 在自身没有保障,挂了就挂了,而且 Pod 本身只有一个。Job 会创建一个或者多个 Pods,在失败时将继续重试 Pods 的执行,或者创建新的 Pod,直到指定数量的 Pods 成功终止。

Job

当 Job 启动时,Job 会跟踪成功完成的 Pod 的个数,当成功数量达到某个阈值时,Job 会被终结。当 Job 运行过程中,我们 暂停/挂起 Job,Job 会删除正在运行的 Pod,保留已完成的 Pod 数量,当恢复 Job 时,会创建新的 Pod,继续完成任务。

Job 的结构很简单,下面是一个示例,这个 Job 只有一个镜像,启动后执行 sleep 3 命令,容器会在3秒后自动退出,Job 标记其已经完成。

apiVersion: batch/v1
kind: Job
metadata:
  name: busybox
spec:
  template:
    spec: 
      containers:
      - name: busybox
        image: busybox
        command: ["/bin/sleep"]
        args: ["3"]
      restartPolicy: Never

对于一个简单的 Job,其关键在于设定一个可结束的命令,容器执行命令后会退出。

此时这个容器被标记为完成状态。

       command: ["/bin/sleep"]
       args: ["3"]
     restartPolicy: Never

restartPolicy 需要标记为 Never 或 OnFailure。

使用 kubectl apply -f job.yaml 命令启动此 Job,3 秒后查看 Pod:

root@instance-1:~# kubectl get pods
NAME                          READY   STATUS      RESTARTS   AGE
busybox-k4wkn                 0/1     Completed   0          16s

可以发现此 Pod 处于完成状态(Completed),再查看 job 列表:

root@instance-1:~# kubectl get jobs
NAME      COMPLETIONS   DURATION   AGE
busybox   1/1           11s        101s

完成后我们可以直接删除它:

kubectl delete job busybox

对于这种简单的 Job,称为非并行的,它的特点有:

  • 只启动一个 Pod,除非该 Pod 失败;
  • 当 Pod 成功终止时,立即视 Job 为完成状态;

这里再列举一个 Job 示例,这个 Job 用于计算圆周率,计算完毕后打印圆周率并结束容器。

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

查看 Pod 列表,找到 pi- 开头的 Pod,查看日志:

root@instance-1:~# kubectl logs pi-7htxl
3.1415926535897932384626433... ...

.spec.backoffLimit 可视为 Job 在失败之前的重试次数。

如果我们设置了 restartPolicy: OnFailure,那么此 Pod 会被重试一次;

如果 restartPolicy: Never,并且设置了 backoffLimit,Pod 会被重试多次。

完成数

使用.spec.completions 来设置完成数时,Job 控制器所创建的每个 Pod 使用完全相同的 spec 模板。 这意味着任务的所有 Pod 都有相同的命令行,都使用相同的镜像和数据卷,甚至连 环境变量都(几乎)相同。

如下 YAML,template 中的 spec,是每个 Pod 都带有的相关属性,使得每个 Pod 都保持一致。

spec:
  completions: 5 
  template:
    spec:

Job 会创建一个或者多个 Pods,并将继续重试 Pods 的执行,直到指定数量的 Pods 成功终止。 随着 Pods 成功结束,Job 跟踪记录成功完成的 Pods 个数。 当数量达到指定的成功个数阈值时,任务(即 Job)结束。 删除 Job 的操作会清除所创建的全部 Pods。

我们继续使用上次的 Job 模板,这里增加一个 completions ,完整 YAML 内容如下:

apiVersion: batch/v1
kind: Job
metadata:
  name: busybox
spec:
  completions: 5 
  template:
    spec: 
      containers:
      - name: busybox
        image: busybox
        command: ["/bin/sleep"]
        args: ["3"]
      restartPolicy: Never

使用 kubectl apply -f job.yaml 启动此 Job。

查看 Job 和 Pod:

root@instance-1:~# kubectl get jobs
NAME      COMPLETIONS   DURATION   AGE
busybox   5/5           36s        38s
root@instance-2:~# kubectl get pods
NAME                          READY   STATUS      RESTARTS   AGE
busybox-rfhcj                 0/1     Completed   0          9s
busybox-stkbg                 0/1     Completed   0          23s
busybox-xk6sb                 0/1     Completed   0          30s
busybox-z6h9x                 0/1     Completed   0          40s
busybox-zqgcb                 0/1     Completed   0          16s

Pod 的创建是串行的,每次只运行一个 Pod,当一个 Pod 处于 Completed 状态时,创建下一个 Pod。当有 5 个 Pod 处于 Completed 状态时,此 Job 标记完成。

前面学习到了 completions,串行启动 Pod,直到 Pod 完成数量为 5 。

如果经常查看 Job 的状态,可以观察到:

NAME      COMPLETIONS   DURATION   AGE
busybox   1/5           30s        10s

NAME      COMPLETIONS   DURATION   AGE
busybox   2/5           28s        18s

NAME      COMPLETIONS   DURATION   AGE
busybox   3/5           28s        25s

root@instance-1:~# kubectl get jobs
NAME      COMPLETIONS   DURATION   AGE
busybox   4/5           30s        30s

root@instance-1:~# kubectl get jobs
NAME      COMPLETIONS   DURATION   AGE
busybox   5/5           36s        37s
flowchart LR Start --> busybox1 --> busybox2 --> busybox3 --> busybox4 --> busybox5 --> Stop

并行

我们查看前面设置 Job 的 YAML :

kubectl get job busybox -o yaml
... ...
spec:
  backoffLimit: 6
  completions: 5
  parallelism: 1
  selector:
  ... ...

可以看到 parallelism=1,这个 parallelism 正是控制并行度的字段,由于这个字段默认为 1,所以这个 Job 每次只能运行一个 Pod。

spec.completionsspec.parallelism,这两个属性都不设置时,均取默认值 1。

当我们修改这个 parallelism 值大于 1 时,多个 Pod 可以同时执行。

.spec.parallelism可以设置为任何非负整数。 如果设置为 0,则 Job 相当于启动之后便被暂停,因为一直没有 Pod 完成,当然我们可以使用 kubectl edit 命令修改其值。

另外,可以 Job 运行时,使用 kubecl scale job xxxx --replicas=n 修改 Job 的并行数量 parallelism 字段的值。

parallelism=2 时,其启动过程可能如下:

graph TD; Start-->busybox1 Start-->busybox2; busybox1-->busybox3; busybox2-->busybox4; busybox3-->busybox5;

带工作队列的 Job

带工作队列的 Job,指设置了 .spec.parallelism,可以不设置 .spec.completions 的 Job。

此时 Job 需要等待所有的 Pod 完成任务,Job 才能终结。

下面创建一个 Job,这里我们启动 5 个 Pod,每个 Pod 不断向 RabbitMQ 请求消费一条消息,当 RabbitMQ 已经没有可消费的消息时,Pod 结束。

apiVersion: batch/v1
kind: Job
metadata:
  name: job-wq-1
spec:
  parallelism: 5
  template:
    metadata:
      name: job-wq-1
    spec:
      containers:
      - name: c
        image: gcr.io/<project>/job-wq-1
        env:
        - name: BROKER_URL
          value: amqp://guest:guest@rabbitmq-service:5672
        - name: QUEUE
          value: job1
      restartPolicy: OnFailure

[Success] 提示

此处使用的 RabbitMQ 是 Kubernetes 官方供大家练习用的,不需要自己部署一个来测试。

此次容器的镜像不是真实存在的,要进行实验,可参考 https://kubernetes.io/docs/tasks/job/_print/#create-an-image 构建镜像测试。

对于没有设置 .spec.completions 的 Job,情况复杂的多,这种情况下任务的总数是未知的。

不设置 .spec.completions 的带工作队列的 Job 会同时启动多个 Pod,只要有任意一个 Pod 成功退出,Job 控制器就知道任务已经完成了,所有的 Pod 将很快会退出。 当所有 Pod 结束,此 Job 才会标记为成功完成,虽然只需要有一个 Pod 完成即可,但 Job 控制器还是会等待其它 Pod 结束。

笔者摘录 Kubernetes 官方文档关于这种 Job 的描述:

  • 不设置 spec.completions
  • 多个 Pod 之间必须相互协调,或者借助外部服务确定每个 Pod 要处理哪个工作条目。
  • 每个 Pod 都可以独立确定是否其它 Pod 都已完成,进而确定 Job 是否完成
  • 当 Job 中 任何 Pod 成功终止,不再创建新 Pod
  • 一旦至少 1 个 Pod 成功完成,并且所有 Pod 都已终止,即可宣告 Job 成功完成
  • 一旦任何 Pod 成功退出,任何其它 Pod 都不应再对此任务执行任何操作或生成任何输出。 所有 Pod 都应启动退出过程。

对于这种 Job ,使用起来比较复杂,这种 Job 一般需要结合外部服务来完成任务,例如前面提到了 RabbitMQ。

以前面的 RabbitMQ 为例,Job 启动了 5 个 Pod 消费消息,当 RabbitMQ 没有消息了,此时 Pod1 把任务完成了,然后请求 RabbitMQ ,发现没有消息可处理,Pod1 便结束运行。此时 Job 已经算完成了,因为 Job 的任务是处理完 RabbitMQ,RabbitMQ 没有消息了,此 Job 自然算完成了,但是此时还有四个 Pod 取到了消息但是就没有计算完成,这四个 Pod 继续处理完成任务。当 5 个 Pod 都结束时,此 Job 便功德圆满了。

带类型的 Job

Job 中的 Pod 都是一样的,因此如果要 Job 处理不同的工作任务,则需要外界帮忙。

举个例子,平台是一个电商系统,消息队列中有评论、订单等五类消息,那么应该设计五种程序去处理这些消息,但是 Pod 只有一种。此时可以设置原子性的任务领取中心,Job 启动 Pod 后,Pod 便向任务中心领取任务类型,领取到后,开始工作。

那么 Job 中的 Pod 可能是这样完成工作的:

gantt dateFormat YYYY-MM-DD title Message Queue section Job 评论处理 :done , 2021-11-01, 1d 问答处理 :done, 2021-11-02, 1d 差评处理 :done, 2021-11-03, 1d 订单处理 :done, 2021-11-04, 1d 退货处理 :active, 2021-11-05, 1d

[Info] 提示

限于条件,这里就不编写具有这种功能的程序了,读者只需要了解即可。

在 Job 创建的 Pod 中,会有个名为 JOB_COMPLETION_INDEX 的环境变量,此环境变量标识了 Pod 的索引,Pod 可以通过此索引标识自己的身份。

示例 YAML 如下:

apiVersion: batch/v1
kind: Job
metadata:
  name: busybox
spec:
  parallelism: 1
  completions: 5
  completionMode: Indexed
  template:
    spec:
      containers:
      - name: busybox
        image: busybox
        command: ["env"]
      restartPolicy: Never

[Info] 提示

completionMode: Indexed 表明当前 Pod 是带索引的,如果 completionMode: NonIndexed 则不带索引。

索引会按照 0,1,2,3 这样递增。

执行 kubectl apply -f job.yaml 启动此 Job,会发现:

root@master:~# kubectl get pods
NAME                       READY   STATUS             RESTARTS          AGE
busybox-0-j8gvz            0/1     Completed          0                 29s
busybox-1-k4kfx            0/1     Completed          0                 25s
busybox-2-zplxl            0/1     Completed          0                 14s
busybox-3-pj4jk            0/1     Completed          0                 10s
busybox-4-q5fq9            0/1     Completed          0                 6s

如果我们查看 Pod 的环境变量,会发现:

root@master:~# kubectl logs busybox-0-j8gvz
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=busybox-0
JOB_COMPLETION_INDEX=0
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
HOME=/root

处理 Pod 和容器失效

Job 终止和清理

如果我们不希望 Job 运行太长时间,可以为 Job 的 .spec.activeDeadlineSeconds 设置一个秒数值。 在 Job 的整个生命期,无论 Job 创建了多少个 Pod。 一旦 Job 运行时间达到 activeDeadlineSeconds 秒,其所有运行中的 Pod 都会被终止,并且 Job 的状态更新为 type: Failedreason: DeadlineExceeded

YAML 示例:

apiVersion: batch/v1
kind: Job
metadata:
  name: busybox
spec:
  completions: 5 
  activeDeadlineSeconds: 2
  template:
    spec: 
      containers:
      - name: busybox
        image: busybox
        command: ["/bin/sleep"]
        args: ["3"]
      restartPolicy: Never

CronJob

CronJobs 对于创建周期性的、反复重复的任务很有用,例如执行数据备份或者发送邮件。 CronJobs 也可以用来计划在指定时间时来执行的独立任务,例如计划当集群看起来很空闲时 执行某个 Job。

CronJob 有 个 schedule 字段,用来配置合适启动此 CronJob,其使用五个时间单位,每一位表示一个时刻表示,如 "*/1 * * * *"

# ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6) (周日到周一;在某些系统上,7 也是星期日)
# │ │ │ │ │                                   
# │ │ │ │ │
# │ │ │ │ │
# * * * * *

每个小时运行一次,在 0 分时开始运行:0 * * * *,或使用 @hourly 表示。

每天运行一次,在半夜 0 时 0 分运行: 0 0 * * * ,或使用 @daily 表示。

在每周的周日 0 点运行一次,时间在半夜: 0 0 * * 0,或使用 @weekly 表示。

在每月的 1 号运行一次: 0 0 1 * * ,或使用 @monthly 表示。

每年的 1 月 1 日 执行一次:0 0 1 1 * ,或使用 @yearly 表示。

CronJob 有四种符合,分别是 *,-/

符号 / ,使用格式 */{值} ,表示每间隔 1 个 时间执行一次,例如 */1 * * * * 表示间隔一分钟执行一次。

符号 -,使用格式 {值1}-{值2},表示范围中的每一个时间,例如 0-2 * * * * 表示 0 到 20 分钟的每一分钟执行一次,一共 20 次。

符号 ,,使用格式 */{值1},{值2}...,表示碰到这里的时间都执行一次,例如 1,5,6 * * * *,表示 每次在 1,5,6 分钟时都执行一次。

组合示例,23 0-20/2 * * *,表示在 0-20 点时,每间隔 2 个小时执行一次,其包括的时间:

00:23:00
02:23:00
04:23:00
... ...
20:23:00

请参考:

https://en.wikipedia.org/wiki/Cron

https://crontab.guru/

可供实验的 YAML 示例如下:

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

此 CronJob 会每分钟执行一次。

Copyright © 痴者工良 2021 all right reserved,powered by Gitbook文档最后更新时间: 2021-11-14 13:53:35

results matching ""

    No results matching ""