通过前面的文章大家已经对 Kubernetes 的控制对象的能力都有所了解了。
Deployment:控制无状态服务应用,例如大多数的Web服务;
StatefulSet:控制有状态服务应用,例如Mysql等有主从节点等拓扑结构的服务;
DaemonSet:控制在每个Node上有且仅运行单个实例的应用,例如日志采集、监控等服务。
这些被控制的对象都有个共同点,都是一直处于Running状态的服务。而在生产实践中也存在着大量的离线应用场景,例如执行一次就退出的脚本,或者是周期性执行的脚本,可以称这类应用为离线业务。Kubernetes 通过Job、CronJob 来实现离线业务应用场景。
首先我么看一个 Job 的定义yaml文件:
apiVersion: batch/v1
kind: Job
metadata:
name: job-example
spec:
template:
spec:
containers:
- name: job-example
image: alpine:latest
command: ["sh", "-c", "echo Job is Running && sleep 10"]
restartPolicy: Never
backoffLimit: 4
activeDeadlineSeconds: 60
parallelism: 2
completions: 4
到现在,大家对 spec.template 部分是非常的熟悉了,是 Kubernetes 的最小调度单位 Pod 的定义。而在示例中我们使用的是一个linux的最小镜像alpine(常作为运行Go应用等无其他依赖的基础镜像),而容器运行起来会输出 Job is Running,同时 sleep 10秒后,容器进程退出。
通过 kubectl get pods 命令可以看到 该 Pod 在Running状态后会变成 Completed 状态:
kubectl get pods
NAME READY STATUS RESTARTS AGE
job-example-th9jc 0/1 Completed 0 107s
在容器进程运行的时候是Running状态,当执行完sleep 10命令后容器进程退出。我们可以查看该 Pod 的日志查看容器输出:
kubectl logs job-example-th9jc
Job is Running
通过 kubectl describe jobs/job-example 命令可以查看Job的详情:
kubectl describe jobs/job-example
Name: job-example
Namespace: default
Selector: controller-uid=f27533fa-3196-492a-af08-2c04c21ba642
Labels: controller-uid=f27533fa-3196-492a-af08-2c04c21ba642
job-name=job-example
Annotations: <none>
Parallelism: 1
Completions: 1
Start Time: Sat, 19 Jun 2021 14:19:12 +0800
Completed At: Sat, 19 Jun 2021 14:19:49 +0800
Duration: 37s
Pods Statuses: 0 Running 1 Succeeded 0 Failed
Pod Template:
Labels: controller-uid=f27533fa-3196-492a-af08-2c04c21ba642
job-name=job-example
Containers:
job-example:
Image: alpine:latest
Port: <none>
Host Port: <none>
Command:
sh
-c
echo Job is Running && sleep 10
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 40s job-controller Created pod: job-example-th9jc
可以看到在 Job 对象被创建后,Pod会被自动加上一个带有uid的label controller-uid=xxxxxx,而当前Job会带有一个Selector来保证当前Job与Pod的关系,避免了不同的Job管理了相同的Pod。
我们再回过头来看看 Job 的定义文件中的字段。
其中 restartPolicy=Never,表示Pod 正常退出后不再重启,实际上Job也不应该在正常退出后被重启,所以 Job 的restartPolicy只能设置为Never或者Onfailure。
而 Never 和 Onfailure 表示在Pod的容器如果是异常退出后的处理不同。Never 表示会不断地重新创建 Pod,Onfailure 则不会创建新的 Pod,只会不停的重启 Pod 里的容器。
但是Never 状态下也不会是无限的重启Pod,默认重试6次,可以通过backoffLimit 来指定重试次数,示例中定义的 backoffLimit 为4。Job重启Pod的时间间隔是指数增长的,10s、20s、40s ......
spec.activeDeadlineSeconds 则是用来设置容器的最长运行时间的。当Job管理的Pod容器运行的脚本逻辑出现异常,一直卡着导致Pod一直无法为Completed状态时,一旦超过了 activeDeadlineSeconds定义的时间,当前Job管理的Pod会被终止,且 Events 中可以看到 Reason 为 DeadlineExceeded。
spec.parallelism控制Job 当前并行运行的Pod数量,spec.completions 控制Job需要完成的Pod数量。在示例中需要执行4次Pod且有2个Pod并行执行。Job管理并行运行Pod数和总数的过程与Pod的滚动升级策略是一样的,详情可以见Kubernetes系列9 一 终于聊到编排了。
但是在我们的生产实践中CronJob是最为常见的,通过名字就能猜想到其与Linux的Cron Job类似,提供周期性执行任务的能力。
我们将上述yaml案例改造为一个每分钟执行一次的Cron Job:
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: cronjob-example
spec:
schedule: "*/1 * * * *"
concurrencyPolicy: Allow
startingDeadlineSeconds: 7200
jobTemplate:
spec:
template:
containers:
- name: cronjob-example
image: alpine:latest
command: ["sh", "-c", "echo Job is Running && sleep 10"]
restartPolicy: Never
backoffLimit: 4
其中JobTemplate.spec.template 正是一个Pod 的定义与Job的 spec.template 对应。说明CronJob同Deployment、StatefulSet、DaemonSet是一类,都是控制器对象。而CronJob直接管理的对象是Job,即Job 的 ownerReference 为CronJob。这与 RelicaSet 同 Deployment 的关系是一样的。
CronJob的 spec.schedule 与linux cron命令语法是一致的,不了解的同学可以自行查阅资料。
我们还需要注意 spec.concurrencyPolicy 定义,定时任务难免会出现上一次任务还未结束,而到达当前任务执行时间的场景。concurrencyPolicy有如下三种类型:
Allow:表示不同周期的任务可以同时存在,这也是concurrency的默认值;
Forbid:表示上一周期的任务未退出则不会创建新的Pod,当前周期会被跳过;
Replacy:表示用新的周期任务代换上一周期任务。
spec.startingDeadlineSeconds 表示在多长滑动窗口时间内,如果Job创建失败的次数达到100(这个100是kubernetes源码里写死的)则不再创建Pod,案例中为2小时内错误数达到100次则不再执行 Job任务。
本系列回顾:
Kubernetes系列3 一 docker隔离与限制的原理
Kubernetes系列10 一 容器编排之StatefulSet
Kubernetes系列11 一 容器编排之DaemonSet