Job 和 CronJob 也是kubernetes 的资源对象。
学习了 Kubernetes 的核心对象 Pod,用来编排一个或多个容器,让这些容器共享网络、存储等资源,总是共同调度,从而紧密协同工作。
因为 Pod 比容器更能够表示实际的应用,所以 Kubernetes 不会在容器层面来编排业务,而是把 Pod 作为在集群里调度运维的最小单位。
前面我们也看到了一张 Kubernetes 的资源对象关系图,以 Pod 为中心,延伸出了很多表示各种业务的其他资源对象。那么你会不会有这样的疑问:Pod 的功能已经足够完善了,为什么还要定义这些额外的对象呢?为什么不直接在 Pod 里添加功能,来处理业务需求呢?
现在你应该知道,Kubernetes 使用的是 RESTful API,把集群中的各种业务都抽象为 HTTP 资源对象,那么在这个层次之上,我们就可以使用面向对象的方式来考虑问题。
< RESTful API 把集群中的各种业务都抽象为HTTP 资源对象.>
如果你有一些编程方面的经验,就会知道面向对象编程(OOP),它把一切都视为高内聚的对象,强调对象之间互相通信来完成任务。
虽然面向对象的设计思想多用于软件开发,但它放到 Kubernetes 里却意外地合适。因为 Kubernetes 使用 YAML 来描述资源,把业务简化成了一个个的对象,内部有属性,外部有联系,也需要互相协作,只不过我们不需要编程,完全由 Kubernetes 自动处理(其实 Kubernetes 的 Go 语言内部实现就大量应用了面向对象)。
面向对象的设计有许多基本原则,其中有两条我认为比较恰当地描述了 Kubernetes 对象设计思路,一个是“单一职责”,另一个是“组合优于继承”。
应用这两条原则,我们再来看 Kubernetes 的资源对象就会很清晰了。因为 Pod 已经是一个相对完善的对象,专门负责管理容器,那么我们就不应该再“画蛇添足”地盲目为它扩充功能,而是要保持它的独立性,容器之外的功能就需要定义其他的对象,把 Pod 作为它的一个成员“组合”进去。
这样每种 Kubernetes 对象就可以只关注自己的业务领域,只做自己最擅长的事情,其他的工作交给其他对象来处理,既不“缺位”也不“越位”,既有分工又有协作,从而以最小成本实现最大收益。
 #空一格
 #空两格
