作业
作业会创建一个或多个 Pod,并持续重试 Pod 的执行,直到指定数量的 Pod 成功终止。随着 Pod 成功完成,作业会跟踪成功完成的次数。当达到指定数量的成功完成次数时,任务(即作业)就完成了。删除作业会清理它创建的 Pod。暂停作业会删除其活动 Pod,直到作业再次恢复。
一个简单的用例是创建一个作业对象,以可靠地运行一个 Pod 直到完成。如果第一个 Pod 失败或被删除(例如,由于节点硬件故障或节点重启),作业对象将启动一个新的 Pod。
您也可以使用作业并行运行多个 Pod。
如果您想按计划运行一个作业(单个任务或多个并行任务),请参见 CronJob.
运行示例作业
这是一个示例作业配置。它计算 π 到 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
检查作业的状态
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: {}
要查看作业的已完成 Pod,请使用 kubectl get pods
。
要以机器可读的格式列出属于作业的所有 Pod,您可以使用以下命令
pods=$(kubectl get pods --selector=batch.kubernetes.io/job-name=pi --output=jsonpath='{.items[*].metadata.name}')
echo $pods
输出类似于以下内容
pi-5rwd7
这里,选择器与作业的选择器相同。--output=jsonpath
选项指定一个表达式,其中包含返回列表中每个 Pod 的名称。
查看其中一个 Pod 的标准输出
kubectl logs $pods
查看作业日志的另一种方法
kubectl logs jobs/pi
输出类似于以下内容
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901
编写作业规范
与所有其他 Kubernetes 配置一样,作业需要 apiVersion
、kind
和 metadata
字段。
当控制平面为作业创建新的 Pod 时,作业的 .metadata.name
是命名这些 Pod 的基础的一部分。作业的名称必须是有效的 DNS 子域 值,但这可能会对 Pod 主机名产生意外的结果。为了获得最佳兼容性,名称应遵循 DNS 标签 的更严格规则。即使名称是 DNS 子域,名称也不能超过 63 个字符。
作业还需要一个 .spec
部分.
作业标签
作业标签将具有 batch.kubernetes.io/
前缀,用于 job-name
和 controller-uid
。
Pod 模板
.spec.template
是 .spec
中唯一必填字段。
.spec.template
是一个 Pod 模板。它与 Pod 具有完全相同的模式,只是它嵌套在内,没有 apiVersion
或 kind
。
除了 Pod 的必填字段外,作业中的 Pod 模板必须指定适当的标签(参见 Pod 选择器)和适当的重启策略。
只允许等于 Never
或 OnFailure
的 RestartPolicy
。
Pod 选择器
.spec.selector
字段是可选的。在几乎所有情况下,您都不应指定它。请参见部分 指定您自己的 Pod 选择器。
作业的并行执行
主要有三种类型适合作为作业运行的任务
- 非并行作业
- 通常,只启动一个 Pod,除非 Pod 失败。
- 作业在 Pod 成功终止后立即完成。
- 具有*固定完成计数*的并行作业
- 为
.spec.completions
指定一个非零正值。 - 作业代表整个任务,当有
.spec.completions
个成功 Pod 时,作业就完成了。 - 当使用
.spec.completionMode="Indexed"
时,每个 Pod 在 0 到.spec.completions-1
的范围内获得不同的索引。
- 为
- 具有*工作队列*的并行作业
- 不要指定
.spec.completions
,默认值为.spec.parallelism
。 - Pod 必须在它们之间或一个外部服务之间协调,以确定每个 Pod 应该做什么。例如,一个 Pod 可能会从工作队列中获取最多 N 个项目的批次。
- 每个 Pod 能够独立确定其所有对等体是否都已完成,从而确定整个作业是否已完成。
- 当作业中的任何 Pod 成功终止时,不会创建新的 Pod。
- 一旦至少一个 Pod 成功终止,并且所有 Pod 都已终止,则作业成功完成。
- 一旦任何 Pod 成功退出,其他 Pod 不应该再为这个任务做任何工作或写入任何输出。它们应该都处于退出过程。
- 不要指定
对于*非并行*作业,您可以保留 .spec.completions
和 .spec.parallelism
未设置。当两者都未设置时,两者都默认为 1。
对于*固定完成计数*作业,您应该将 .spec.completions
设置为所需的完成次数。您可以设置 .spec.parallelism
,或者保留它未设置,它将默认为 1。
对于*工作队列*作业,您必须保留 .spec.completions
未设置,并将 .spec.parallelism
设置为一个非负整数。
有关如何使用不同类型的作业的更多信息,请参见 作业模式 部分。
控制并行度
请求的并行度 (.spec.parallelism
) 可以设置为任何非负值。如果未指定,它默认为 1。如果它被指定为 0,则作业将有效地暂停,直到它被增加。
实际并行度(任何时刻运行的 Pod 数量)可能多于或少于请求的并行度,原因有很多
- 对于*固定完成计数*作业,实际运行的 Pod 数量不会超过剩余的完成次数。
.spec.parallelism
的较高值将被有效地忽略。 - 对于*工作队列*作业,在任何 Pod 成功后都不会启动新的 Pod - 但是,剩余的 Pod 允许完成。
- 如果作业 控制器 没有时间做出反应。
- 如果作业控制器由于任何原因(缺少
ResourceQuota
、缺少权限等)未能创建 Pod,则 Pod 的数量可能少于请求的数量。 - 作业控制器可能会由于同一作业中之前的 Pod 失败过多而限制新的 Pod 创建。
- 当 Pod 被优雅地关闭时,它需要时间来停止。
完成模式
Kubernetes v1.24 [稳定]
具有*固定完成计数*的作业 - 也就是说,具有非空 .spec.completions
的作业 - 可以具有在 .spec.completionMode
中指定的完成模式
NonIndexed
(默认):当有.spec.completions
个成功完成的 Pod 时,作业被认为已完成。换句话说,每个 Pod 完成都是同源的。请注意,具有空.spec.completions
的作业隐式地为NonIndexed
。Indexed
:作业的 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 作业与 服务 组合使用时,作业中的 Pod 可以使用确定性的主机名通过 DNS 互相寻址。有关如何配置此功能的更多信息,请参见 具有 Pod 到 Pod 通信的作业。 - 从容器化任务中,在环境变量
JOB_COMPLETION_INDEX
中。
当每个索引都有一个成功完成的 Pod 时,作业被认为已完成。有关如何使用此模式的更多信息,请参见 用于并行处理的带静态工作分配的 Indexed 作业。
- Pod 注解
注意
虽然很少见,但可能为同一个索引启动多个 Pod(由于各种原因,例如节点故障、kubelet 重启或 Pod 驱逐)。在这种情况下,只有第一个成功完成的 Pod 会计入完成计数并更新作业的状态。对于同一个索引正在运行或已完成的其他 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 失败的处理方式。
此外,您还可以选择针对 索引 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 终止(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 视为失败之前允许重试的次数。默认情况下,回退限制设置为 6。与 Job 相关的失败 Pod 将由 Job 控制器重新创建,并具有指数级回退延迟(10 秒、20 秒、40 秒……)上限为六分钟。
重试次数以两种方式计算
- 具有
.status.phase = "Failed"
的 Pod 数量。 - 当使用
restartPolicy = "OnFailure"
时,具有.status.phase
等于Pending
或Running
的所有 Pod 中所有容器的重试次数。
如果任一计算结果达到 .spec.backoffLimit
,则 Job 被视为失败。
注意
如果您的作业具有restartPolicy = "OnFailure"
,请记住,一旦达到作业回退限制,运行作业的 Pod 将被终止。这会使调试作业的可执行文件更加困难。我们建议在调试作业或使用日志系统时设置 restartPolicy = "Never"
,以确保失败作业的输出不会意外丢失。每个索引的回退限制
Kubernetes v1.29 [beta]
当您运行 索引 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 的示例清单
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 将被终止。
作业完成后,作业状态如下所示
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
操作可用,它允许您避免在失败的 Pod 的索引内进行不必要的重试。
Pod 失败策略
Kubernetes v1.31 [stable]
使用 .spec.podFailurePolicy
字段定义的 Pod 失败策略使您的集群能够根据容器退出代码和 Pod 条件处理 Pod 失败。
在某些情况下,您可能希望在处理 Pod 失败时获得比 Pod 回退失败策略 提供的控制更好的控制,该策略基于 Job 的 .spec.backoffLimit
。以下是一些用例示例
- 要通过避免不必要的 Pod 重启来优化运行工作负载的成本,您可以尽快终止 Job,只要其 Pod 之一以退出代码失败,该退出代码指示软件错误。
- 为了确保您的 Job 即使在出现中断的情况下也能完成,您可以忽略由中断(例如 抢占、API 发起的驱逐 或 污点 驱逐)引起的 Pod 失败,这样它们就不会计入
.spec.backoffLimit
重试次数限制。
您可以在 .spec.podFailurePolicy
字段中配置 Pod 失败策略,以满足上述用例。该策略可以根据容器退出代码和 Pod 条件处理 Pod 失败。
这是一个定义了 podFailurePolicy
的 Job 的示例清单
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 失败策略的第二个规则指定了 Ignore
操作,用于具有条件 DisruptionTarget
的失败 Pod,它将 Pod 中断排除在计入 .spec.backoffLimit
重试次数限制之外。
注意
如果 Job 失败(通过 Pod 失败策略或 Pod 回退失败策略),并且 Job 正在运行多个 Pod,Kubernetes 会终止该 Job 中所有仍处于 Pending 或 Running 状态的 Pod。以下是 API 的一些要求和语义
- 如果您想对 Job 使用
.spec.podFailurePolicy
字段,您还必须使用.spec.restartPolicy
设置为Never
来定义该 Job 的 Pod 模板。 - 您在
spec.podFailurePolicy.rules
下指定的 Pod 失败策略规则将按顺序评估。一旦某个规则匹配 Pod 失败,其余规则将被忽略。当没有规则匹配 Pod 失败时,将应用默认处理。 - 您可能希望通过在
spec.podFailurePolicy.rules[*].onExitCodes.containerName
中指定容器名称来将规则限制为特定容器。当未指定时,规则将应用于所有容器。当指定时,它应该与 Pod 模板中的容器或initContainer
名称之一匹配。 - 您可以通过
spec.podFailurePolicy.rules[*].action
指定在匹配 Pod 失败策略时采取的操作。可能的值为FailJob
:用于指示 Pod 的作业应被标记为 Failed,并且所有正在运行的 Pod 应被终止。Ignore
:用于指示不应该增加.spec.backoffLimit
的计数,并且应该创建替换 Pod。Count
:用于指示应以默认方式处理 Pod。应该增加.spec.backoffLimit
的计数。FailIndex
:将此操作与 每个索引的回退限制 结合使用,以避免在失败 Pod 的索引内进行不必要的重试。
注意
当您使用podFailurePolicy
时,作业控制器只匹配处于 Failed
阶段的 Pod。具有删除时间戳但不在终端阶段(Failed
或 Succeeded
)的 Pod 被认为仍在终止。这意味着终止的 Pod 会保留 跟踪终结器,直到它们到达终端阶段。从 Kubernetes 1.27 开始,Kubelet 将已删除的 Pod 转变为终端阶段(参见:Pod 阶段)。这确保了已删除的 Pod 的终结器将由 Job 控制器删除。注意
从 Kubernetes v1.28 开始,当使用 Pod 失败策略时,Job 控制器只会在这些 Pod 达到终端Failed
状态后才重新创建终止的 Pod。此行为类似于 podReplacementPolicy: Failed
。有关更多信息,请参见 Pod 替换策略。当您使用 podFailurePolicy
时,如果 Job 由于与 FailJob
操作的规则匹配的 pod 失败而失败,则 Job 控制器将通过添加 FailureTarget
条件来触发 Job 终止过程。有关更多详细信息,请参见 Job 终止和清理。
成功策略
Kubernetes v1.31 [beta]
创建索引 Job 时,您可以使用 .spec.successPolicy
定义 Job 何时可以根据成功的 pod 被声明为成功。
默认情况下,当成功 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 的清单。
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
条件,其原因是 SuccessPolicy
。在发出删除剩余 Pod 的命令后,Job 会获得 Complete
条件。
请注意,succeededIndexes
表示为用连字符分隔的区间。数字由系列的第一个和最后一个元素表示,用连字符分隔。
注意
当您同时指定成功策略和一些终止策略,例如.spec.backoffLimit
和 .spec.podFailurePolicy
时,一旦 Job 满足任一策略,Job 控制器就会尊重终止策略,并忽略成功策略。Job 终止和清理
当 Job 完成时,不会再创建 Pod,但通常也不会删除 Pod。通常不会删除它们,因为这样可以使您仍然可以查看已完成 pod 的日志,以检查错误、警告或其他诊断输出。在 Job 完成后,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 的另一种方法是设置活动截止期限。为此,将 Job 的 .spec.activeDeadlineSeconds
字段设置为秒数。activeDeadlineSeconds
适用于 Job 的持续时间,无论创建了多少 Pod。一旦 Job 达到 activeDeadlineSeconds
,所有正在运行的 Pod 都会被终止,并且 Job 状态将变为 type: Failed
,其原因是 reason: DeadlineExceeded
。
请注意,Job 的 .spec.activeDeadlineSeconds
优先于 .spec.backoffLimit
。因此,一个正在重试一个或多个失败 Pod 的 Job,一旦达到 activeDeadlineSeconds
指定的时间限制,即使尚未达到 backoffLimit
,也不会部署额外的 Pod。
示例
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 规范和 Job 中的 Pod 模板规范 都具有 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.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 终结器后立即添加 Complete
或 Failed
Job 终端条件。但是,在添加终端条件时,某些 Pod 仍然处于运行或终止状态。
在 Kubernetes v1.31 及更高版本中,控制器只会在所有 Pod 都终止后才添加 Job 终端条件。您可以使用 JobManagedBy
或 JobPodReplacementPolicy
(默认情况下启用)功能网关 来启用此行为。
Job pod 的终止
Job 控制器将 FailureTarget
条件或 SuccessCriteriaMet
条件添加到 Job 中,以在 Job 满足成功或失败条件后触发 Pod 终止。
terminationGracePeriodSeconds
等因素可能会延长从 Job 控制器添加 FailureTarget
条件或 SuccessCriteriaMet
条件到所有 Job Pod 终止并 Job 控制器添加 终端条件(Failed
或 Complete
)的时间。
您可以使用 FailureTarget
或 SuccessCriteriaMet
条件来评估 Job 是否失败或成功,而无需等待控制器添加终端条件。
例如,您可能希望决定何时创建替换失败 Job 的替换 Job。如果在 FailureTarget
条件出现时替换失败的 Job,您的替换 Job 就会更快地运行,但可能会导致失败 Job 和替换 Job 的 Pod 同时运行,从而使用额外的计算资源。
或者,如果您的集群的资源容量有限,您可以选择等待 Failed
条件出现在 Job 上,这会延迟您的替换 Job,但会确保通过等待所有失败的 Pod 都删除来节省资源。
自动清理已完成的 Job
已完成的 Job 通常不再需要在系统中。将它们保留在系统中会给 API 服务器带来压力。如果 Job 是由更高层的控制器(例如 CronJobs)直接管理的,则 CronJobs 可以根据指定的基于容量的清理策略来清理 Job。
已完成 Job 的 TTL 机制
Kubernetes v1.23 [稳定]
自动清理已完成 Job(Complete
或 Failed
)的另一种方法是使用由 TTL 控制器 为已完成的资源提供的 TTL 机制,方法是指定 Job 的 .spec.ttlSecondsAfterFinished
字段。
当 TTL 控制器清理 Job 时,它将级联删除 Job,即删除其依赖对象,例如 Pod,以及 Job 本身。请注意,当 Job 被删除时,其生命周期保证(如终结器)将得到尊重。
例如
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
字段,因为未管理作业(您直接创建的作业,而不是通过其他工作负载 API(如 CronJob)间接创建的作业)的默认删除策略为orphanDependents
,导致未管理作业创建的 Pod 在作业完全删除后仍然存在。即使控制平面最终会回收已删除作业的 Pod(在这些 Pod 失败或完成之后),但有时这些残留的 Pod 可能会导致集群性能下降,最糟糕的情况下会导致集群因性能下降而离线。
作业模式
作业对象可用于处理一组独立但相关的工作项。这些工作项可能是要发送的电子邮件、要渲染的帧、要转码的文件、要扫描的 NoSQL 数据库中的键范围等等。
在复杂的系统中,可能存在多组不同的工作项。这里我们只考虑用户想要一起管理的一组工作项——批处理作业。
并行计算有几种不同的模式,每种模式都有其优缺点。权衡如下
- 每个工作项一个作业对象,与所有工作项一个作业对象。每个工作项一个作业会为用户和系统创建一些开销,以管理大量作业对象。对于大量工作项,所有工作项一个作业更好。
- 创建的 Pod 数量等于工作项数量,与每个 Pod 可以处理多个工作项。当 Pod 数量等于工作项数量时,Pod 通常需要对现有代码和容器进行较少的修改。对于大量工作项,每个 Pod 处理多个工作项更好。
- 几种方法使用工作队列。这需要运行队列服务,并修改现有程序或容器以使其使用工作队列。其他方法更容易适应现有的容器化应用程序。
- 当作业与无头服务关联时,您可以启用作业中的 Pod 之间进行通信,以便在计算中进行协作。
权衡总结如下,第 2 列到第 4 列对应于上述权衡。模式名称也是示例和更详细描述的链接。
模式 | 单个作业对象 | Pod 数量少于工作项数量? | 使用未修改的应用程序? |
---|---|---|---|
带 Pod 每工作项的队列 | ✓ | 有时 | |
带可变 Pod 数量的队列 | ✓ | ✓ | |
带静态工作分配的索引作业 | ✓ | ✓ | |
具有 Pod 到 Pod 通信的作业 | ✓ | 有时 | 有时 |
作业模板扩展 | ✓ |
当您使用.spec.completions
指定完成次数时,作业控制器创建的每个 Pod 都有一个相同的spec
。这意味着所有任务 Pod 将具有相同的命令行和相同的镜像、相同的卷,以及(几乎)相同的环境变量。这些模式是安排 Pod 处理不同事物的不同方式。
此表显示了每种模式的.spec.parallelism
和.spec.completions
所需的设置。这里,W
是工作项的数量。
模式 | .spec.completions | .spec.parallelism |
---|---|---|
带 Pod 每工作项的队列 | W | 任何 |
带可变 Pod 数量的队列 | null | 任何 |
带静态工作分配的索引作业 | W | 任何 |
具有 Pod 到 Pod 通信的作业 | W | W |
作业模板扩展 | 1 | 应为 1 |
高级用法
暂停作业
Kubernetes v1.24 [稳定]
创建作业后,作业控制器将立即开始创建 Pod 以满足作业的要求,并将继续这样做,直到作业完成。但是,您可能希望暂时暂停作业的执行并在以后恢复它,或者在暂停状态启动作业,并让自定义控制器稍后决定何时启动它们。
要暂停作业,您可以将作业的.spec.suspend
字段更新为 true;稍后,当您想要再次恢复它时,将其更新为 false。使用.spec.suspend
设置为 true 创建作业将以暂停状态创建它。
当作业从暂停状态恢复时,其.status.startTime
字段将重置为当前时间。这意味着当作业暂停和恢复时,.spec.activeDeadlineSeconds
计时器将停止并重置。
当您暂停作业时,任何状态不是Completed
的正在运行的 Pod 将使用 SIGTERM 信号终止。将尊重 Pod 的优雅终止期限,您的 Pod 必须在此期限内处理此信号。这可能涉及保存进度以备后用或撤销更改。以这种方式终止的 Pod 不会计入作业的completions
计数。
暂停状态下的示例作业定义如下
kubectl get job myjob -o yaml
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
suspend: true
parallelism: 1
completions: 5
template:
spec:
...
您还可以通过使用命令行修补作业来切换作业暂停。
暂停活动作业
kubectl patch job/myjob --type=strategic --patch '{"spec":{"suspend":true}}'
恢复暂停的作业
kubectl patch job/myjob --type=strategic --patch '{"spec":{"suspend":false}}'
可以使用作业状态来确定作业是否已暂停或过去是否已暂停。
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”的作业条件表示作业已暂停;lastTransitionTime
字段可用于确定作业已暂停了多长时间。如果该条件的状态为“False”,则作业以前已暂停,现在正在运行。如果作业状态中不存在此类条件,则该作业从未停止。
在作业暂停和恢复时也会创建事件。
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,但一旦作业恢复,Pod 创建就重新开始。
可变调度指令
Kubernetes v1.27 [stable]
在大多数情况下,并行作业希望 Pod 在运行时具有约束条件,例如,所有 Pod 都在同一个区域中,或者都在 GPU 模型 x 或 y 上,而不是两者混合。
暂停字段是实现这些语义的第一步。暂停允许自定义队列控制器决定何时启动作业;但是,一旦作业取消暂停,自定义队列控制器就无法影响作业的 Pod 实际着陆的位置。
此功能允许在作业开始之前更新作业的调度指令,这使自定义队列控制器能够影响 Pod 的放置,同时将实际的 Pod 到节点的分配卸载到 kube-scheduler。仅允许针对从未取消暂停过的暂停作业执行此操作。
可以在作业 Pod 模板中更新的字段包括节点亲和性、节点选择器、容忍度、标签、注释和调度门。
指定您自己的 Pod 选择器
通常,在创建作业对象时,您不会指定.spec.selector
。系统默认逻辑会在创建作业时添加此字段。它会选择一个选择器值,该值不会与任何其他作业重叠。
但是,在某些情况下,您可能需要覆盖此自动设置的选择器。要执行此操作,您可以指定作业的.spec.selector
。
在执行此操作时要非常小心。如果您指定了一个不特定于该作业 Pod 的标签选择器,并且它与无关的 Pod 匹配,则无关作业的 Pod 可能会被删除,或者此作业可能会将其他 Pod 计入其完成的 Pod,或者这两个作业都可能拒绝创建 Pod 或运行到完成。如果选择了非唯一选择器,则其他控制器(例如 ReplicationController)及其 Pod 也可能以不可预测的方式运行。Kubernetes 不会阻止您在指定.spec.selector
时犯错。
以下是一个可能需要使用此功能的示例。
假设作业old
已经运行。您希望现有 Pod 继续运行,但您希望它创建的其余 Pod 使用不同的 Pod 模板,并且作业具有新的名称。您无法更新作业,因为这些字段不可更新。因此,您删除了作业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
的新作业,并明确指定相同的选择器。由于现有 Pod 具有标签batch.kubernetes.io/controller-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002
,因此它们也受作业new
控制。
您需要在新的作业中指定manualSelector: true
,因为您没有使用系统通常自动为您生成的 selector。
kind: Job
metadata:
name: new
...
spec:
manualSelector: true
selector:
matchLabels:
batch.kubernetes.io/controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
...
新的作业本身将具有与a8f3d00d-c6d2-11e5-9f87-42010af00002
不同的 uid。设置manualSelector: true
告诉系统您知道自己在做什么,并允许这种不匹配。
使用终结器跟踪作业
Kubernetes v1.26 [stable]
控制平面会跟踪属于任何作业的 Pod,并注意到是否有任何此类 Pod 从 API 服务器中删除。为此,作业控制器会使用终结器batch.kubernetes.io/job-tracking
创建 Pod。控制器只会在 Pod 已计入作业状态后才删除终结器,从而允许其他控制器或用户删除 Pod。
注意
如果您观察到作业的 Pod 卡在跟踪终结器上,请参阅我的 Pod 停留在终止状态。弹性索引作业
Kubernetes v1.31 [stable]
您可以通过同时更改.spec.parallelism
和.spec.completions
来扩展或缩减索引作业,使.spec.parallelism == .spec.completions
。在缩减规模时,Kubernetes 会删除具有较高索引的 Pod。
弹性索引作业的用例包括需要扩展索引作业的批处理工作负载,例如 MPI、Horovord、Ray 和 PyTorch 训练作业。
延迟创建替换 Pod
Kubernetes v1.29 [beta]
默认情况下,作业控制器会在 Pod 失败或终止(具有删除时间戳)时立即重新创建 Pod。这意味着,在给定时间,当某些 Pod 正在终止时,作业的正在运行的 Pod 数量可能大于parallelism
,或者大于每个索引一个 Pod(如果您使用的是索引作业)。
您可以选择仅在终止 Pod 完全终止(具有 status.phase: Failed
)时创建替换 Pod。为此,请设置 .spec.podReplacementPolicy: Failed
。默认的替换策略取决于 Job 是否设置了 podFailurePolicy
。如果未为 Job 定义 Pod 失败策略,则省略 podReplacementPolicy
字段将选择 TerminatingOrFailed
替换策略:控制平面将在 Pod 删除后立即创建替换 Pod(只要控制平面看到此 Job 的 Pod 已设置 deletionTimestamp
)。对于设置了 Pod 失败策略的 Job,默认的 podReplacementPolicy
为 Failed
,并且不允许使用其他值。有关 Job 的 Pod 失败策略的更多信息,请参阅 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.30 [alpha]
此功能允许您为特定 Job 禁用内置 Job 控制器,并将 Job 的协调委托给外部控制器。
您可以通过为 spec.managedBy
字段设置自定义值(除 kubernetes.io/job-controller
之外的任何值)来指示协调 Job 的控制器。该字段的值是不可变的。
注意
使用此功能时,请确保已安装字段指示的控制器,否则 Job 可能根本无法协调。注意
在开发外部 Job 控制器时,请注意您的控制器需要以符合 Job 对象的 API 规范和状态字段定义的方式运行。
请在 Job API 中详细查看这些定义。我们还建议您运行 Job 对象的 e2e 符合性测试以验证您的实现。
最后,在开发外部 Job 控制器时,请确保它不使用 batch.kubernetes.io/job-tracking
终结器,该终结器是为内置控制器保留的。
警告
如果您正在考虑禁用JobManagedBy
功能门,或将集群降级到未启用功能门的版本,请检查是否有带有 spec.managedBy
字段自定义值的作业。如果存在此类作业,则存在操作后它们可能被两个控制器协调的风险:内置 Job 控制器和字段值指示的外部控制器。替代方案
裸 Pod
当 Pod 运行所在的节点重新启动或出现故障时,Pod 将被终止,并且不会重新启动。但是,Job 将创建新的 Pod 来替换已终止的 Pod。因此,我们建议您使用 Job 而不是裸 Pod,即使您的应用程序只需要一个 Pod。
复制控制器
Job 与 复制控制器 相辅相成。复制控制器管理不应终止的 Pod(例如,Web 服务器),而 Job 管理应终止的 Pod(例如,批处理任务)。
如 Pod 生命周期 中所述,Job
仅适用于 RestartPolicy
等于 OnFailure
或 Never
的 Pod。(注意:如果未设置 RestartPolicy
,则默认值为 Always
。)
单个 Job 启动控制器 Pod
另一种模式是单个 Job 创建一个 Pod,然后该 Pod 创建其他 Pod,充当这些 Pod 的自定义控制器。这提供了最大的灵活性,但可能在开始时比较复杂,并且与 Kubernetes 的集成度较低。
这种模式的一个例子是 Job 启动一个运行脚本的 Pod,该脚本反过来启动 Spark 主控制器(参见 spark 示例),运行 Spark 驱动程序,然后清理。
这种方法的一个优点是整个流程获得了 Job 对象的完成保证,但对创建哪些 Pod 以及如何将工作分配给它们保持完全控制。
下一步
- 了解有关 Pod 的信息。
- 阅读有关运行 Job 的不同方法
- 使用工作队列进行粗略并行处理
- 使用工作队列进行精细并行处理
- 使用 带索引的 Job 进行并行处理,并进行静态工作分配
- 根据模板创建多个 Job:使用扩展进行并行处理
- 按照 自动清理已完成的作业 中的链接,了解有关集群如何清理已完成和/或失败的任务的更多信息。
Job
是 Kubernetes REST API 的一部分。阅读 Job 对象定义,以了解有关作业的 API。- 阅读有关
CronJob
的信息,您可以使用它来定义一系列作业,这些作业将根据时间表运行,类似于 UNIX 工具cron
。 - 练习如何根据分步 示例,使用
podFailurePolicy
配置可重试和不可重试 Pod 失败的处理方式。