Job
Job 创建一个或多个 Pod,并将持续重试执行 Pod,直到指定数量的 Pod 成功终止。随着 Pod 成功完成,Job 会跟踪成功完成的数量。当达到指定数量的成功完成时,任务(即 Job)即完成。删除 Job 会清理其创建的 Pod。暂停 Job 会删除其活跃的 Pod,直到 Job 再次被恢复。
一个简单的用例是创建一个 Job 对象,以便可靠地运行一个 Pod 直至完成。如果第一个 Pod 失败或被删除(例如由于节点硬件故障或节点重启),Job 对象将启动一个新的 Pod。
你也可以使用 Job 来并行运行多个 Pod。
如果你想按计划运行 Job(单任务或并行任务),请参阅 CronJob。
运行示例 Job
这里是一个示例 Job 配置。它计算 π 到小数点后 2000 位并打印出来。它大约需要 10 秒完成。
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl:5.34.0
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
backoffLimit: 4
你可以使用此命令运行示例
kubectl apply -f https://kubernetes.ac.cn/examples/controllers/job.yaml
输出类似于
job.batch/pi created
使用 kubectl
查看 Job 状态
Name: pi
Namespace: default
Selector: batch.kubernetes.io/controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
Labels: batch.kubernetes.io/controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
batch.kubernetes.io/job-name=pi
...
Annotations: batch.kubernetes.io/job-tracking: ""
Parallelism: 1
Completions: 1
Start Time: Mon, 02 Dec 2019 15:20:11 +0200
Completed At: Mon, 02 Dec 2019 15:21:16 +0200
Duration: 65s
Pods Statuses: 0 Running / 1 Succeeded / 0 Failed
Pod Template:
Labels: batch.kubernetes.io/controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
batch.kubernetes.io/job-name=pi
Containers:
pi:
Image: perl:5.34.0
Port: <none>
Host Port: <none>
Command:
perl
-Mbignum=bpi
-wle
print bpi(2000)
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 21s job-controller Created pod: pi-xf9p4
Normal Completed 18s job-controller Job completed
apiVersion: batch/v1
kind: Job
metadata:
annotations: batch.kubernetes.io/job-tracking: ""
...
creationTimestamp: "2022-11-10T17:53:53Z"
generation: 1
labels:
batch.kubernetes.io/controller-uid: 863452e6-270d-420e-9b94-53a54146c223
batch.kubernetes.io/job-name: pi
name: pi
namespace: default
resourceVersion: "4751"
uid: 204fb678-040b-497f-9266-35ffa8716d14
spec:
backoffLimit: 4
completionMode: NonIndexed
completions: 1
parallelism: 1
selector:
matchLabels:
batch.kubernetes.io/controller-uid: 863452e6-270d-420e-9b94-53a54146c223
suspend: false
template:
metadata:
creationTimestamp: null
labels:
batch.kubernetes.io/controller-uid: 863452e6-270d-420e-9b94-53a54146c223
batch.kubernetes.io/job-name: pi
spec:
containers:
- command:
- perl
- -Mbignum=bpi
- -wle
- print bpi(2000)
image: perl:5.34.0
imagePullPolicy: IfNotPresent
name: pi
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Never
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status:
active: 1
ready: 0
startTime: "2022-11-10T17:53:57Z"
uncountedTerminatedPods: {}
要查看 Job 已完成的 Pod,使用 kubectl get pods
。
要以机器可读的格式列出属于某个 Job 的所有 Pod,你可以使用如下命令
pods=$(kubectl get pods --selector=batch.kubernetes.io/job-name=pi --output=jsonpath='{.items[*].metadata.name}')
echo $pods
输出类似于
pi-5rwd7
这里,selector 与 Job 的 selector 相同。--output=jsonpath
选项指定一个表达式,该表达式包含返回列表中每个 Pod 的名称。
查看其中一个 Pod 的标准输出
kubectl logs $pods
查看 Job 日志的另一种方式
kubectl logs jobs/pi
输出类似于
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901
编写 Job 规约
与其他所有 Kubernetes 配置一样,Job 需要 apiVersion
、kind
和 metadata
字段。
当控制平面为 Job 创建新的 Pod 时,Job 的 .metadata.name
是命名这些 Pod 的基础的一部分。Job 的名称必须是合法的 DNS 子域 值,但这可能导致 Pod 主机名出现意外结果。为了获得最佳兼容性,名称应遵循更严格的 DNS 标签 规则。即使名称是 DNS 子域,名称长度也不能超过 63 个字符。
Job 还需要一个 .spec
节。
Job 标签
Job 的 job-name
和 controller-uid
标签将带有 batch.kubernetes.io/
前缀。
Pod 模板
.spec.template
是 .spec
中唯一必需的字段。
.spec.template
是一个 Pod 模板。它的 Schema 与 Pod 完全相同,只是它是嵌套的,并且没有 apiVersion
或 kind
字段。
除了 Pod 所需的字段外,Job 中的 Pod 模板必须指定适当的标签(参阅 Pod 选择算符)和适当的重启策略。
只允许使用等于 Never
或 OnFailure
的 RestartPolicy
。
Pod 选择算符
.spec.selector
字段是可选的。在几乎所有情况下,你不应指定它。请参阅指定自己的 Pod 选择算符一节。
Job 的并行执行
有三种主要类型的任务适合作为 Job 运行
- 非并行 Job
- 通常只启动一个 Pod,除非该 Pod 失败。
- 一旦其 Pod 成功终止,Job 即完成。
- 具有固定完成数的并行 Job
- 为
.spec.completions
指定一个非零正值。 - Job 代表整个任务,当有
.spec.completions
个 Pod 成功完成时,Job 即完成。 - 当使用
.spec.completionMode="Indexed"
时,每个 Pod 会获得一个从 0 到.spec.completions-1
范围内的不同索引。
- 为
- 具有工作队列的并行 Job
- 不要指定
.spec.completions
,它将默认为.spec.parallelism
。 - Pod 必须在它们自己之间或通过外部服务协调,以确定每个 Pod 应该处理什么。例如,Pod 可以从工作队列中获取一批最多 N 个项目。
- 每个 Pod 都能够独立地确定其所有对等 Pod 是否完成,从而确定整个 Job 是否完成。
- 当 Job 中的任何 Pod 成功终止时,不会创建新的 Pod。
- 一旦至少有一个 Pod 成功终止并且所有 Pod 都已终止,则 Job 成功完成。
- 一旦任何 Pod 成功退出,其他 Pod 都不应继续为该任务执行任何工作或写入任何输出。它们都应处于退出过程中。
- 不要指定
对于非并行 Job,你可以将 .spec.completions
和 .spec.parallelism
都留空。当两者都未设置时,都默认为 1。
对于固定完成数 Job,你应该将 .spec.completions
设置为所需的完成数。你可以设置 .spec.parallelism
,也可以将其留空,它将默认为 1。
对于工作队列 Job,你必须将 .spec.completions
留空,并将 .spec.parallelism
设置为非负整数。
有关如何使用不同类型 Job 的更多信息,请参阅Job 模式一节。
控制并行度
所请求的并行度(.spec.parallelism
)可以设置为任何非负值。如果未指定,则默认为 1。如果指定为 0,则 Job 会被有效暂停,直到其增加为止。
由于各种原因,实际并行度(任意时刻运行的 Pod 数)可能大于或小于请求的并行度
- 对于固定完成数 Job,并行运行的实际 Pod 数不会超过剩余的完成数。较高值的
.spec.parallelism
会被有效忽略。 - 对于工作队列 Job,在任何 Pod 成功完成后,都不会启动新的 Pod——但允许剩余的 Pod 完成。
- 如果 Job 控制器 未及时作出反应。
- 如果 Job 控制器因任何原因(缺少
ResourceQuota
、缺少权限等)未能创建 Pod,那么运行的 Pod 可能少于请求的数量。 - 由于同一 Job 中之前 Pod 失败次数过多,Job 控制器可能会限制新 Pod 的创建。
- 当 Pod 被优雅关闭时,需要时间停止。
完成模式
Kubernetes v1.24 [stable]
具有固定完成数的 Job——即具有非空 .spec.completions
的 Job——可以指定在 .spec.completionMode
中的完成模式
NonIndexed
(默认): 当有.spec.completions
个 Pod 成功完成时,Job 被视为完成。换句话说,每个 Pod 完成都是同构的。请注意,具有空.spec.completions
的 Job 隐式为NonIndexed
。Indexed
: Job 的 Pod 会获得一个关联的完成索引,范围从 0 到.spec.completions-1
。该索引可以通过四种机制获得- Pod 注解
batch.kubernetes.io/job-completion-index
。 - Pod 标签
batch.kubernetes.io/job-completion-index
(适用于 v1.28 及更高版本)。请注意,必须启用特性门控PodIndexLabel
才能使用此标签,并且该特性门控默认是启用的。 - 作为 Pod 主机名的一部分,遵循
$(job-name)-$(index)
模式。当你将 Indexed Job 与 Service 结合使用时,Job 中的 Pod 可以使用确定的主机名通过 DNS 相互寻址。有关如何配置此项的更多信息,请参阅 Job 之间的 Pod 到 Pod 通信。 - 在容器化任务中,通过环境变量
JOB_COMPLETION_INDEX
。
当每个索引都有一个成功完成的 Pod 时,Job 被视为完成。有关如何使用此模式的更多信息,请参阅 Indexed Job 的静态工作分配并行处理。
- Pod 注解
注意
虽然很少见,但可能为同一个索引启动多个 Pod(由于各种原因,例如节点故障、kubelet 重启或 Pod 驱逐)。在这种情况下,只有一个最先成功完成的 Pod 将计入完成数并更新 Job 的状态。Job 控制器一旦检测到同一索引的其他正在运行或已完成的 Pod,就会将其删除。处理 Pod 和容器故障
Pod 中的容器可能因多种原因而失败,例如进程退出返回非零退出代码,或者容器因超出内存限制而被杀死等。如果发生这种情况,并且设置了 .spec.template.spec.restartPolicy = "OnFailure"
,则 Pod 会留在节点上,但容器会重新运行。因此,你的程序需要处理在本地重新启动的情况,否则应指定 .spec.template.spec.restartPolicy = "Never"
。有关 restartPolicy
的更多信息,请参阅 Pod 生命周期。
整个 Pod 也可能因多种原因而失败,例如当 Pod 被从节点上驱逐(节点升级、重启、删除等)时,或者当 Pod 的某个容器失败且 .spec.template.spec.restartPolicy = "Never"
时。当 Pod 失败时,Job 控制器会启动一个新的 Pod。这意味着你的应用程序需要处理在新的 Pod 中重新启动的情况。特别是,它需要处理由先前运行留下的临时文件、锁、未完成的输出等。
默认情况下,每次 Pod 失败都会计入 .spec.backoffLimit
限制,参阅 Pod 回退失败策略。但是,你可以通过设置 Job 的 Pod 失败策略 来自定义处理 Pod 失败的方式。
此外,对于 Indexed Job 的每个索引,你可以选择独立计算 Pod 失败次数,通过设置 .spec.backoffLimitPerIndex
字段(更多信息,请参阅 按索引的回退限制)。
请注意,即使你指定了 .spec.parallelism = 1
、.spec.completions = 1
和 .spec.template.spec.restartPolicy = "Never"
,同一个程序有时仍可能启动两次。
如果你确实将 .spec.parallelism
和 .spec.completions
都设置为大于 1,则可能同时运行多个 Pod。因此,你的 Pod 也必须能够容忍并发。
如果你指定了 .spec.podFailurePolicy
字段,Job 控制器不会将正在终止的 Pod(设置了 .metadata.deletionTimestamp
字段的 Pod)视为失败,直到该 Pod 进入终端状态(其 .status.phase
为 Failed
或 Succeeded
)。但是,一旦终止迹象出现,Job 控制器就会创建一个替代 Pod。一旦 Pod 终止,Job 控制器会针对相关 Job 评估 .backoffLimit
和 .podFailurePolicy
,并将此已终止的 Pod 考虑在内。
如果这些要求中的任何一个不满足,Job 控制器会将正在终止的 Pod 立即视为失败,即使该 Pod 后来以 phase: "Succeeded"
状态终止。
Pod 回退失败策略
在某些情况下,由于配置中的逻辑错误等原因,你希望在一定次数的重试后使 Job 失败。为此,可以设置 .spec.backoffLimit
来指定在将 Job 视为失败之前的重试次数。
.spec.backoffLimit
默认设置为 6,除非指定了按索引的回退限制(仅限 Indexed Job)。如果指定了 .spec.backoffLimitPerIndex
,则 .spec.backoffLimit
默认为 2147483647 (MaxInt32)。
与 Job 相关联的失败 Pod 会被 Job 控制器以指数退避延迟(10 秒、20 秒、40 秒...)的方式重新创建,上限为六分钟。
重试次数通过两种方式计算
.status.phase = "Failed"
的 Pod 数量。- 当使用
restartPolicy = "OnFailure"
时,.status.phase
等于Pending
或Running
的 Pod 中所有容器的重试次数总和。
如果任一计算达到 .spec.backoffLimit
,则 Job 被视为失败。
注意
如果你的 Job 设置了restartPolicy = "OnFailure"
,请记住一旦达到 Job 回退限制,运行该 Job 的 Pod 将被终止。这可能会使调试 Job 的可执行文件变得更加困难。我们建议在调试 Job 或使用日志系统以确保失败 Job 的输出不会意外丢失时,将 restartPolicy
设置为 "Never"
。按索引的回退限制
Kubernetes v1.33 [stable]
(默认启用:true)当你运行 Indexed Job 时,你可以选择独立处理每个索引的 Pod 失败重试。为此,设置 .spec.backoffLimitPerIndex
以指定每个索引的最大 Pod 失败次数。
当某个索引超出按索引回退限制时,Kubernetes 将该索引视为失败,并将其添加到 .status.failedIndexes
字段中。成功完成的索引(即具有成功执行的 Pod 的索引)记录在 .status.completedIndexes
字段中,无论你是否设置了 backoffLimitPerIndex
字段。
请注意,失败的索引不会中断其他索引的执行。一旦指定了按索引回退限制的 Job 中所有索引都完成,如果其中至少一个索引确实失败了,Job 控制器会通过在状态中设置 Failed 条件来将整个 Job 标记为失败。即使部分(甚至几乎所有)索引都已成功处理,Job 仍会被标记为失败。
你还可以通过设置 .spec.maxFailedIndexes
字段来限制标记为失败的最大索引数量。当失败索引的数量超过 maxFailedIndexes
字段时,Job 控制器会触发终止该 Job 的所有剩余正在运行的 Pod。一旦所有 Pod 都终止,Job 控制器会通过在 Job 状态中设置 Failed 条件来将整个 Job 标记为失败。
这里是一个定义了 backoffLimitPerIndex
的 Job Manifest 示例
apiVersion: batch/v1
kind: Job
metadata:
name: job-backoff-limit-per-index-example
spec:
completions: 10
parallelism: 3
completionMode: Indexed # required for the feature
backoffLimitPerIndex: 1 # maximal number of failures per index
maxFailedIndexes: 5 # maximal number of failed indexes before terminating the Job execution
template:
spec:
restartPolicy: Never # required for the feature
containers:
- name: example
image: python
command: # The jobs fails as there is at least one failed index
# (all even indexes fail in here), yet all indexes
# are executed as maxFailedIndexes is not exceeded.
- python3
- -c
- |
import os, sys
print("Hello world")
if int(os.environ.get("JOB_COMPLETION_INDEX")) % 2 == 0:
sys.exit(1)
在上面的示例中,Job 控制器允许每个索引重试一次。当失败索引的总数超过 5 时,整个 Job 将被终止。
Job 完成后,其状态如下所示
kubectl get -o yaml job job-backoff-limit-per-index-example
status:
completedIndexes: 1,3,5,7,9
failedIndexes: 0,2,4,6,8
succeeded: 5 # 1 succeeded pod for each of 5 succeeded indexes
failed: 10 # 2 failed pods (1 retry) for each of 5 failed indexes
conditions:
- message: Job has failed indexes
reason: FailedIndexes
status: "True"
type: FailureTarget
- message: Job has failed indexes
reason: FailedIndexes
status: "True"
type: Failed
Job 控制器添加 FailureTarget
Job 条件以触发 Job 终止和清理。当所有 Job Pod 都终止后,Job 控制器添加 Failed
条件,其 reason
和 message
的值与 FailureTarget
Job 条件相同。更多详情,请参阅 Job Pod 的终止。
此外,你可能希望将按索引回退与 Pod 失败策略 一起使用。使用按索引回退时,有一个新的 FailIndex
Action 可用,它允许你避免在索引内部进行不必要的重试。
Pod 失败策略
Kubernetes v1.31 [stable]
(默认启用:true)使用 .spec.podFailurePolicy
字段定义的 Pod 失败策略使你的集群能够根据容器退出代码和 Pod 条件处理 Pod 失败。
在某些情况下,你可能希望在处理 Pod 失败时拥有比基于 Job 的 .spec.backoffLimit
的Pod 回退失败策略 提供的控制更好的控制。以下是一些用例示例
- 为了优化运行工作负载的成本,避免不必要的 Pod 重启,一旦 Job 的某个 Pod 因指示软件错误的退出代码而失败,你可以立即终止该 Job。
- 为了保证你的 Job 即使在发生中断时也能完成,你可以忽略由中断(例如抢占、API 调度的驱逐或基于污点的驱逐)引起的 Pod 失败,这样它们就不会计入
.spec.backoffLimit
的重试限制。
你可以在 .spec.podFailurePolicy
字段中配置 Pod 失败策略,以满足上述用例。此策略可以根据容器退出代码和 Pod 条件来处理 Pod 失败。
这里是一个定义了 podFailurePolicy
的 Job Manifest 示例
apiVersion: batch/v1
kind: Job
metadata:
name: job-pod-failure-policy-example
spec:
completions: 12
parallelism: 3
template:
spec:
restartPolicy: Never
containers:
- name: main
image: docker.io/library/bash:5
command: ["bash"] # example command simulating a bug which triggers the FailJob action
args:
- -c
- echo "Hello world!" && sleep 5 && exit 42
backoffLimit: 6
podFailurePolicy:
rules:
- action: FailJob
onExitCodes:
containerName: main # optional
operator: In # one of: In, NotIn
values: [42]
- action: Ignore # one of: Ignore, FailJob, Count
onPodConditions:
- type: DisruptionTarget # indicates Pod disruption
在上面的示例中,Pod 失败策略的第一条规则指定,如果 main
容器以退出码 42 失败,则应将 Job 标记为失败。以下是专门针对 main
容器的规则
- 退出码为 0 表示容器成功
- 退出码为 42 表示整个 Job 失败
- 任何其他退出码表示容器失败,因此整个 Pod 也失败。如果总重启次数低于
backoffLimit
,则 Pod 将被重新创建。如果达到backoffLimit
,则整个 Job 失败。
注意
由于 Pod 模板指定了restartPolicy: Never
,kubelet 不会重新启动该特定 Pod 中的 main
容器。Pod 失败策略的第二条规则,为带有条件 DisruptionTarget
的失败 Pod 指定 Ignore
操作,这将中断引起的 Pod 失败排除在 .spec.backoffLimit
重试限制之外。
注意
如果 Job 因 Pod 失败策略或 Pod 回退失败策略而失败,并且 Job 正在运行多个 Pod,Kubernetes 会终止该 Job 中所有仍处于 Pending 或 Running 状态的 Pod。以下是 API 的一些要求和语义
- 如果你想为 Job 使用
.spec.podFailurePolicy
字段,你还必须将该 Job 的 Pod 模板中的.spec.restartPolicy
设置为Never
。 - 你在
spec.podFailurePolicy.rules
下指定的 Pod 失败策略规则按顺序进行评估。一旦某条规则匹配到 Pod 失败,其余规则将被忽略。如果没有规则匹配到 Pod 失败,则应用默认处理方式。 - 你可以通过在
spec.podFailurePolicy.rules[*].onExitCodes.containerName
中指定容器名称来将规则限制到特定的容器。如果未指定,则规则适用于所有容器。如果指定了,它应与 Pod 模板中的一个容器或initContainer
名称匹配。 - 你可以通过
spec.podFailurePolicy.rules[*].action
指定匹配到 Pod 失败策略时采取的动作。可能的值有FailJob
: 用于指示应将 Pod 的 Job 标记为 Failed,并终止所有正在运行的 Pod。Ignore
: 用于指示不应增加针对.spec.backoffLimit
的计数器,并且应创建一个替代 Pod。Count
: 用于指示应以默认方式处理 Pod。应增加针对.spec.backoffLimit
的计数器。FailIndex
: 将此 Action 与 按索引的回退限制 一起使用,以避免在失败 Pod 的索引内部进行不必要的重试。
注意
当你使用podFailurePolicy
时,Job 控制器只匹配处于 Failed
阶段的 Pod。具有删除时间戳但未处于终端阶段(Failed
或 Succeeded
)的 Pod 被认为仍在终止中。这意味着正在终止的 Pod 会保留一个 跟踪终结器,直到它们达到终端阶段。自 Kubernetes 1.27 起,Kubelet 会将已删除的 Pod 转换到终端阶段(参阅:Pod 阶段)。这确保已删除的 Pod 的终结器会被 Job 控制器移除。注意
从 Kubernetes v1.28 开始,使用 Pod 失败策略时,Job 控制器仅在终止中的 Pod 达到终端Failed
阶段后才会重新创建它们。此行为类似于 podReplacementPolicy: Failed
。有关更多信息,请参阅 Pod 替换策略。当你使用 podFailurePolicy
并且 Job 因 Pod 匹配带有 FailJob
Action 的规则而失败时,Job 控制器会通过添加 FailureTarget
条件来触发 Job 终止过程。有关更多详情,请参阅 Job 终止和清理。
成功策略
创建 Indexed Job 时,你可以使用 .spec.successPolicy
根据成功完成的 Pod 来定义何时可以将 Job 声明为成功。
默认情况下,当成功完成的 Pod 数量等于 .spec.completions
时,Job 成功。在某些情况下,你可能希望对声明 Job 成功有额外的控制,例如
- 当运行具有不同参数的模拟时,你可能不需要所有模拟都成功才能使整个 Job 成功。
- 当遵循领导者-工作者模式时,只有领导者的成功决定了 Job 的成功或失败。例如 MPI 和 PyTorch 等框架。
你可以在 .spec.successPolicy
字段中配置成功策略,以满足上述用例。此策略可以根据成功完成的 Pod 来处理 Job 的成功。当 Job 满足成功策略后,Job 控制器会终止剩余的 Pod。成功策略由规则定义。每条规则可以采用以下形式之一
- 当你只指定
succeededIndexes
时,一旦succeededIndexes
中指定的所有索引都成功完成,Job 控制器会将 Job 标记为成功。succeededIndexes
必须是 0 到.spec.completions-1
之间的索引区间列表。 - 当你只指定
succeededCount
时,一旦成功完成的索引数量达到succeededCount
,Job 控制器会将 Job 标记为成功。 - 当你同时指定
succeededIndexes
和succeededCount
时,一旦succeededIndexes
中指定的索引子集中成功完成的索引数量达到succeededCount
,Job 控制器会将 Job 标记为成功。
请注意,当你在 .spec.successPolicy.rules
中指定多条规则时,Job 控制器会按顺序评估这些规则。一旦 Job 满足某条规则,Job 控制器就会忽略剩余的规则。
这里是一个带有 successPolicy
的 Job Manifest 示例
apiVersion: batch/v1
kind: Job
metadata:
name: job-success
spec:
parallelism: 10
completions: 10
completionMode: Indexed # Required for the success policy
successPolicy:
rules:
- succeededIndexes: 0,2-3
succeededCount: 1
template:
spec:
containers:
- name: main
image: python
command: # Provided that at least one of the Pods with 0, 2, and 3 indexes has succeeded,
# the overall Job is a success.
- python3
- -c
- |
import os, sys
if os.environ.get("JOB_COMPLETION_INDEX") == "2":
sys.exit(0)
else:
sys.exit(1)
restartPolicy: Never
在上面的例子中,succeededIndexes
和 succeededCount
都已指定。因此,当任一指定的索引(0、2 或 3)成功时,Job 控制器会将 Job 标记为成功并终止剩余的 Pod。满足成功策略的 Job 会获得 SuccessCriteriaMet
状态条件,原因(reason)为 SuccessPolicy
。发出移除剩余 Pod 的指令后,Job 会获得 Complete
状态条件。
注意,succeededIndexes
表示为以连字符分隔的区间。数字列表由系列的首个和最后一个元素表示,它们之间用连字符分隔。
注意
当你同时指定一个成功策略和一些终止策略(例如.spec.backoffLimit
和 .spec.podFailurePolicy
)时,一旦 Job 满足其中任何一个策略,Job 控制器将遵循终止策略并忽略成功策略。Job 的终止和清理
Job 完成后,不会再创建 Pod,但 Pod 通常也不会被删除。保留它们使你仍然可以查看已完成 Pod 的日志,以检查错误、警告或其他诊断输出。Job 对象在完成后也仍然保留,以便你可以查看其状态。用户应在记下其状态后删除旧的 Job。使用 kubectl
删除 Job(例如 kubectl delete jobs/pi
或 kubectl delete -f ./job.yaml
)。当你使用 kubectl
删除 Job 时,它创建的所有 Pod 也将被删除。
默认情况下,Job 会不间断地运行,除非 Pod 失败(restartPolicy=Never
)或容器出错退出(restartPolicy=OnFailure
),此时 Job 将遵从上述的 .spec.backoffLimit
。一旦达到 .spec.backoffLimit
,Job 将被标记为失败,所有正在运行的 Pod 都将被终止。
终止 Job 的另一种方法是设置活跃时限(active deadline)。通过将 Job 的 .spec.activeDeadlineSeconds
字段设置为秒数来实现。activeDeadlineSeconds
适用于 Job 的持续时间,无论创建多少 Pod。一旦 Job 达到 activeDeadlineSeconds
,其所有正在运行的 Pod 都将被终止,并且 Job 状态将变为 type: Failed
,原因(reason)为 reason: DeadlineExceeded
。
注意,Job 的 .spec.activeDeadlineSeconds
优先级高于其 .spec.backoffLimit
。因此,正在重试一个或多个失败 Pod 的 Job,一旦达到 activeDeadlineSeconds
指定的时限,将不再部署额外的 Pod,即使尚未达到 backoffLimit
。
示例
apiVersion: batch/v1
kind: Job
metadata:
name: pi-with-timeout
spec:
backoffLimit: 5
activeDeadlineSeconds: 100
template:
spec:
containers:
- name: pi
image: perl:5.34.0
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
注意,Job Spec 和 Job 中的 Pod 模板 Spec 都有一个 activeDeadlineSeconds
字段。确保你在合适的级别设置此字段。
请记住,restartPolicy
应用于 Pod,而不是 Job 本身:一旦 Job 状态为 type: Failed
,不会自动重启 Job。也就是说,通过 .spec.activeDeadlineSeconds
和 .spec.backoffLimit
激活的 Job 终止机制会导致永久性的 Job 失败,需要手动干预来解决。
Job 的终止状态条件
Job 有两种可能的终止状态,每种状态都有相应的 Job 状态条件
- 成功:Job 状态条件
Complete
- 失败:Job 状态条件
Failed
Job 因以下原因失败
- Pod 失败次数超过了 Job Spec 中指定的
.spec.backoffLimit
。详情请参阅 Pod 回退失败策略。 - Job 运行时长超过了指定的
.spec.activeDeadlineSeconds
。 - 使用过
.spec.backoffLimitPerIndex
的索引式 Job 存在失败的索引。详情请参阅 每索引回退限制。 - Job 中失败索引的数量超过了指定的
spec.maxFailedIndexes
。详情请参阅 每索引回退限制。 - 一个失败的 Pod 与
.spec.podFailurePolicy
中的规则匹配,该规则包含FailJob
操作。关于 Pod 失败策略规则如何影响失败评估的详情,请参阅 Pod 失败策略。
Job 因以下原因成功
- 成功 Pod 的数量达到了指定的
.spec.completions
。 - 满足了
.spec.successPolicy
中指定的条件。详情请参阅 成功策略。
在 Kubernetes v1.31 及更高版本中,Job 控制器会延迟添加终止状态条件(Failed
或 Complete
),直到所有 Job Pod 终止。
在 Kubernetes v1.30 及更早版本中,Job 控制器会在 Job 终止过程触发且所有 Pod Finalizer 都被移除后立即添加 Complete
或 Failed
Job 终止状态条件。然而,在添加终止状态条件时,一些 Pod 可能仍在运行或正在终止。
在 Kubernetes v1.31 及更高版本中,控制器只会在所有 Pod 终止**后**添加 Job 终止状态条件。你可以通过使用 JobManagedBy
和 JobPodReplacementPolicy
(两者默认都启用)特性开关(feature gates)来控制此行为。
Job Pod 的终止
Job 控制器会在 Job 满足成功或失败条件后,向 Job 添加 FailureTarget
状态条件或 SuccessCriteriaMet
状态条件,以触发 Pod 终止。
像 terminationGracePeriodSeconds
这样的因素可能会增加从 Job 控制器添加 FailureTarget
或 SuccessCriteriaMet
状态条件的时刻起,到所有 Job Pod 终止并且 Job 控制器添加一个终止状态条件(Failed
或 Complete
)的时刻之间的时间量。
你可以使用 FailureTarget
或 SuccessCriteriaMet
状态条件来评估 Job 是失败还是成功,而无需等待控制器添加终止状态条件。
例如,你可能希望决定何时创建一个替代 Job 来替换失败的 Job。如果你在 FailureTarget
状态条件出现时替换失败的 Job,你的替代 Job 会更快地运行,但可能导致来自失败 Job 和替代 Job 的 Pod 同时运行,占用额外的计算资源。
或者,如果你的集群资源容量有限,你可以选择等到 Job 上出现 Failed
状态条件,这会延迟你的替代 Job,但能确保你通过等待所有失败 Pod 被移除来节省资源。
自动清理已完成的 Job
已完成的 Job 通常不再需要保留在系统中。将它们保留在系统中会给 API Server 带来压力。如果 Job 由更高级别的控制器直接管理(例如 CronJobs),CronJobs 可以基于指定的容量清理策略清理这些 Job。
已完成 Job 的 TTL 机制
Kubernetes v1.23 [stable]
自动清理已完成(Complete
或 Failed
)Job 的另一种方法是,通过指定 Job 的 .spec.ttlSecondsAfterFinished
字段,使用由已完成资源的 TTL 控制器提供的 TTL 机制。
当 TTL 控制器清理 Job 时,它将级联删除 Job,即与 Job 一起删除其依赖对象,例如 Pod。注意,当 Job 被删除时,其生命周期保证(例如 Finalizer)将得到遵守。
例如
apiVersion: batch/v1
kind: Job
metadata:
name: pi-with-ttl
spec:
ttlSecondsAfterFinished: 100
template:
spec:
containers:
- name: pi
image: perl:5.34.0
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
Job pi-with-ttl
在它完成后 100
秒将符合自动删除的条件。
如果该字段设置为 0
,Job 将在它完成后立即符合自动删除的条件。如果该字段未设置,这个 Job 在完成后不会被 TTL 控制器清理。
注意
建议设置 ttlSecondsAfterFinished
字段,因为非托管 Job(你直接创建的 Job,而不是通过 CronJob 等其他工作负载 API 间接创建的 Job)默认删除策略为 orphanDependents
,这会导致非托管 Job 创建的 Pod 在 Job 完全删除后被保留下来。即使 控制平面最终会对已删除 Job 的 Pod 进行垃圾收集(在它们失败或完成后),有时这些剩余的 Pod 可能会导致集群性能下降,或者在最坏的情况下,由于这种下降而导致集群下线。
你可以使用 LimitRanges 和 ResourceQuotas 来限制特定命名空间可以消耗的资源数量。
Job 模式
Job 对象可用于处理一组独立但相关的**工作项**。这些可能是要发送的电子邮件、要渲染的帧、要转码的文件、要扫描的 NoSQL 数据库中的键范围等等。
在复杂的系统中,可能存在多组不同的工作项。这里我们只考虑用户希望一起管理的一组工作项 —— 一个**批处理 Job**。
并行计算有几种不同的模式,每种模式都有其优点和缺点。权衡因素包括:
- 每个工作项对应一个 Job 对象,还是所有工作项共用一个 Job 对象。每个工作项对应一个 Job 会给用户和系统管理大量 Job 对象带来一些开销。所有工作项共用一个 Job 对于处理大量工作项更优。
- 创建的 Pod 数量等于工作项数量,还是每个 Pod 可以处理多个工作项。当 Pod 数量等于工作项数量时,Pod 通常需要对现有代码和容器进行较少的修改。让每个 Pod 处理多个工作项对于处理大量工作项更优。
- 有几种方法使用工作队列。这需要运行一个队列服务,并修改现有程序或容器以使其使用工作队列。其他方法更容易适应现有的容器化应用。
- 当 Job 与无头 Service 关联时,你可以使 Job 中的 Pod 之间相互通信,以协同完成计算。
这里总结了这些权衡,第 2 到第 4 列对应于上述权衡因素。模式名称也是指向示例和更详细描述的链接。
模式 | 单个 Job 对象 | Pod 数量少于工作项? | 应用无需修改即可使用? |
---|---|---|---|
每工作项一个 Pod 的队列模式 | ✓ | 有时 | |
可变 Pod 数量的队列模式 | ✓ | ✓ | |
静态工作分配的索引式 Job | ✓ | ✓ | |
Pod 之间通信的 Job | ✓ | 有时 | 有时 |
Job 模板扩展 | ✓ |
当你使用 .spec.completions
指定完成数时,Job 控制器创建的每个 Pod 都有相同的 spec
。这意味着一个任务的所有 Pod 将拥有相同的命令行、相同的镜像、相同的卷以及(几乎)相同的环境变量。这些模式是安排 Pod 执行不同工作的不同方式。
这个表格显示了每种模式所需的 .spec.parallelism
和 .spec.completions
设置。这里,W
是工作项的数量。
模式 | .spec.completions | .spec.parallelism |
---|---|---|
每工作项一个 Pod 的队列模式 | W | 任意 |
可变 Pod 数量的队列模式 | null | 任意 |
静态工作分配的索引式 Job | W | 任意 |
Pod 之间通信的 Job | W | W |
Job 模板扩展 | 1 | 应该为 1 |
高级用法
暂停 Job
Kubernetes v1.24 [stable]
创建 Job 时,Job 控制器会立即开始创建 Pod 以满足 Job 的要求,并持续创建直到 Job 完成。然而,你可能希望暂时暂停 Job 的执行并在以后恢复,或者以暂停状态启动 Job 并让自定义控制器稍后决定何时启动它们。
要暂停 Job,你可以将 Job 的 .spec.suspend
字段更新为 true;稍后,当你希望再次恢复它时,将其更新为 false。创建一个将 .spec.suspend
设置为 true 的 Job 将在暂停状态下创建它。
Job 从暂停状态恢复时,其 .status.startTime
字段将被重置为当前时间。这意味着当 Job 暂停和恢复时,.spec.activeDeadlineSeconds
计时器将停止并重置。
暂停 Job 时,任何状态不是 Completed
的正在运行的 Pod 将通过 SIGTERM 信号被终止。Pod 的优雅终止期限将得到遵守,你的 Pod 必须在此期间处理此信号。这可能涉及保存进度以便稍后继续或撤销更改。以这种方式终止的 Pod 不会计入 Job 的 completions
计数。
处于暂停状态的 Job 定义示例如下
kubectl get job myjob -o yaml
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
suspend: true
parallelism: 1
completions: 5
template:
spec:
...
你也可以使用命令行通过 Patch Job 来切换 Job 的暂停状态。
暂停一个活跃的 Job
kubectl patch job/myjob --type=strategic --patch '{"spec":{"suspend":true}}'
恢复一个已暂停的 Job
kubectl patch job/myjob --type=strategic --patch '{"spec":{"suspend":false}}'
Job 的状态可以用来确定 Job 当前是否暂停或过去是否暂停过
kubectl get jobs/myjob -o yaml
apiVersion: batch/v1
kind: Job
# .metadata and .spec omitted
status:
conditions:
- lastProbeTime: "2021-02-05T13:14:33Z"
lastTransitionTime: "2021-02-05T13:14:33Z"
status: "True"
type: Suspended
startTime: "2021-02-05T13:13:48Z"
类型为 "Suspended" 且状态为 "True" 的 Job 状态条件意味着 Job 已暂停;lastTransitionTime
字段可用于确定 Job 已暂停多久。如果该状态条件的状态为 "False",则 Job 之前被暂停过,现在正在运行。如果 Job 的状态中不存在这样的状态条件,则 Job 从未被暂停过。
暂停和恢复 Job 时也会创建事件
kubectl describe jobs/myjob
Name: myjob
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 12m job-controller Created pod: myjob-hlrpl
Normal SuccessfulDelete 11m job-controller Deleted pod: myjob-hlrpl
Normal Suspended 11m job-controller Job suspended
Normal SuccessfulCreate 3s job-controller Created pod: myjob-jvb44
Normal Resumed 3s job-controller Job resumed
最后四个事件,特别是 "Suspended" 和 "Resumed" 事件,是切换 .spec.suspend
字段的直接结果。在这两个事件之间的时间段内,我们看到没有创建 Pod,但 Job 恢复后立即重启了 Pod 创建。
可变的调度指令
Kubernetes v1.27 [stable]
在大多数情况下,并行 Job 希望 Pod 在约束下运行,例如都在同一个可用区,或者都运行在 GPU 模型 x 或 y 上,而不是混合使用。
suspend 字段是实现这些语义的第一步。Suspend 允许自定义队列控制器决定何时启动 Job;然而,一旦 Job 被取消暂停,自定义队列控制器就无法影响 Job 的 Pod 实际落在哪里。
此特性允许在 Job 启动前更新其调度指令,这使得自定义队列控制器能够影响 Pod 的放置位置,同时将实际的 Pod 到节点分配工作分载给 kube-scheduler。这只适用于之前从未被取消暂停过的已暂停 Job。
Job 的 Pod 模板中可以更新的字段包括节点亲和性、节点选择器、容忍度、标签、注解和调度门。
指定你自己的 Pod 选择器
通常,当你创建 Job 对象时,你不需要指定 .spec.selector
。系统默认逻辑会在 Job 创建时添加此字段。它会选择一个不会与其他 Job 重叠的选择器值。
然而,在某些情况下,你可能需要覆盖此自动设置的选择器。为此,你可以指定 Job 的 .spec.selector
。
执行此操作时务必小心。如果你指定一个并非该 Job 的 Pod 所独有的标签选择器,并且该选择器匹配了无关的 Pod,那么无关 Job 的 Pod 可能会被删除,或者此 Job 可能会将其他 Pod 计为完成其任务的 Pod,或者一个或两个 Job 可能拒绝创建 Pod 或运行到完成。如果选择了非唯一的选择器,那么其他控制器(例如 ReplicationController)及其 Pod 的行为也可能变得不可预测。在指定 .spec.selector
时,Kubernetes 不会阻止你犯错。
这里有一个你可能希望使用此特性的示例。
假设 Job old
正在运行。你希望现有 Pod 继续运行,但希望它创建的其余 Pod 使用不同的 Pod 模板,并且 Job 有一个新的名称。你无法更新 Job,因为这些字段不可更新。因此,你删除 Job old
,但**保留其 Pod 继续运行**,使用 kubectl delete jobs/old --cascade=orphan
。在删除之前,你记下它使用的选择器
kubectl get job old -o yaml
输出类似于
kind: Job
metadata:
name: old
...
spec:
selector:
matchLabels:
batch.kubernetes.io/controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
...
然后你创建一个名为 new
的新 Job,并显式指定相同的选择器。由于现有 Pod 具有标签 batch.kubernetes.io/controller-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002
,它们也由 Job new
控制。
你需要在新 Job 中指定 manualSelector: true
,因为你没有使用系统通常为你自动生成的选择器。
kind: Job
metadata:
name: new
...
spec:
manualSelector: true
selector:
matchLabels:
batch.kubernetes.io/controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
...
新 Job 本身将具有与 a8f3d00d-c6d2-11e5-9f87-42010af00002
不同的 UID。设置 manualSelector: true
告诉系统你知道自己在做什么,并允许这种不匹配。
使用 Finalizer 进行 Job 跟踪
Kubernetes v1.26 [stable]
控制平面会跟踪属于任何 Job 的 Pod,并注意是否有此类 Pod 从 API Server 中被移除。为此,Job 控制器创建带有 Finalizer batch.kubernetes.io/job-tracking
的 Pod。控制器仅在 Pod 已在 Job 状态中被计算后才会移除该 Finalizer,从而允许 Pod 由其他控制器或用户移除。
注意
如果你观察到 Job 中的 Pod 带有跟踪 Finalizer 并停留在终止状态,请参阅 我的 Pod 停留在 terminating 状态。弹性索引式 Job
Kubernetes v1.31 [stable]
(默认启用:true)你可以通过同时修改 .spec.parallelism
和 .spec.completions
来扩缩索引式 Job,以便满足 .spec.parallelism == .spec.completions
。缩容时,Kubernetes 会移除索引较高的 Pod。
弹性索引式 Job 的用例包括需要扩缩索引式 Job 的批处理工作负载,例如 MPI、Horovod、Ray 和 PyTorch 训练 Job。
延迟创建替代 Pod
Kubernetes v1.29 [beta]
默认情况下,Job 控制器会在 Pod 失败或正在终止(有删除时间戳)后立即重新创建 Pod。这意味着在特定时间点,当一些 Pod 正在终止时,Job 的正在运行 Pod 数量可能大于 parallelism
,或者大于每索引一个 Pod(如果你使用的是索引式 Job)。
你可以选择仅在终止中的 Pod 完全终止(状态为 status.phase: Failed
)时才创建替代 Pod。为此,设置 .spec.podReplacementPolicy: Failed
。默认的替换策略取决于 Job 是否设置了 podFailurePolicy
。如果未为 Job 定义 Pod 失败策略,省略 podReplacementPolicy
字段会选择 TerminatingOrFailed
替换策略:控制平面会在 Pod 删除后立即创建替代 Pod(即一旦控制平面看到该 Job 的 Pod 设置了 deletionTimestamp
)。对于设置了 Pod 失败策略的 Job,默认的 podReplacementPolicy
是 Failed
,并且不允许其他值。请参阅 Pod 失败策略 了解更多关于 Job 的 Pod 失败策略的信息。
kind: Job
metadata:
name: new
...
spec:
podReplacementPolicy: Failed
...
如果你的集群启用了该特性开关,你可以检查 Job 的 .status.terminating
字段。该字段的值是当前正在终止的、属于该 Job 的 Pod 数量。
kubectl get jobs/myjob -o yaml
apiVersion: batch/v1
kind: Job
# .metadata and .spec omitted
status:
terminating: 3 # three Pods are terminating and have not yet reached the Failed phase
将 Job 对象的管理委托给外部控制器
Kubernetes v1.32 [beta]
(默认启用:true)此特性允许你针对特定 Job 禁用内置 Job 控制器,并将 Job 的协调工作委托给外部控制器。
你通过为 spec.managedBy
字段设置自定义值来指明协调 Job 的控制器 —— 除了 kubernetes.io/job-controller
之外的任何值。该字段的值是不可变的。
注意
使用此特性时,确保字段指示的控制器已安装,否则 Job 可能根本不会被协调。注意
开发外部 Job 控制器时,请注意,你的控制器需要按照 Job 对象的 API Spec 和状态字段定义的方式运行。
请在 Job API 中详细查看这些内容。我们也建议你运行 Job 对象的 e2e 一致性测试来验证你的实现。
最后,开发外部 Job 控制器时,确保它不使用 batch.kubernetes.io/job-tracking
finalizer,该 finalizer 保留供内置控制器使用。
警告
如果你考虑禁用JobManagedBy
特性开关,或将集群降级到未启用该特性开关的版本,检查是否存在 spec.managedBy
字段具有自定义值的 Job。如果存在此类 Job,操作后存在它们可能被两个控制器协调的风险:内置 Job 控制器和字段值指示的外部控制器。替代方案
裸 Pod
当 Pod 运行的节点重启或失败时,Pod 被终止且不会被重启。然而,Job 会创建新的 Pod 来替代被终止的 Pod。因此,我们建议你使用 Job 而不是裸 Pod,即使你的应用只需要一个 Pod。
Replication Controller
Job 是 Replication Controllers 的补充。Replication Controller 管理不期望终止的 Pod(例如 Web 服务器),而 Job 管理期望终止的 Pod(例如批处理任务)。
正如Pod 生命周期 中所讨论的,Job
**仅**适用于 RestartPolicy
等于 OnFailure
或 Never
的 Pod。(注意:如果未设置 RestartPolicy
,默认值为 Always
。)
单个 Job 启动控制器 Pod
另一种模式是一个 Job 创建一个 Pod,该 Pod 再创建其他 Pod,作为这些 Pod 的一种自定义控制器。这提供了最大的灵活性,但入门可能有些复杂,并且与 Kubernetes 的集成度较低。
这种模式的一个示例是一个 Job 启动一个 Pod,该 Pod 运行一个脚本,脚本进而启动 Spark master 控制器(请参阅 spark 示例),运行一个 Spark driver,然后进行清理。
这种方法的优点是,整个过程获得了 Job 对象的完成保证,但保持对创建哪些 Pod 以及如何将工作分配给它们的完全控制。
接下来
- 了解 Pod。
- 阅读有关运行 Job 的不同方式
- 遵循自动清理已完成的 Job 中的链接,了解有关你的集群如何清理已完成和/或失败任务的更多信息。
Job
是 Kubernetes REST API 的一部分。阅读 Job 对象定义以了解 Job 的 API。- 阅读有关
CronJob
的信息,你可以使用它来定义一系列 Job,这些 Job 将根据计划运行,类似于 UNIX 工具cron
。 - 基于分步示例,练习如何使用
podFailurePolicy
配置可重试和不可重试的 Pod 失败处理。