learn-tech/专栏/Kubernetes入门实战课/13Job_CronJob:为什么不直接用Pod来处理业务?.md
2024-10-16 06:37:41 +08:00

14 KiB
Raw Blame History

                        因收到Google相关通知网站将会择期关闭。相关通知内容
                        
                        
                        13 Job_CronJob为什么不直接用Pod来处理业务
                        你好我是Chrono。

在上次的课里我们学习了Kubernetes的核心对象Pod用来编排一个或多个容器让这些容器共享网络、存储等资源总是共同调度从而紧密协同工作。

因为Pod比容器更能够表示实际的应用所以Kubernetes不会在容器层面来编排业务而是把Pod作为在集群里调度运维的最小单位。

前面我们也看到了一张Kubernetes的资源对象关系图以Pod为中心延伸出了很多表示各种业务的其他资源对象。那么你会不会有这样的疑问Pod的功能已经足够完善了为什么还要定义这些额外的对象呢为什么不直接在Pod里添加功能来处理业务需求呢

这个问题体现了Google对大规模计算集群管理的深度思考今天我就说说Kubernetes基于Pod的设计理念先从最简单的两种对象——Job和CronJob讲起。

为什么不直接使用Pod

现在你应该知道Kubernetes使用的是RESTful API把集群中的各种业务都抽象为HTTP资源对象那么在这个层次之上我们就可以使用面向对象的方式来考虑问题。

如果你有一些编程方面的经验就会知道面向对象编程OOP它把一切都视为高内聚的对象强调对象之间互相通信来完成任务。

虽然面向对象的设计思想多用于软件开发但它放到Kubernetes里却意外地合适。因为Kubernetes使用YAML来描述资源把业务简化成了一个个的对象内部有属性外部有联系也需要互相协作只不过我们不需要编程完全由Kubernetes自动处理其实Kubernetes的Go语言内部实现就大量应用了面向对象

面向对象的设计有许多基本原则其中有两条我认为比较恰当地描述了Kubernetes对象设计思路一个是“单一职责”另一个是“组合优于继承”。

“单一职责”的意思是对象应该只专注于做好一件事情,不要贪大求全,保持足够小的粒度才更方便复用和管理。

“组合优于继承”的意思是应该尽量让对象在运行时产生联系,保持松耦合,而不要用硬编码的方式固定对象的关系。

应用这两条原则我们再来看Kubernetes的资源对象就会很清晰了。因为Pod已经是一个相对完善的对象专门负责管理容器那么我们就不应该再“画蛇添足”地盲目为它扩充功能而是要保持它的独立性容器之外的功能就需要定义其他的对象把Pod作为它的一个成员“组合”进去。

这样每种Kubernetes对象就可以只关注自己的业务领域只做自己最擅长的事情其他的工作交给其他对象来处理既不“缺位”也不“越位”既有分工又有协作从而以最小成本实现最大收益。

为什么要有Job/CronJob

现在我们来看看Kubernetes里的两种新对象Job和CronJob它们就组合了Pod实现了对离线业务的处理。

上次课讲Pod的时候我们运行了两个PodNginx和busybox它们分别代表了Kubernetes里的两大类业务。一类是像Nginx这样长时间运行的“在线业务”另一类是像busybox这样短时间运行的“离线业务”。

“在线业务”类型的应用有很多比如Nginx、Node.js、MySQL、Redis等等一旦运行起来基本上不会停也就是永远在线。

而“离线业务”类型的应用也并不少见,它们一般不直接服务于外部用户,只对内部用户有意义,比如日志分析、数据建模、视频转码等等,虽然计算量很大,但只会运行一段时间。“离线业务”的特点是必定会退出,不会无期限地运行下去,所以它的调度策略也就与“在线业务”存在很大的不同,需要考虑运行超时、状态检查、失败重试、获取计算结果等管理事项。

而这些业务特性与容器管理没有必然的联系如果由Pod来实现就会承担不必要的义务违反了“单一职责”所以我们应该把这部分功能分离到另外一个对象上实现让这个对象去控制Pod的运行完成附加的工作。

