linux cpu占用率如何看
263
2022-09-10
kubernetes job玩转到底
文章目录
1. 简介2. 参数3. job创建过程细讲4. Job Controller 对并行作业的控制方法5. Job Controller 的工作原理6. 三种job方法
6.1 外部管理器 +Job 模板6.2 拥有固定任务数目的并行 Job6.3 指定并行度(parallelism),但不设置固定的 completions 的值
7. CronJob定时8. 实战
8.1 非并发Job8.2 粗并发Job8.3 CronJob8.4. Job的自动清理
1. 简介
Job对象通常用于运行那些仅需要执行一次的任务(例如数据库迁移,批处理脚本等等)。通过Job对象创建运行的Pod具有高可靠性,因为Job Controller会自动重启运行失败的Pod(例如Pod所在Node重启或宕机)。 Job的本质是确保一个或多个Pod健康地运行直至运行完毕。
2. 参数
.spec.completions #需要成功执行的次数.spec.parallelism #并发的数量.spec.template.spec.restartPolicy .spec.template.spec.restartPolicy属性拥有三个候选值:OnFailure,Never和Always。默认值为Always。它主要用于描述Pod内容器的重启策略。在Job中只能将此属性设置为OnFailure或Never。如果.spec.template.spec.restartPolicy = OnFailure,如果Pod内某个容器的exit code不为0,那么Pod就会在其内部重启这个容器。.spec.template.spec.restartPolicy = Never,那么Pod内某个容器exit code不为0时,就不会触发容器的重启.spec.backoffLimit .spec.backoffLimit用于设置Job的容错次数,默认值为6。当Job运行的Pod失败次数到达.spec.backoffLimit次时,Job Controller不再新建Pod,直接停止运行这个Job,将其运行结果标记为Failure。另外,Pod运行失败后再次运行的时间间隔呈递增状态,例如10s,20s,40s。。。.spec.activeDeadlineSeconds .spec.activeDeadlineSeconds属性用于设置Job运行的超时时间。如果Job运行的时间超过了设定的秒数,那么此Job就自动停止运行所有的Pod,并将Job退出状态标记为reason:DeadlineExceeded。ttlSecondsAfterFinished 1.12版本之后,k8s提出了通过TTL自动删除Job的特性,当前仅对job生效,对 Complete 和 Failed 状态的Job都会自动删除,以后会逐步对所有的其他资源对象生效。 Job pi-with-ttl 的 ttlSecondsAfterFinished 值为 100,则,在其结束 100 秒之后,将可以被自动删除 如果 ttlSecondsAfterFinished 被设置为 0,则 TTL 控制器在 Job 执行结束后,立刻就可以清理该 Job 及其 Pod 如果 ttlSecondsAfterFinished 值未设置,则 TTL 控制器不会清理该 Job
截止日期 该.spec.startingDeadlineSeconds字段是可选的。如果它由于任何原因错过了计划的时间,则表示开始工作的最后期限(以秒为单位)。截止日期之后,cron作业不会开始作业。未能按时完成任务的作业将计为失败的作业。如果未指定此字段,则作业没有截止日期。 CronJob控制器计算出cron作业错过了多少时间表。如果错过了100个以上的计划,则不再计划cron作业。如果.spec.startingDeadlineSeconds未设置,则CronJob控制器将从status.lastScheduleTime现在开始计数错过的日程表。 并发策略 该.spec.concurrencyPolicy字段也是可选的。它指定如何处理由该cron作业创建的作业的并发执行。该规范可能仅指定以下并发策略之一:
Allow (默认):cron作业允许同时运行的作业Forbid:cron作业不允许并发运行;如果是时候开始新的作业并且之前的作业尚未完成,则cron作业会跳过新的作业Replace:如果是时候开始新的作业并且之前的作业尚未完成,则cron作业将用新的作业替换当前正在运行的作业
3. job创建过程细讲
apiVersion: batch/v1kind: Jobmetadata: name: pispec: template: spec: containers: - name: pi image: resouer/ubuntu-bc command: ["sh", "-c", "echo 'scale=10000; 4*a(1)' | bc -l "] restartPolicy: Never backoffLimit: 4 #容错4次$ kubectl create -f job.yaml
bc 命令是 Linux 里的“计算器”;-l 表示,我现在要使用标准数学库;而 a(1),则是调用数学库中的 arctangent 函数,计算 atan(1)。这是什么意思呢?中学知识告诉我们:tan(π/4) = 1。所以,4*atan(1)正好就是π,也就是 3.1415926…。这其实就是一个计算π值的容器。而通过 scale=10000,我指定了输出的小数点后的位数是 10000。在我的计算机上,这个计算大概用时 1 分 54 秒。
但是,跟其他控制器不同的是,Job 对象并不要求你定义一个 spec.selector 来描述要控制哪些 Pod
$ kubectl describe jobs/piName: piNamespace: defaultSelector: controller-uid=c2db599a-2c9d-11e6-b324-0209dc45a495Labels: controller-uid=c2db599a-2c9d-11e6-b324-0209dc45a495 job-name=piAnnotations: Parallelism: 1Completions: 1..Pods Statuses: 0 Running / 1 Succeeded / 0 FailedPod Template: Labels: controller-uid=c2db599a-2c9d-11e6-b324-0209dc45a495 job-name=pi Containers: ... Volumes: Events: FirstSeen LastSeen Count From SubobjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: pi-rq5rl
可以看到,这个 Job 对象在创建后,它的 Pod 模板,被自动加上了一个 controller-uid=< 一个随机字符串 > 这样的 Label。而这个 Job 对象本身,则被自动加上了这个 Label 对应的 Selector,从而 保证了 Job 与它所管理的 Pod 之间的匹配关系。
而 Job Controller 之所以要使用这种携带了 UID 的 Label,就是为了避免不同 Job 对象所管理的 Pod 发生重合。需要注意的是,这种自动生成的 Label 对用户来说并不友好,所以不太适合推广到 Deployment 等长作业编排对象上。
Pod 进入了 Running 状态
$ kubectl get podsNAME READY STATUS RESTARTS AGEpi-rq5rl 1/1 Running 0 10s
而几分钟后计算结束,这个 Pod 就会进入 Completed 状态:
$ kubectl get podsNAME READY STATUS RESTARTS AGEpi-rq5rl 0/1 Completed 0 4m
这也是我们需要在 Pod 模板中定义 restartPolicy=Never 的原因:离线计算的 Pod 永远都不应该被重启,否则它们会再重新计算一遍。事实上,restartPolicy 在 Job 对象里只允许被设置为 Never 和 OnFailure;而在 Deployment 对象里,restartPolicy 则只允许被设置为 Always。
此时,我们通过 kubectl logs 查看一下这个 Pod 的日志,就可以看到计算得到的 Pi 值已经被打印了出来:
$ kubectl logs pi-rq5rl3.141592653589793238462643383279...
这时候,你一定会想到这样一个问题,如果这个离线作业失败了要怎么办?比如,我们在这个例子中定义了 restartPolicy=Never,那么离线作业失败后 Job Controller 就会不断地尝试创建一个新 Pod,如下所示:
$ kubectl get podsNAME READY STATUS RESTARTS AGEpi-55h89 0/1 ContainerCreating 0 2spi-tqbcz 0/1 Error 0 5s
Job 对象的 spec.backoffLimit 字段里定义了重试次数为 4(即,backoffLimit=4),而这个字段的默认值是 6。
需要注意的是,Job Controller 重新创建 Pod 的间隔是呈指数增加的,即下一次重新创建 Pod 的动作会分别发生在 10 s、20 s、40 s …后,而如果你定义的 restartPolicy=OnFailure,那么离线作业失败后,Job Controller 就不会去尝试创建新的 Pod。但是,它会不断地尝试重启 Pod 里的容器。这也正好对应了 restartPolicy 的含义。
如前所述,当一个 Job 的 Pod 运行结束后,它会进入 Completed 状态。但是,如果这个 Pod 因为某种原因一直不肯结束呢?在 Job 的 API 对象里,有一个 spec.activeDeadlineSeconds 字段可以设置最长运行时间,比如:
spec: backoffLimit: 5 activeDeadlineSeconds: 100
一旦运行超过了 100 s,这个 Job 的所有 Pod 都会被终止。并且,你可以在 Pod 的状态里看到终止的原因是 reason: DeadlineExceeded。以上,就是一个 Job API 对象最主要的概念和用法了。不过,离线业务之所以被称为 Batch Job,当然是因为它们可以以“Batch”,也就是并行的方式去运行。
4. Job Controller 对并行作业的控制方法
在 Job 对象中,负责并行控制的参数有两个:
spec.parallelism,它定义的是一个 Job 在任意时间最多可以启动多少个 Pod 同时运行;spec.completions,它定义的是 Job 至少要完成的 Pod 数目,即 Job 的最小完成数。
apiVersion: batch/v1kind: Jobmetadata: name: pispec:#这个 Job 最大的并行数是 2,而最小的完成数是 4 parallelism: 2 completions: 4 template: spec: containers: - name: pi image: resouer/ubuntu-bc command: ["sh", "-c", "echo 'scale=5000; 4*a(1)' | bc -l "] restartPolicy: Never backoffLimit: 4$ kubectl create -f job.yaml#两个状态字段,即 DESIRED 和 SUCCESSFUL#DESIRED 的值,正是 completions 定义的最小完成数。$ kubectl get jobNAME DESIRED SUCCESSFUL AGEpi 4 0 3s$ kubectl get podsNAME READY STATUS RESTARTS AGEpi-5mt88 1/1 Running 0 6spi-gmcq5 1/1 Running 0 6s
40 s 后,这两个 Pod 相继完成计算。这时我们可以看到,每当有一个 Pod 完成计算进入 Completed 状态时,就会有一个新的 Pod 被自动创建出来,并且快速地从 Pending 状态进入到 ContainerCreating 状态:
$ kubectl get podsNAME READY STATUS RESTARTS AGEpi-gmcq5 0/1 Completed 0 40spi-84ww8 0/1 Pending 0 0spi-5mt88 0/1 Completed 0 41spi-62rbt 0/1 Pending 0 0s$ kubectl get podsNAME READY STATUS RESTARTS AGEpi-gmcq5 0/1 Completed 0 40spi-84ww8 0/1 ContainerCreating 0 0spi-5mt88 0/1 Completed 0 41spi-62rbt 0/1 ContainerCreating 0 0s
紧接着,Job Controller 第二次创建出来的两个并行的 Pod 也进入了 Running 状态:
$ kubectl get pods NAME READY STATUS RESTARTS AGEpi-5mt88 0/1 Completed 0 54spi-62rbt 1/1 Running 0 13spi-84ww8 1/1 Running 0 14spi-gmcq5 0/1 Completed 0 54s
终,后面创建的这两个 Pod 也完成了计算,进入了 Completed 状态。这时,由于所有的 Pod 均已经成功退出,这个 Job 也就执行完了,所以你会看到它的 SUCCESSFUL 字段的值变成了 4:
$ kubectl get pods NAME READY STATUS RESTARTS AGEpi-5mt88 0/1 Completed 0 5mpi-62rbt 0/1 Completed 0 4mpi-84ww8 0/1 Completed 0 4mpi-gmcq5 0/1 Completed 0 5m$ kubectl get jobNAME DESIRED SUCCESSFUL AGEpi 4 4 5m
5. Job Controller 的工作原理
首先,Job Controller 控制的对象,直接就是 Pod。其次,Job Controller 在控制循环中进行的调谐(Reconcile)操作,是根据实际在 Running 状态 Pod 的数目、已经成功退出的 Pod 的数目,以及parallelism、completions参数的值共同计算出在这个周期里,应该创建或者删除的 Pod 数目,然后调用 Kubernetes API 来执行这个操作。
以创建 Pod 为例。在上面计算 Pi 值的这个例子中,当 Job 一开始创建出来时,实际处于 Running 状态的 Pod 数目 =0,已经成功退出的 Pod 数目 =0,而用户定义的 completions,也就是最终用户需要的 Pod 数目 =4。
所以,在这个时刻,需要创建的 Pod 数目 = 最终需要的 Pod 数目 - 实际在 Running 状态 Pod 数目 - 已经成功退出的 Pod 数目 = 4 - 0 - 0= 4。也就是说,Job Controller 需要创建 4 个 Pod 来纠正这个不一致状态。
可是,我们又定义了这个 Job 的 parallelism=2。也就是说,我们规定了每次并发创建的 Pod 个数不能超过 2 个。所以,Job Controller 会对前面的计算结果做一个修正,修正后的期望创建的 Pod 数目应该是:2 个。
这时候,Job Controller 就会并发地向 kube-apiserver 发起两个创建 Pod 的请求。类似地,如果在这次调谐周期里,Job Controller 发现实际在 Running 状态的 Pod 数目,比 parallelism 还大,那么它就会删除一些 Pod,使两者相等。综上所述,Job Controller 实际上控制了,作业执行的并行度,以及总共需要完成的任务数这两个重要参数。而在实际使用时,你需要根据作业的特性,来决定并行度(parallelism)和任务数(completions)的合理取值。
6. 三种job方法
6.1 外部管理器 +Job 模板
也是最简单粗暴的用法,这种模式的特定用法是:把 Job 的 YAML 文件定义为一个“模板”,然后用一个外部工具控制这些“模板”来生成 Job。这时,Job 的定义方式如下所示:
apiVersion: batch/v1kind: Jobmetadata: name: process-item-$ITEM labels: jobgroup: jobexamplespec: 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 时,我们只要注意如下两个方面即可:
创建 Job 时,替换掉$ITEM 这样的变量;所有来自于同一个模板的 Job,都有一个jobgroup: jobexample 标签,也就是说这一组 Job使用这样一个相同的标识。
而做到第一点非常简单。比如,你可以通过这样一句 shell 把 $ITEM 替换掉:
$ mkdir ./jobs$ for i in apple banana cherrydo cat job-tmpl.yaml | sed "s/\$ITEM/$i/" > ./jobs/job-$i.yamldone
这样,一组来自于同一个模板的不同 Job 的 yaml 就生成了。接下来,你就可以通过一句 kubectl create 指令创建这些 Job 了:
$ kubectl create -f ./jobs$ kubectl get pods -l jobgroup=jobexampleNAME READY STATUS RESTARTS AGEprocess-item-apple-kixwv 0/1 Completed 0 4mprocess-item-banana-wrsf7 0/1 Completed 0 4mprocess-item-cherry-dnfu9 0/1 Completed 0 4m
6.2 拥有固定任务数目的并行 Job
这种模式下,我只关心最后是否有指定数目(spec.completions)个任务成功退出。至于执行时的并行度是多少,我并不关心。比如,我们这个计算 Pi 值的例子,就是这样一个典型的、拥有固定任务数目(completions=4)的应用场景。 它的 parallelism 值是 2;或者,你可以干脆不指定 parallelism,直接使用默认的并行度(即:1)。此外,你还可以使用一个工作队列(Work Queue)进行任务分发。这时,Job 的 YAML 文件定义如下所示:
apiVersion: batch/v1kind: Jobmetadata: name: job-wq-1spec: 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
/* job-wq-1的伪代码 */queue := newQueue($BROKER_URL, $QUEUE)task := queue.Pop()process(task)exit
6.3 指定并行度(parallelism),但不设置固定的 completions 的值
此时,你就必须自己想办法,来决定什么时候启动新 Pod,什么时候 Job 才算执行完成。在这种情况下,任务的总数是未知的,所以你不仅需要一个工作队列来负责任务分发,还需要能够判断工作队列已经为空(即:所有的工作已经结束了)。这时候,Job 的定义基本上没变化,只不过是不再需要定义 completions 的值了而已
apiVersion: batch/v1kind: Jobmetadata: name: job-wq-2spec: 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 的逻辑会稍微复杂一些,我可以用这样一段伪代码来描述:
/* job-wq-2的伪代码 */for !queue.IsEmpty($BROKER_URL, $QUEUE) { task := queue.Pop() process(task)}print("Queue empty, exiting")exit
7. CronJob定时
apiVersion: batch/v1beta1kind: CronJobmetadata: name: hellospec: 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 对象的控制器(Controller) CronJob 与 Job 的关系,正如同 Deployment 与 ReplicaSet 的关系一样。CronJob 是一个专门用来管理 Job 对象的控制器。只不过,它创建和删除 Job 的依据,是 schedule 字段定义的、一个标准的Unix Cron格式的表达式。比如,"*/1 * * * *"。这个 Cron 表达式里 */1 中的 * 表示从 0 开始,/ 表示“每”,1 表示偏移量。所以,它的意思就是:从 0 开始,每 1 个时间单位执行一次。
Cron 表达式中的五个部分分别代表:分钟、小时、日、月、星期
$ kubectl create -f ./cronjob.yamlcronjob "hello" created# 一分钟后$ kubectl get jobsNAME DESIRED SUCCESSFUL AGEhello-4111706356 1 1 2s$ kubectl get cronjob helloNAME SCHEDULE SUSPEND ACTIVE LAST-SCHEDULEhello */1 * * * * False 0 Thu, 6 Sep 2018 14:34:00 -070
需要注意的是,由于定时任务的特殊性,很可能某个 Job 还没有执行完,另外一个新 Job 就产生了。这时候,你可以通过 spec.concurrencyPolicy 字段来定义具体的处理策略。比如:
concurrencyPolicy=Allow,这也是默认情况,这意味着这些 Job 可以同时存在;concurrencyPolicy=Forbid,这意味着不会创建新的 Pod,该创建周期被跳过;concurrencyPolicy=Replace,这意味着新产生的 Job 会替换旧的、没有执行完的 Job。
而如果某一次 Job 创建失败,这次创建就会被标记为“miss”。当在指定的时间窗口内,miss 的数目达到 100 时,那么 CronJob 会停止再创建这个 Job。这个时间窗口,可以由 spec.startingDeadlineSeconds 字段指定。比如 startingDeadlineSeconds=200,意味着在过去 200 s 里,如果 miss 的数目达到了 100 次,那么这个 Job 就不会被创建执行了。
8. 实战
8.1 非并发Job
非并发Job的含义是,Job启动后,只运行一个pod,pod运行结束后整个Job也就立刻结束。 以下是简单的Job配置文件,只包含一个pod,输出圆周率小数点后2000位,运行时间大概为10s:
apiVersion: batch/v1kind: Jobmetadata: name: pispec: template: spec: containers: - name: pi image: perl command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] restartPolicy: Never backoffLimit: 4
以上示例无需设置选择器、pod标签。无需设置.spec.completions、.spec.parallelism,这两个字段的默认值都是1。backoffLimit=4,表示允许pod失败的次数。将以上内容保存成文件并创建Job:
$ kubectl create -f "pi" created
8.2 粗并发Job
本例创建一个Job,但Job要创建多个pod。了解完示例后就明白为什么叫“粗并发”。 本示例需要一个消息队列服务的配合,不详细描述如何部署、填充消息队列服务。假设我们有一个RabbitMQ服务,集群内访问地址为:amqp://guest:guest@rabbitmq-service:5672。其有一个名为job1的队列,队列内有apple banana cherry date fig grape lemon melon共8个成员。 另外假设我们有一个名为gcr.io//job-wq-1的image,其功能是从队列中读取出一个元素并打印到标准输出,然后结束。注意,它只处理一个元素就结束了。接下来创建如下Job:
apiVersion: batch/v1kind: Jobmetadata: name: job-wq-1spec: completions: 8 parallelism: 2 template: metadata: name: job-wq-1 spec: containers: - name: c image: gcr.io//job-wq-1 env: - name: BROKER_URL value: amqp://guest:guest@rabbitmq-service:5672 - name: QUEUE value: job1 restartPolicy: OnFailure
上例中,completions的值为8,等于job1队列中元素的个数。因为每个成功的pod处理一个元素,所以需要成功8次,job1中的所有成员就会被处理完成。在粗并发模式下,completions的值必需指定,否则其默认值为1,整个Job只处理一个成员就结束了。
上例中,parallelism的值是2。虽然需要pod成功8次,但在同一时间,只允许有两个pod并发。一个成功结束后,再启动另一个。这个参数的主要目的是控制并发pod的个数,可根据实际情况调整。当然可以不指定,那么默认的并发个数就是1。 env中的内容告诉image如何访问队列。
8.3 CronJob
apiVersion: batch/v1beta1kind: CronJobmetadata: name: hellospec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: containers: - name: hello image: busybox args: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: OnFailure
kubectl create -f get cronjob hellokubectl logs $podskubectl delete cronjob hello
命令行执行cronjob
kubectl run hello --schedule="*/1 * * * *" --restart=OnFailure --image=busybox -- /bin/sh -c "date; echo Hello from the Kubernetes cluster"
8.4. Job的自动清理
apiVersion: batch/v1kind: Jobmetadata: name: pi-with-ttlspec: ttlSecondsAfterFinished: 100 template: spec: containers: - name: pi image: perl command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] restartPolicy: Never
参考资料:https://v1-15.docs.kubernetes.io/zh/docs/concepts/workloads/controllers/cron-jobs/
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~