<font color='puple'> text </font> #文本添加颜色
现在我们来看看 Kubernetes 里的两种新对象:Job 和 CronJob,它们就组合了 Pod,实现了对离线业务的处理。
我们运行了两个 Pod:Nginx 和 busybox,它们分别代表了 Kubernetes 里的两大类业务。一类是像 Nginx 这样长时间运行的“在线业务”,另一类是像 busybox 这样短时间运行的“离线业务”。
而这些业务特性与容器管理没有必然的联系,如果由 Pod 来实现就会承担不必要的义务,违反了“单一职责”,所以我们应该把这部分功能分离到另外一个对象上实现,让这个对象去控制 Pod 的运行,完成附加的工作。
“离线业务”也可以分为两种。
对应到 Kubernetes 里,“临时任务”就是 API 对象 Job,“定时任务”就是 API 对象 CronJob,使用这两个对象你就能够在 Kubernetes 里调度管理任意的离线业务了。
由于 Job 和 CronJob 都属于离线业务,所以它们也比较相似。我们先学习通常只会运行一次的 Job 对象以及如何操作。如何使用 YAML 描述 Job
Job 的 YAML“文件头”部分还是那几个必备字段,我就不再重复解释了,简单说一下:
如果记不住这些也不要紧,你还可以使用命令 kubectl explain job 来看它的字段说明。
想要生成 YAML 样板文件的话不能使用 kubectl run,因为 kubectl run 只能创建 Pod,要创建 Pod 以外的其他 API 对象,需要使用命令 kubectl create,再加上对象的类型名。
比如用 busybox 创建一个“echo-job”,命令就是这样的
export out="--dry-run=client -o yaml" # 定义Shell变量
kubectl create job echo-job --image=busybox $out
会生成一个基本的 YAML 文件,保存之后做点修改,就有了一个 Job 对象:
apiVersion: batch/v1
kind: Job
metadata:
name: echo-job
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- image: busybox
name: echo-job
imagePullPolicy: IfNotPresent
command: ["/bin/echo"]
args: ["hello", "world"]
你会注意到 Job 的描述与 Pod 很像,但又有些不一样,主要的区别就在“spec”字段里,多了一个 template 字段,然后又是一个“spec”,显得有点怪。
如果你理解了刚才说的面向对象设计思想,就会明白这种做法的道理。它其实就是在 Job 对象里应用了组合模式,template 字段定义了一个“应用模板”,里面嵌入了一个 Pod,这样 Job 就可以从这个模板来创建出 Pod。
而这个 Pod 因为受 Job 的管理控制,不直接和 apiserver 打交道,也就没必要重复 apiVersion 等“头字段”,只需要定义好关键的 spec,描述清楚容器相关的信息就可以了,可以说是一个“无头”的 Pod 对象。
为了辅助理解,我把 Job 对象重新组织了一下,用不同的颜色来区分字段,这样你就能够很容易看出来,其实这个“echo-job”里并没有太多额外的功能,只是把 Pod 做了个简单的包装:
总的来说,这里的 Pod 工作非常简单,在 containers 里写好名字和镜像,command 执行 /bin/echo,输出“hello world”。
不过,因为 Job 业务的特殊性,所以我们还要在 spec 里多加一个字段 restartPolicy,确定 Pod 运行失败时的策略,OnFailure 是失败原地重启容器,而 Never 则是不重启容器,让 Job 去重新调度生成一个新的 Pod。
yaml 文件可参考上图
kubectl apply -f job.yml
kubectl delete -f sleep-job.yml
kubectl get job
kubectl get pod
可以看到,因为 Pod 被 Job 管理,它就不会反复重启报错了,而是会显示为 Completed 表示任务完成,而 Job 里也会列出运行成功的作业数量,这里只有一个作业,所以就是 1/1。
你还可以看到,Pod 被自动关联了一个名字,用的是 Job 的名字(echo-job)再加上一个随机字符串(pb5gh),这当然也是 Job 管理的“功劳”,免去了我们手工定义的麻烦,这样我们就可以使用命令 kubectl logs 来获取 Pod 的运行结果:
其实 Kubernetes 的这套 YAML 描述对象的框架提供了非常多的灵活性,可以在 Job 级别、Pod 级别添加任意的字段来定制业务,这种优势是简单的容器技术无法相比的。
这里我列出几个控制离线作业的重要字段,其他更详细的信息可以参考 Job 文档:
要注意这 4 个字段并不在 template 字段下,而是在 spec 字段下,所以它们是属于 Job 级别的,用来控制模板里的 Pod 对象。
举个栗子:
下面我再创建一个 Job 对象,名字叫“sleep-job”,它随机睡眠一段时间再退出,模拟运行时间较长的作业(比如 MapReduce)。Job 的参数设置成 15 秒超时,最多重试 2 次,总共需要运行完 4 个 Pod,但同一时刻最多并发 2 个 Pod:
apiVersion: batch/v1
kind: Job
metadata:
name: sleep-job
spec:
activeDeadlineSeconds: 15
backoffLimit: 2
completions: 4
parallelism: 2
template:
spec:
restartPolicy: OnFailure
containers:
- image: busybox
name: echo-job
imagePullPolicy: IfNotPresent
command:
- sh
- -c
- sleep $(($RANDOM % 10 + 1)) && echo done
使用 kubectl apply 创建 Job 之后,我们可以用 kubectl get pod -w 来实时观察 Pod 的状态,看到 Pod 不断被排队、创建、运行的过程:
kubectl apply -f sleep-job.yml
kubectl get pod -w
等到 4 个 Pod 都运行完毕,我们再用 kubectl get 来看看 Job 和 Pod 的状态:
就会看到 Job 的完成数量如同我们预期的是 4,而 4 个 Pod 也都是完成状态。
显然,“声明式”的 Job 对象让离线业务的描述变得非常直观,简单的几个字段就可以很好地控制作业的并行度和完成数量,不需要我们去人工监控干预,Kubernetes 把这些都自动化实现了
学习了“临时任务”的 Job 对象之后,再学习“定时任务”的 CronJob 对象也就比较容易了,我就直接使用命令 kubectl create 来创建 CronJob 的样板。
要注意两点。第一,因为 CronJob 的名字有点长,所以 Kubernetes 提供了简写 cj,这个简写也可以使用命令 kubectl api-resources 看到;第二,CronJob 需要定时运行,所以我们在命令行里还需要指定参数 --schedule。
export out="--dry-run=client -o yaml" # 定义Shell变量
kubectl create cj echo-cj --image=busybox --schedule="" $out
然后我们编辑这个 YAML 样板,生成 CronJob 对象:
apiVersion: batch/v1
kind: CronJob
metadata:
name: echo-cj
spec:
schedule: '*/1 * * * *'
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- image: busybox
name: echo-cj
imagePullPolicy: IfNotPresent
command: ["/bin/echo"]
args: ["hello", "world"]
我们还是重点关注它的 spec 字段,你会发现它居然连续有三个 spec 嵌套层次:
所以,CronJob 其实是又组合了 Job 而生成的新对象,我还是画了一张图,方便你理解它的“套娃”结构:
除了定义 Job 对象的“jobTemplate”字段之外,CronJob 还有一个新字段就是“schedule”,用来定义任务周期运行的规则。它使用的是标准的 Cron 语法,指定分钟、小时、天、月、周,和 Linux 上的 crontab 是一样的。像在这里我就指定每分钟运行一次,格式具体的含义你可以课后参考 Kubernetes 官网文档。
除了名字不同,CronJob 和 Job 的用法几乎是一样的,使用 kubectl apply 创建 CronJob,使用 kubectl get cj、kubectl get pod 来查看状态:
kubectl apply -f cronjob.yml
kubectl get cj
kubectl get pod
今天我们以面向对象思想分析了一下 Kubernetes 里的资源对象设计,它强调“职责单一”和“对象组合”,简单来说就是“对象套对象”。
通过这种嵌套方式,Kubernetes 里的这些 API 对象就形成了一个“控制链”:
CronJob 使用定时规则控制 Job,Job 使用并发数量控制 Pod,Pod 再定义参数控制容器,容器再隔离控制进程,进程最终实现业务功能,层层递进的形式有点像设计模式里的 Decorator(装饰模式),链条里的每个环节都各司其职,在 Kubernetes 的统一指挥下完成任务。
小结一下今天的内容:
Kubernetes中组合对象,类似于面向对象编程中的继承,即不破坏父对象的功能,又扩展了自己领域场景中的功能,在API层面也简单了,只需要处理自己扩展的功能即可,比在一个对象上做加法进入逻辑判断要优雅很多。
Job与CronJob分别对应一次性调用的任务与周期性定时任务;
Job与CronJob解决了任务的管理,如执行超时、失败尝试、执行数量与并行数量、任务结果记录等等,方便对任务执行的监控与管理;另外,Pod解决了批处理任务关联打包统一调度,容器解决了任务运行时环境。