Kubernetes的主要任务是保证Pod中的应用长久稳定的运行,但是我们有时候也需要一些只需要运行一次,执行完就退出了的”短时”任务,这时候使用Deployment等这类控制器就无法满足我们的需求,Kubernetes就诞生了Job Controller,专门用来处理这类需求。

1、Job

1.1、基本操作

Job负责处理仅执行一次的任务,它保证批处理的任务的一个或多个成功结束,我们可以通过kubectl explain job来查看具体语法,如下:

  1. [root@master ~]# kubectl explain job
  2. KIND: Job
  3. VERSION: batch/v1
  4. DESCRIPTION:
  5. Job represents the configuration of a single job.
  6. FIELDS:
  7. apiVersion <string>
  8. APIVersion defines the versioned schema of this representation of an
  9. object. Servers should convert recognized schemas to the latest internal
  10. value, and may reject unrecognized values. More info:
  11. https://git.k8s.io/community/contributors/devel/api-conventions.md#resources
  12. kind <string>
  13. Kind is a string value representing the REST resource this object
  14. represents. Servers may infer this from the endpoint the client submits
  15. requests to. Cannot be updated. In CamelCase. More info:
  16. https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
  17. metadata <Object>
  18. Standard object's metadata. More info:
  19. https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
  20. spec <Object>
  21. Specification of the desired behavior of a job. More info:
  22. https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
  23. status <Object>
  24. Current status of a job. More info:
  25. https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

从上面可以看到跟我们定义Deployment,Pod的语法是差不多的,下面我们定义一个Job的YAML文件:
job-demo.yaml

  1. apiVersion: batch/v1
  2. kind: Job
  3. metadata:
  4. name: job-demo
  5. namespace: default
  6. spec:
  7. template:
  8. metadata:
  9. name: job-demo
  10. spec:
  11. containers:
  12. - name: test-job
  13. image: busybox
  14. imagePullPolicy: IfNotPresent
  15. command:
  16. - "/bin/sh"
  17. - "-c"
  18. args:
  19. - "for i in $(seq 10); do echo $i; done"
  20. restartPolicy: Never
  21. backoffLimit: 4

