前面讲述的无论是Deployment,StatefulSet还是DeamonSet管理的都是在线业务,也就是:Long Running Task(长作业)。这些应用一直保持运行状态。
而这节讲述的就是只会运行一次的离线任务,也就是执行完之后就会退出。不需要进行重启与滚动更新。
Job
这是v1.4版本之后,社区设计出的一个用来描述离线业务的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启动了一个Ubuntu镜像的容器,然后运行echo "scale=10000; 4*a(1)" | bc -l 命令。这就是一个计算Π值小数点后10000的计算任务。不会重启,即restartPolicy: Nevwer。
Job对象并不要求定义一个spec.selector来描述要控制哪些Pod。
创建这个Job
$ kubectl create -f job.yaml查看创建的Job对象可以看到,其Pod模板中自动添加了一个 controller-uid=< 一个随机字符串 > 这样的 Label。这个Job对象本身自动被加上了这个Label对应的Selector,保证了Job换个它所管理的Pod之间的匹配关系。所以前面提到过不需要定义spec.selector字段,因为Job会自动添加。
$ 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-rq5rlPod计算结束,就会进入Completed状态。重启策略为Never,所以离线计算的Pod永远都不会被重启。
Note
事实上,restartPolicy 在 Job 对象里只允许被设置为 Never 和 OnFailure;而在 Deployment 对象里,restartPolicy 则只允许被设置为 Always。
虽然Job不会重启,但是如果离线任务失败是会进行重试的,Job Controller会不断重试创建一个新Pod直到计算成功。为了避免无限重试,sepc.backoffLimit定义能够重试的次数,默认是6。重试之之间的时间间隔是呈指数增加的,10s, 20s, 40s…。
一个Job的Pod运行结束进入Completed状态,但是为了避免一个Pod因为某种原因无法退出,通过sepc.activeDeadlineSeconds字段可以设置Pod的最长运行时间,一旦超过了这个时间,这个Job的所有Pod都会被终止。
Job Controller并行作业控制
负责并行控制的参数有两个:
- spec.parallelism:定义一个Pod在任意时间最多可以启动多少个Pod同时运行。
- spec.completions:定义Job至少要完成的Pod数目,即Job的最小完成数。
简单来说就是一次最多运行的任务数和最终需要达到的完成数。
给Job的YAML配置添加这两个参数:
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个Pod,最终需要完成4个Pod。最初启动两个Pod完成计算任务,每完成一个就启动一个新的Pod继续计算,直到达到4个最小完成数。
JobController控制的对象就是Pod,根据前面提到的两个参数和集群中的状态,即实际Running状态Pod的数目、已经成功退出的Pod的数目共同计算出这个周期里,应该创建或者删除的Pod的数目,然后调用k8s的API来执行操作。
需要创建的 Pod 数目 = 最终需要的 Pod 数目 - 实际在 Running 状态 Pod 数目 - 已经成功退出的 Pod 数目。
Job对象使用方法
1. 外部管理器+Job模板
这是最简单粗暴的用法。把Job的YAML文件定义为一个模板,然后用一个外部工具来控制这些模板生成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在模板中引入了类似$ITEM这样的变量,在创建Job是将这些变量进行替换就生成了Job的YAML文件,例如通过shell脚本进行替换:
$ mkdir ./jobs
$ for i in apple banana cherry
do
cat job-tmpl.yaml | sed "s/\$ITEM/$i/" > ./jobs/job-$i.yaml
done然后即可运行这些Job。
2. 拥有固定任务数目的Job
就是只关心是否有指定数目(spec.completions)的任务成功退出,并不关心执行时的并行度。
比如下面的例子中,指定了8个最小完成数目,然后还使用了工作队列进行任务分发,这里的生产者是外部程序,Pod作为消费者来从工作队列中获取任务并执行:
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只关心最终一共有 8 个计算任务启动并且退出,只要这个目标达到,我就认为整个 Job 处理完成了。所以说,这种用法,对应的就是“任务总数固定”的场景。
3.指定并行度,不设置固定的completions值
任务的总数是未知的,所以需要想办法判断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
- name: QUEUE
value: job2
restartPolicy: OnFailure上一步可以由Job来决定什么时候任务结束,但是现在需要Pod进行处理,工作队列为空则结束任务:
/* job-wq-2的伪代码 */
for !queue.IsEmpty($BROKER_URL, $QUEUE) {
task := queue.Pop()
process(task)
}
print("Queue empty, exiting")
exitCronJob
这是一个定时任务的Job。 其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的控制器。通过schedule字段定义的unix cron格式的表达式来决定何时创建或删除Job对象。
这两者的关系与Deployment和ReplicaSet的关系很像。
定时任务很特殊,一个Job可能还没有完成,另一个新的Job可能就需要创建了。通过spec.concurrencyPolicy字段来定义具体的处理策略。
- Allow,默认情况。表示这些Job可以同时存在。
- Forbid,不会创建新的Pod,创建周期被跳过。
- Replace:新产生的Job会替代旧的、没有执行完的Job。
一个Job如果创建失败,那么这次创建就会被标记为miss,在指定时间窗口内达到100次miss,那么这个CronJob就会停止创建这个Job。这个时间窗口可以由spec.startingDeadlineSeconds来指定。