我们学习了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在成功执行结束后一直处于的状态,只能考虑手动清理或者使用脚本

发表评论

邮箱地址不会被公开。 必填项已用*标注