“离线业务”也可以分为两种。一种是“临时任务”,跑完就完事了,下次有需求了说一声再重新安排;另一种是“定时任务”,可以按时按点周期运行,不需要过多干预。

对应到Kubernetes里“临时任务”就是API对象Job“定时任务”就是API对象CronJob使用这两个对象你就能够在Kubernetes里调度管理任意的离线业务了。

由于Job和CronJob都属于离线业务所以它们也比较相似。我们先学习通常只会运行一次的Job对象以及如何操作。

如何使用YAML描述Job

Job的YAML“文件头”部分还是那几个必备字段我就不再重复解释了简单说一下

apiVersion不是 v1而是 batch/v1。 kind是 Job这个和对象的名字是一致的。 metadata里仍然要有 name 标记名字,也可以用 labels 添加任意的标签。

如果记不住这些也不要紧,你还可以使用命令 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。

如何在Kubernetes里操作Job

现在让我们来创建Job对象运行这个简单的离线作业用的命令还是 kubectl apply

kubectl apply -f job.yml

创建之后Kubernetes就会从YAML的模板定义中提取Pod在Job的控制下运行Pod你可以用 kubectl get job、kubectl get pod 来分别查看Job和Pod的状态

kubectl get job kubectl get pod

可以看到因为Pod被Job管理它就不会反复重启报错了而是会显示为 Completed 表示任务完成而Job里也会列出运行成功的作业数量这里只有一个作业所以就是 1/1。

你还可以看到Pod被自动关联了一个名字用的是Job的名字echo-job再加上一个随机字符串pb5gh这当然也是Job管理的“功劳”免去了我们手工定义的麻烦这样我们就可以使用命令 kubectl logs 来获取Pod的运行结果

到这里你可能会觉得经过了Job、Pod对容器的两次封装虽然从概念上很清晰但好像并没有带来什么实际的好处和直接跑容器也差不了多少。

其实Kubernetes的这套YAML描述对象的框架提供了非常多的灵活性可以在Job级别、Pod级别添加任意的字段来定制业务这种优势是简单的容器技术无法相比的。

这里我列出几个控制离线作业的重要字段其他更详细的信息可以参考Job文档

activeDeadlineSeconds设置Pod运行的超时时间。 backoffLimit设置Pod的失败重试次数。 completionsJob完成需要运行多少个Pod默认是1个。 parallelism它与completions相关表示允许并发运行的Pod数量避免过多占用资源。

要注意这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把这些都自动化实现了。

如何使用YAML描述CronJob

学习了“临时任务”的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 嵌套层次:

第一个 spec 是CronJob自己的对象规格声明 第二个 spec 从属于“jobTemplate”它定义了一个Job对象。 第三个 spec 从属于“template”它定义了Job里运行的Pod。

所以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使用定时规则控制JobJob使用并发数量控制PodPod再定义参数控制容器容器再隔离控制进程进程最终实现业务功能层层递进的形式有点像设计模式里的Decorator装饰模式链条里的每个环节都各司其职在Kubernetes的统一指挥下完成任务。

小结一下今天的内容:

Pod是Kubernetes的最小调度单元但为了保持它的独立性不应该向它添加多余的功能。 Kubernetes为离线业务提供了Job和CronJob两种API对象分别处理“临时任务”和“定时任务”。 Job的关键字段是 spec.template里面定义了用来运行业务的Pod模板其他的重要字段有 completions、parallelism 等 CronJob的关键字段是 spec.jobTemplate 和 spec.schedule分别定义了Job模板和定时运行的规则。

课下作业

最后是课下作业时间,给你留两个思考题:

你是怎么理解Kubernetes组合对象的方式的它带来了什么好处 Job和CronJob的具体应用场景有哪些能够解决什么样的问题

欢迎在留言区分享你的疑问和学习心得,如果觉得有收获,也欢迎你分享给朋友一起学习。

下节课见。