Job
-
https://kuboard.cn/learning/k8s-intermediate/workload/wl-job/
-
https://kubernetes.io/docs/concepts/workloads/controllers/job/
Kubernetes中的 Job 对象将创建一个或多个 Pod,并确保指定数量的 Pod 可以成功执行到进程正常结束:
- 当 Job 创建的 Pod 执行成功并正常结束时,Job 将记录成功结束的 Pod 数量
- 当成功结束的 Pod 达到指定的数量时,Job 将完成执行
- 删除 Job 对象时,将清理掉由 Job 创建的 Pod
例子
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
backoffLimit: 4
执行结果可以通过 kubectl logs <pod-name>
查看。
1. 创建 Job⚓
1.1 Pod Template⚓
指定合适的重启策略 restartPolicy .spec.template.spec.restartPolicy
,此处只允许使用 Never
和 OnFailure
两个取值。
1.2 Parallel Jobs⚓
有三种主要的任务类型适合使用 Job 运行:
- Non-parallel Jobs
- 通常,只启动一个 Pod,除非该 Pod 执行失败
- Pod 执行成功并结束以后,Job 也立刻进入完成 completed 状态
- Parallel Jobs with a fixed completion count
.spec.completions
为一个非零正整数- Job 将创建至少
.spec.completions
个 Pod,编号为 1 -.spec.completions
(尚未实现) - Job 记录了任务的整体执行情况,当 1 -
.spec.completions
中每一个编号都有一个对应的 Pod 执行成功时,Job 进入完成状态
- Parallel Jobs with a work queue
- 不指定
.spec.completions
,使用.spec.parallelism
- Pod 之间必须相互之间自行协调并发,或者使用一个外部服务决定每个 Pod 各自执行哪些任务。例如,一个 Pod 可能会从工作队列中获取多达 n 个项目的批处理。
- 每个 Pod 都可以独立判断其他同僚(peers)是否完成,并确定整个Job是否完成
- 当 Job 中任何一个 Pod 成功结束,将不再为其创建新的 Pod
- 当所有的 Pod 都结束了,且至少有一个 Pod 执行成功后才结束,则 Job 判定为成功结束
- 一旦任何一个 Pod 执行成功并退出,Job 中的任何其他 Pod 都应停止工作和输出信息,并开始终止该 Pod 的进程
- 不指定
completions 和 parallelism
- 对于 non-parallel Job,
.spec.completions
和.spec.parallelism
可以不填写,默认值都为 1 - 对于 fixed completion count Job,需要设置
.spec.completions
为您期望的个数;同时不设置.spec.parallelism
字段(默认值为 1) - 对于 work queue Job,不能设置
.spec.completions
字段,且必须设置.spec.parallelism
为0或任何正整数
1.3 Controlling Parallelism 并发控制⚓
并发数 .spec.parallelism
可以被设置为0或者任何正整数,如果不设置,默认为1,如果设置为 0,则 Job 被暂停,直到该数字被调整为一个正整数。
实际的并发数(同一时刻正在运行的 Pod 数量)可能比设定的并发数 .spec.parallelism
要大一些或小一些,不一定严格相等,主要的原因有:
- 对于 fixed completion count Job,实际并发运行的 Pod 数量不会超过剩余未完成的数量。如果
.spec.parallelism
比这个数字更大,将被忽略 - 对于 work queue Job,任何一个 Pod 成功执行后,将不再创建新的 Pod (剩余的 Pod 将继续执行)
- Job 控制器可能没有足够的时间处理并发控制
- 如果 Job 控制器创建 Pod 失败(例如,ResourceQuota 不够用,没有足够的权限等)
- 同一个Job中,在已创建的 Pod 出现大量失败的情况下,Job 控制器可能限制 Pod 的创建
- 当 Pod 被优雅地关闭时(gracefully shut down),需要等候一段时间才能结束
2. 处理Pod和容器的失败⚓
Pod 中的容器可能会因为多种原因执行失败,例如:
- 容器中的进程退出了,且退出码(exit code)不为 0
- 容器因为超出内存限制而被 Kill
- 其他原因
如果 Pod 中的容器执行失败,且 .spec.template.spec.restartPolicy = "OnFailure"
,则 Pod 将停留在该节点上,但是容器将被重新执行。此时,您的应用程序需要处理在原节点(失败之前的节点)上重启的情况。或者,您也可以设置为 .spec.template.spec.restartPolicy = "Never"
。
整个 Pod 也可能因为多种原因执行失败,例如:
- Pod 从节点上被驱逐(节点升级、重启、被删除等)
- Pod 的容器执行失败,且
.spec.template.spec.restartPolicy = "Never"
当 Pod 执行失败时,Job 控制器将创建一个新的 Pod。此时,您的应用程序需要处理在一个新 Pod 中重新启动的情况。具体来说,需要处理临时文件、锁、未完成的输出信息以及前一次执行可能遗留下来的其他东西。
Warning
- 即使您指定
.spec.parallelism = 1
、.spec.completions = 1
以及.spec.template.spec.restartPolicy = "Never"
,同一个应用程序仍然可能被启动多次 - 如果指定
.spec.parallelism
和.spec.completions
的值都大于 1,则,将可能有多个 Pod 同时执行。此时,您的 Pod 还必须能够处理并发的情况
2.1 Pod 失败重试策略⚓
某些情况下(例如,配置错误),您可能期望在 Job 多次重试仍然失败的情况下停止该 Job。此时,可通过 .spec.backoffLimit
来设定 Job 最大的重试次数。该字段的默认值为 6。
Job 中的 Pod 执行失败之后,Job 控制器将按照一个指数增大的时间延迟(10s,20s,40s ... 最大为 6 分钟)来多次重新创建 Pod。如果没有新的 Pod 执行失败,则重试次数的计数将被重置。
建议在 debug 时,设置 restartPolicy = "Never"
,或者使用日志系统确保失败的 Job 的日志不会丢失。
3. Job的终止和清理⚓
当 Job 完成后:
- 将不会创建新的 Pod
- 已经创建的 Pod 也不会被清理掉。此时,您仍然可以继续查看已结束 Pod 的日志,以检查 errors/warnings 或者其他诊断用的日志输出
- Job 对象也仍然保留着,以便您可以查看该 Job 的状态
- 由用户决定是否删除已完成的 Job 及其 Pod
- 可通过
kubectl
命令删除 Job,例如:kubectl delete jobs/pi
或者kubectl delete -f https://kuboard.cn/statics/learning/job/job.yaml
- 删除 Job 对象时,由该 Job 创建的 Pod 也将一并被删除
- 可通过
Job 通常会顺利的执行下去,但是在如下情况可能会非正常终止:
- 某一个 Pod 执行失败(且
restartPolicy=Never
) - 或者某个容器执行出错(且
restartPolicy=OnFailure
)- 此时,Job 按照上节《Pod 失败重试策略》
.spec.bakcoffLimit
描述的方式进行处理 - 一旦重试次数达到了
.spec.backoffLimit
中的值,Job 将被标记为失败,且尤其创建的所有 Pod 将被终止
- 此时,Job 按照上节《Pod 失败重试策略》
- Job 中设置了
.spec.activeDeadlineSeconds
。该字段限定了 Job 对象在集群中的存活时长,一旦达到.spec.activeDeadlineSeconds
(比.spec.backoffLimit
的优先级高)指定的时长,该 Job 创建的所有的 Pod 都将被终止,Job 的 Status 将变为type:Failed
、reason: DeadlineExceeded
。
4. Job的自动清理⚓
系统中已经完成的 Job 通常是不在需要里的,长期在系统中保留这些对象,将给 apiserver 带来很大的压力。如果通过更高级别的控制器(例如 CronJobs)来管理 Job,则 CronJob 可以根据其中定义的基于容量的清理策略(capacity-based cleanup policy)自动清理Job。
4.1 TTL 机制⚓
FEATURE STATE: Kubernetes v1.12 [alpha]
除了 CronJob 之外,TTL 机制是另外一种自动清理已结束Job(Completed
或 Finished
)的方式:
- TTL 机制由 TTL 控制器 提供
-
在 Job 对象中指定
.spec.ttlSecondsAfterFinished
字段可激活该特性 -
.spec.ttlSecondsAfterFinished
值为 100,则,在其结束100
秒之后,将可以被自动删除 - 如果
.spec.ttlSecondsAfterFinished
被设置为0
,则 TTL 控制器在 Job 执行结束后,立刻就可以清理该 Job 及其 Pod - 如果
.spec.ttlSecondsAfterFinished
值未设置,则 TTL 控制器不会清理该 Job
5. Job 模式⚓
Kubernetes Job 对象可以用来支持 Pod 的并发执行,但是:
- Job 对象并非设计为支持需要紧密相互通信的Pod的并发执行,例如科学计算。
- Job 对象支持并发处理一系列相互独立但是又相互关联的工作任务,例如:
- 发送邮件
- 渲染页面
- 转码文件
- 扫描 NoSQL 数据库中的主键
- 其他
在一个复杂的系统中,可能存在多种类型的工作任务,本文只考虑批处理任务(batch job)。
- 每个工作任务一个 Job 对象 v.s. 一个 Job 对象负责所有的工作任务
- 当工作任务特别多时,第二种选择(一个 Job 对象负责所有的工作任务)更合适一些
- 第一种选择(每个工作任务一个 Job 对象)将为管理员和系统带来很大的额外开销,因为要管理很多数量的 Job 对象
- Pod的数量与工作任务的数量相等 v.s. 每个Pod可以处理多个工作任务
- 第一种选择(Pod的数量与工作任务的数量相等)通常只需要对现有的代码或容器做少量的修改
- 第二种选择(每个Pod可以处理多个工作任务)更适合工作任务的数量特别多的情况,相较于第一种选择可以降低系统开销
- 使用工作队列,此时:
- 需要运行一个队列服务
- 需要对已有的程序或者容器做修改,以便其可以配合队列工作
- 如果是一个已有的程序,改造时可能存在难度
模式 | 单个 Job 对象 | Pod的数量少于工作任务 | 无需修改已有代码 | 在 Kube 1.1 可以运行 |
---|---|---|---|---|
Job Template Expansion | ✓ | ✓ | ||
Queue with Pod Per Work Item | ✓ | sometimes | ✓ | |
Queue with Variable Pod Count | ✓ | ✓ | ✓ | |
Single Job with Static Work Assignment | ✓ | ✓ |
当您指定 .spec.completions
时,Job 控制器创建的每个 Pod 都有一个相同的 spec。这意味着,同一个 Job 创建的所有的 Pod 都使用:
- 相同的执行命令
- 相同的容器镜像
- 相同的数据卷
- 相同的环境变量(例如,不同时间点创建的Pod,Service的环境变量 可能会不同)
Job 的不同模式本质上讲,是如何为一组工作任务分配 Pod。下表总结了不同的模式下 .spec.parallelism
和 .spec.completions
字段的设置。(表中 w
代表工作任务的数量)
Pattern | .spec.completions |
.spec.parallelism |
---|---|---|
Job Template Expansion | 1 | should be 1 |
Queue with Pod Per Work Item | W | any |
Queue with Variable Pod Count | 1 | any |
Single Job with Static Work Assignment | W | any |