我们学习了Deployment,StatefulSet,以及DaemonSet这三个编排概念,有没有发现共同之处,这些都是些在线业务,这些业务如果运行起来,除非出错了,不然会一直保持在Running状态
但是有些任务,是希望可以再执行完成后直接退出的,如果使用Deployment来管理这种业务的话,就会发现Pod会在计算之后直接退出,但是Deployment Controller会不断地重启,就好比滚动更新一样
而在Kubernetes提供了一个专门描述一次性 离线业务的API对象 名为Job
Job对象的定义很简单,可以看下面的例子
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
在这个里面,首先说的是,一个标准的template字段,这是一个Pod模板,其中定义了一个Ubuntu镜像的容器,在这个容器里面,运行的程序是
echo “scale=10000; 4*a(1)” | bc -l
bc命令是Linux中的计算器 -l表示用标准数字库,a(1)是调用arctangent函数,
上面的式子翻译过来时计算π的值,并且计算到小数点后10000
然后我们就可以直接创建这个Job对象了
kubectl create -f job.yaml
然后可以看这个job对象有没有执行完成了
在这个Job对象被创建后,这个Pod模板,被自动加上了一个Controller-uid=<一个随机字符串>这样的Label,这个Job对象的本身,被自动加上了这个Label对应的Selecotr,保证了匹配关系
当然,这种自动生成的Label对用户来说并不友好,所以不适合推广到Deployment等长作业编排的对象上
然后在结束后,状态变为如下
这样,Pod完成了任务的执行,
而且,离线计算的Pod并不会被重启,不然会重新的计算一遍,这也是为何Pod模板汇总定义restartPolicy=Never的原因
而且,在Job中对象只被允许设置为Never和OnFailure,而在Deployment对象中,restartPolicy只能被设置为Always
然后,根据kubectl logs查看一下Pod的日志,发现已经有计算后的π的值了
当然,由于我们定义了restartPolicy=Never,所以在离线作业失败了之后Job Controller会不断的尝试创建一个新的Pod
当然,为了避免无限制的尝试重新创建,我们设置了重试的次数
backoffLimit: 4
重试创建4次,这四次过程中,也是会呈现指数增加的,即下一次创建的Pod的动作分别在10 20 40S后
如果是restartPolicy=OnFailure,那么离线作业失败了,Job Controller就不会尝试创建新的Pod,但是,也会不断的尝试重启Pod的容器
如果一个Job的Pod运行结束后,会进入Completed状态,但是如果一直不结束怎么办呢?
在Job的API对象中,有一个sepc.activeDeadlineSeconds字段,可以设置最长的运行时间
spec:
backoflimit: 5
activeDeadlineSeconds: 100
一旦超过了100s,这个Job的所有Pod都会被终止,而且Pod的状态中的终止原因是reson:DeadlineExceeded
然后是并行的方式去运行Job
控制并行的方式还是在YAML文件中声明我们相关的参数
spec.parallelism :一个Job在任意时间最多可以启动多少个Pod
spec.completions : 定义的Job最少要完成的Pi数目,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=10000; 4*a(1)’ | bc -l “]
restartPolicy: Never
backoffLimit: 4
然后创建并观察这个Batch Job
DESIRED是定义的最小完成数
首先是Job创建了两个并行运行的Pod来计算pi
然后有一个完成进入Completed时候,就会有一个新的Pod创建出来,进入执行
直到所有的Pod都已经完成退出,这个Job也就执行完成了
在Job Controller中进行调谐操作,就是利用了
Running状态Pod数目,已经成功退出的Pod数目,以及parallelism,completions参数共同计算出这个周期中,应该创建或者删除Pod数目,然后调出Kubernetes API来执行这个操作
上面的例子中,计算Pi值的例子,当Job一开始被创建出来的时候,实际处于Running状态的Pod数目为0,成功退出的为0,定义中,需要完成的Pod数目为4
所以需要创建的Pod = 最终需要完成的Pod – 实际Running – 完成的
4 – 0 – 0 = 4
也就是说需要创建4个Pod来纠正状态
然后,因为定义了Job的parallelism=2
规定了并发创建的Pod个数不能超过2个,所以Job Contraoller会对前面并发创建的Pod个数进行修正,设置为2个
这时候,Job Controller会并发的请求创建两个Pod
那么Job Controller实际上控制了,作业执行的并行度,需要完成的任务数这两个重要参数,在实际使用的时候,来根据作业的特性来决定并行度和任务数的合理取值
接下来,是关于常见的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的时候,将$ITEM替换为我们需要的变量
然后将所有来自同一模板的Job,利用相同的jobgroup:jobexample标签来进行管理
替换的步骤可以如下
$ 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
这样创建出的job名字不同,但是模板一致
这是K8S社区中使用Job的一个普遍模式,这种用法流行的原因,是因为Kubernetes中比较有价值的是,Job本身这个API对象,所以,能启动就可以了
这样,并行设置都可以设置为1,具体启动多少个Job,讲给外部的启动工具来管理
第二种用法,固定任务数目的并行Job
只关心最后是否有指定数目的任务退出,至于执行的并行度,并不关心
利用一个工作队列来进行任务分发,比如,我们定义一个从队列中取任务的Job
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个任务来处理,任务的取出地址就是BROKER_URL
然后会创建8个Pod,每个Pod都会去连接BROKER_URL,从RabbitMQ中读取任务,进行各自的处理
这样,每个Pod将任务信息读取出来,处理完成,然后退出就可以了,这就是任务总数固定的场景
第三种用法,指定并行度,但是不设置固定的completions
就是由自己决定,合适来判断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比较复杂,是需要进行判断工作队列是否为空的,来作为自己是否需要退出的标志
最后,是一个加强的定时版任务模板
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
CronJob从本质上说,是Job对象的控制器,所以CronJob和Job的关系,可以类比Deployment和ReplicaSet的关系,CronJob是一个专门用来管理Job对象的控制器,不过,创建和删除Job的一句,是schedule字段定义的,一个标准的Unix Cron格式的表达式
所以,这个CronJob对象在创建1分钟后,就会有一个Job产生,而且,还会保存下来这次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 不会创建新的Pod,这次执行跳过
concurrencyPolicy=Replace,将由新创建的Pod替换之前的Pod
还有一个默认的配置,一次Job创建失败,这次创建会被标记为了miss,在一定时间内,miss达到了100,就会停止创建这个Job
这个时间窗口,利用的是spec.startingDeadlineSeconds字段,如果设置了200,那么200s内,如果miss到达了100次,就不会再创建Job执行了
本篇文章中,我们分享了Job这个离线业务的编排方法,讲解了completions和parallelism字段的含义,Job Controller的执行原理
分享了Job对象的几种使用方式,还有一种Job的控制器,CronJob
思考题,\
定义的parallelism比completions还大的话
parallelism: 4
completions: 2
会创建几个Pod呢?
首先说
总共需要完成的-已经创建的-已经完成的
2-0-0=2
虽然parallelism定义了4,但是只会多退,不会少补,所以还是2个
Completed是Job在成功执行结束后一直处于的状态,只能考虑手动清理或者使用脚本