从上面的YAML文件我们可以看到Job的spec里就是我们熟悉的Pod模板。我们执行kubectl apply -f job-demo.yaml,查看运行信息:

  1. [root@master job]# kubectl get job
  2. NAME COMPLETIONS DURATION AGE
  3. job-demo 1/1 5s 7s
  4. [root@master job]# kubectl describe job job-demo
  5. Name: job-demo
  6. Namespace: default
  7. Selector: controller-uid=5000fea1-8af7-4199-b482-082b7e94f6fd
  8. Labels: controller-uid=5000fea1-8af7-4199-b482-082b7e94f6fd
  9. job-name=job-demo
  10. Annotations: kubectl.kubernetes.io/last-applied-configuration:
  11. {"apiVersion":"batch/v1","kind":"Job","metadata":{"annotations":{},"name":"job-demo","namespace":"default"},"spec":{"backoffLimit":4,"temp...
  12. Parallelism: 1
  13. Completions: 1
  14. Start Time: Wed, 23 Oct 2019 15:55:19 +0800
  15. Completed At: Wed, 23 Oct 2019 15:55:24 +0800
  16. Duration: 5s
  17. Pods Statuses: 0 Running / 1 Succeeded / 0 Failed
  18. Pod Template:
  19. Labels: controller-uid=5000fea1-8af7-4199-b482-082b7e94f6fd
  20. job-name=job-demo
  21. Containers:
  22. test-job:
  23. Image: busybox
  24. Port: <none>
  25. Host Port: <none>
  26. Command:
  27. /bin/sh
  28. -c
  29. Args:
  30. for i in $(seq 10); do echo $i; done
  31. Environment: <none>
  32. Mounts: <none>
  33. Volumes: <none>
  34. Events:
  35. Type Reason Age From Message
  36. ---- ------ ---- ---- -------
  37. Normal SuccessfulCreate 15s job-controller Created pod: job-demo-f9hmn

从上面可以看到在Pod里自动生成了一个controller-uid的label,然后在Job的selector也会自动加上这个label,这就建立Job和它管理的Pod之间的对应关系。

接下来我们查看这个Pod的状态,如下:

  1. [root@master job]# kubectl get pod
  2. NAME READY STATUS RESTARTS AGE
  3. job-demo-f9hmn 0/1 Completed 0 2m35s

我们可以看到执行结束后,STATUS变成了Completed状态,而且RESTARTS为0(表示为重启)。我们也可以看一下这个Pod的日志,如下:

  1. [root@master job]# kubectl logs job-demo-f9hmn
  2. 1
  3. 2
  4. 3
  5. 4
  6. 5
  7. 6
  8. 7
  9. 8
  10. 9
  11. 10

我们可以看到这个Pod只运行了一次。
那么如果这个任务执行失败了呢?比如我们将上面的YAML文件中的args参数随便改一个非Linux命令,如下:

  1. ......
  2. args:
  3. - "xxxxxx"
  4. ......

然后我们查看Pod的状态:

  1. [root@master job]# kubectl get pod
  2. NAME READY STATUS RESTARTS AGE
  3. job-demo-2ccjq 0/1 Error 0 11s
  4. job-demo-hwss6 0/1 ContainerCreating 0 3s

我们看到这个Job任务没有执行成功,我们定义了restartPolicy=Never,这时候Job Controller就会创建一个新的Pod。我们在Job的YAML文件里定义了backoffLimit=4,这表明重试次数只有4次,默认是6次,4次重试都还未成功,则这个Job则失败:

  1. [root@master job]# kubectl get pod
  2. NAME READY STATUS RESTARTS AGE
  3. job-demo-2ccjq 0/1 Error 0 3m21s
  4. job-demo-2kdpc 0/1 Error 0 2m43s
  5. job-demo-7q62j 0/1 Error 0 3m3s
  6. job-demo-hwss6 0/1 Error 0 3m13s
  7. You have new mail in /var/spool/mail/root
  8. [root@master job]# kubectl get job
  9. NAME COMPLETIONS DURATION AGE
  10. job-demo 0/1 5m49s 5m49s
  11. You have new mail in /var/spool/mail/root

如果我们定义restartPolicy=OnFailure,如果作业失败,Job Controller就不会在创建新的Pod,但是会不断重启这个Pod。如下,我们把Job的YAML文件中的restartPolicy改成restartPolicy=OnFailure,测试如下:

  1. [root@master job]# kubectl get pod
  2. NAME READY STATUS RESTARTS AGE
  3. job-demo-cbmft 0/1 Error 3 69s

我们可以看到RESTARTS变为了1,表示重启了3次。

还有一种情况,如果这个Job一直不肯结束怎么办呢?比如我们将上面的YAML文件做如下修改:

  1. apiVersion: batch/v1
  2. kind: Job
  3. metadata:
  4. name: job-demo
  5. namespace: default
  6. spec:
  7. template:
  8. metadata:
  9. name: job-demo
  10. spec:
  11. containers:
  12. - name: test-job
  13. image: busybox
  14. imagePullPolicy: IfNotPresent
  15. command:
  16. - "/bin/sh"
  17. - "-c"
  18. args:
  19. - "sleep 3600"
  20. restartPolicy: OnFailure
  21. backoffLimit: 4

然后执行kubectl apply -f job-demo.yaml。我们可以发现这个Pod不会结束直到3600秒后,这时候如果我们加一个参数activeDeadlineSeconds,如下:

  1. spec:
  2. ......
  3. restartPolicy: OnFailure
  4. backoffLimit: 4
  5. activeDeadlineSeconds: 100

这个参数的作用是如果这个Pod运行时间超过100s,这个Pod将被终止。

1.2、并行控制

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

  • completions:定义Job至少要完成的Pod数目,既Job的最小完成数;
  • parallelism:定义一个Job在任意时间最多可以启动多少个Pod;

我们定义下面一个Job的YAML文件:

  1. apiVersion: batch/v1
  2. kind: Job
  3. metadata:
  4. name: job-demo
  5. namespace: default
  6. spec:
  7. parallelism: 2
  8. completions: 4
  9. template:
  10. metadata:
  11. name: job-demo
  12. spec:
  13. containers:
  14. - name: test-job
  15. image: busybox
  16. imagePullPolicy: IfNotPresent
  17. command:
  18. - "/bin/sh"
  19. - "-c"
  20. args:
  21. - "for i in $(seq 10); do echo $i; done"
  22. restartPolicy: OnFailure
  23. backoffLimit: 4
  24. activeDeadlineSeconds: 100

parallelism: 2 和 completions: 4表示要完成4个pod,每次可以同时运行两个Pod,我们创建这个Job。

  1. [root@master job]# kubectl apply -f job-demo.yaml
  2. job.batch/job-demo created
  3. [root@master job]# kubectl get pod
  4. NAME READY STATUS RESTARTS AGE
  5. job-demo-kcm4c 0/1 ContainerCreating 0 1s
  6. job-demo-kdrxl 0/1 Completed 0 4s
  7. job-demo-r4k49 0/1 ContainerCreating 0 2s
  8. job-demo-w9c49 0/1 Completed 0 4s

可以看到两个运行结束,另外两个才开始。

1.3、原理总结

从上面可以知道,Job Controller实际控制的就是Pod,它在创建的时候会在Job和Pod里自动生成随机字符串的label,然后将它们进行绑定。
Job Controller在实际的调谐操作是根据实际在running状态的Pod数,还有已经退出的Pod数以及parallelism和completions的参数值共同计算出在Job周期内应该创建或者删除多少Pod,然后调用kube-api来执行这类操作。
所以Job Controller实际上是控制的Pod的并行度以及总共要完成的任务数这两个重要的参数。

1.4、使用场景

1.4.1、外部管理器+Job模板

用法:把Job的YAML文件定义为一个模板,然后用外部工具来控制这个模板生成Job。
比如我们定义如下YAML:

  1. apiVersion: batch/v1
  2. kind: Job
  3. metadata:
  4. name: process-item-$ITEM
  5. labels:
  6. jobgroup: jobexample
  7. spec:
  8. template:
  9. metadata:
  10. name: jobexample
  11. labels:
  12. jobgroup: jobexample
  13. spec:
  14. containers:
  15. - name: c
  16. image: busybox
  17. command: ["sh", "-c", "echo Processing item $ITEM && sleep 5"]
  18. restartPolicy: Never

我们在这个YAML文件里设定了一个$ITEM的变量,然后我们使用外部脚本来通过这个YAML文件生成Job,如下:

  1. $ mkdir ./jobs
  2. $ for i in apple banana cherry
  3. do
  4. cat job-tmpl.yaml | sed "s/\$ITEM/$i/" > ./jobs/job-$i.yaml
  5. done

然后就会生成三个Job文件,这时候就可以通过kubectl apply -f .来执行这些Job。

1.4.2、固定任务数的并行Job

在这种场景下,我们只关注是否有我们定义的指定数目的任务成功退出,并不会去关心并行度是多少。
比如:

  1. apiVersion: batch/v1
  2. kind: Job
  3. metadata:
  4. name: job-wq-1
  5. spec:
  6. completions: 8
  7. parallelism: 2
  8. template:
  9. metadata:
  10. name: job-wq-1
  11. spec:
  12. containers:
  13. - name: c
  14. image: myrepo/job-wq-1
  15. env:
  16. - name: BROKER_URL
  17. value: amqp://guest:guest@rabbitmq-service:5672
  18. - name: QUEUE
  19. value: job1
  20. restartPolicy: OnFailure

上面的文件会以并发度为2的方式创建8个Pod,然后每个Pod各自去处理任务,我们最终只关心是否有8个Pod启动并且退出,只要这个目标达成,我们就认为这个Job成功执行。

1.4.3、设置并行度,不设置固定值

这种情况就必须自己决定什么时候启动Pod,什么时候执行完成,由于没有限制任务的总数,所以不仅需要一个工作队列来决定任务的分发,还需要能够判定工作队列是否为空,也就是任务是否完成。
如下:

  1. apiVersion: batch/v1
  2. kind: Job
  3. metadata:
  4. name: job-wq-2
  5. spec:
  6. parallelism: 2
  7. template:
  8. metadata:
  9. name: job-wq-2
  10. spec:
  11. containers:
  12. - name: c
  13. image: gcr.io/myproject/job-wq-2
  14. env:
  15. - name: BROKER_URL
  16. value: amqp://guest:guest@rabbitmq-service:5672
  17. - name: QUEUE
  18. value: job2
  19. restartPolicy: OnFailure

由于任务数不确定,所以每个Pod必须能够知道,自己什么时候可以退出。

2、CronJob

CronJob其实就在Job的基础上加了时间调度,类似于用Deployment管理Pod一样。它和我们Linux上的Crontab差不多。
比如:

  1. apiVersion: batch/v1beta1
  2. kind: CronJob
  3. metadata:
  4. name: hello
  5. spec:
  6. schedule: "*/1 * * * *"
  7. jobTemplate:
  8. spec:
  9. template:
  10. spec:
  11. containers:
  12. - name: hello
  13. image: busybox
  14. command:
  15. - "/bin/sh"
  16. - "-c"
  17. args:
  18. - "for i in $(seq 10); do echo $i; done"
  19. restartPolicy: OnFailure

我们可以看到spec里其实就是一个Job Template。另外其schedule就是一个便准的Cron格式,

  1. 分钟 小时 星期
  2. * * * * *

我们创建上面的YAML文件。查看器结果:

  1. [root@master job]# kubectl apply -f cronjob-demo.yaml
  2. cronjob.batch/hello created
  3. [root@master job]# kubectl get cronjobs.batch
  4. NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
  5. hello */1 * * * * False 0 <none> 5s

需要注意的是,由于cron的特殊性,有时候会存在由于上一个定时任务还没有执行完成,新的定时任务又开始了的情况,我们可以通过定义spec.concurrencyPolicy字段来定义规则,比如:

  • concurrencyPolicy=Allow:表示这些Job可以同时存在
  • concurrencyPolicy=Firbid:表示不会创建新的Job,也就是这个定时任务被跳过
  • concurrencyPolicy=Replace:表示产生的新Job会替代旧的Job

如果某一个Job创建失败,那么这次创建就会被标记为miss,当在指定的时间窗口内,Miss的数达到100,那么CronJob就会停止再创建这个Job。这个时间窗口可以通过spec.startingDeadlineSeconds来指定。