作业
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
此处,选择器与 Job 的选择器相同。--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 模板。它与Pod的架构完全相同,只是它是嵌套的,并且没有 apiVersion
或 kind
。
除了 Pod 的必需字段外,Job 中的 Pod 模板还必须指定适当的标签(请参阅Pod 选择器)和适当的重启策略。
仅允许RestartPolicy
等于 Never
或 OnFailure
。
Pod 选择器
.spec.selector
字段是可选的。在几乎所有情况下,您都不应该指定它。请参阅指定您自己的 Pod 选择器部分。
Job 的并行执行
适合作为 Job 运行的任务主要有三种类型
- 非并行 Job
- 通常,除非 Pod 失败,否则只启动一个 Pod。
- Job 在其 Pod 成功终止后立即完成。
- 具有*固定完成计数*的并行 Job
- 为
.spec.completions
指定一个非零正值。 - Job 表示整个任务,并在有
.spec.completions
个成功的 Pod 时完成。 - 使用
.spec.completionMode="Indexed"
时,每个 Pod 在 0 到.spec.completions-1
的范围内获得不同的索引。
- 为
- 具有*工作队列*的并行 Job
- 不要指定
.spec.completions
,默认为.spec.parallelism
。 - Pod 必须相互协调或与外部服务协调,以确定每个 Pod 应该处理什么工作。例如,Pod 可以从工作队列中获取最多 N 个项目的批次。
- 每个 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 [稳定]
具有*固定完成计数*的 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)
。当您将索引作业与 服务 结合使用时,作业中的 Pod 可以使用确定性主机名通过 DNS 相互寻址。有关如何配置此功能的更多信息,请参阅具有 Pod 间通信的作业。 - 从容器化任务中,在环境变量
JOB_COMPLETION_INDEX
中。
当每个索引都存在一个成功完成的 Pod 时,该作业被视为已完成。有关如何使用此模式的更多信息,请参阅用于静态工作分配的并行处理的索引作业。
- 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 失败时,作业控制器会启动一个新的 Pod。这意味着您的应用程序需要处理在新 Pod 中重新启动的情况。特别是,它需要处理由先前运行引起的临时文件、锁、不完整输出等。
默认情况下,每个 Pod 故障都会计入 .spec.backoffLimit
限制,请参阅Pod 退避失败策略。但是,您可以通过设置作业的Pod 失败策略来自定义 Pod 故障的处理方式。
此外,您可以选择通过设置 .spec.backoffLimitPerIndex
字段来独立计算索引作业的每个索引的 Pod 故障次数(有关更多信息,请参阅每个索引的退避限制)。
请注意,即使您指定了 .spec.parallelism = 1
、.spec.completions = 1
和 .spec.template.spec.restartPolicy = "Never"
,同一个程序有时也可能会启动两次。
如果您确实指定了 .spec.parallelism
和 .spec.completions
都大于 1,则可能会有多个 Pod 同时运行。因此,您的 Pod 还必须能够容忍并发性。
当功能门控 PodDisruptionConditions
和 JobPodFailurePolicy
都启用,并且设置了 .spec.podFailurePolicy
字段时,作业控制器不会将终止 Pod(已设置 .metadata.deletionTimestamp
字段的 Pod)视为失败,直到该 Pod 终止(其 .status.phase
为 Failed
或 Succeeded
)。但是,作业控制器会在终止变得明显时立即创建一个替换 Pod。一旦 Pod 终止,作业控制器就会评估相关作业的 .backoffLimit
和 .podFailurePolicy
,并将此现已终止的 Pod 考虑在内。
如果其中任何一个要求不满足,作业控制器会将终止 Pod 视为立即失败,即使该 Pod 稍后以 phase: "Succeeded"
终止。
Pod 退避失败策略
在某些情况下,您希望在经过一定次数的重试后使作业失败,原因是配置中存在逻辑错误等。为此,请设置 .spec.backoffLimit
以指定在将作业视为失败之前的重试次数。默认情况下,退避限制设置为 6。与作业关联的失败 Pod 由作业控制器使用指数退避延迟(10 秒、20 秒、40 秒...)重新创建,上限为六分钟。
重试次数的计算方式有两种
.status.phase = "Failed"
的 Pod 数量。- 使用
restartPolicy = "OnFailure"
时,.status.phase
等于Pending
或Running
的 Pod 的所有容器中的重试次数。
如果任一计算结果达到 .spec.backoffLimit
,则该作业被视为失败。
注意
如果您的作业的restartPolicy = "OnFailure"
,请记住,一旦达到作业退避限制,运行该作业的 Pod 将被终止。这可能会使调试作业的可执行文件变得更加困难。我们建议在调试作业或使用日志系统时设置 restartPolicy = "Never"
,以确保不会无意中丢失来自失败作业的输出。每个索引的退避限制
Kubernetes v1.29 [测试版]
当您运行索引作业时,您可以选择独立处理每个索引的 Pod 故障重试。为此,请设置 .spec.backoffLimitPerIndex
以指定每个索引的最大 Pod 故障次数。
当某个索引的每个索引退避限制被超过时,Kubernetes 会将该索引视为失败,并将其添加到 .status.failedIndexes
字段中。无论您是否设置了 backoffLimitPerIndex
字段,成功执行 Pod 的索引(即成功的索引)都会记录在 .status.completedIndexes
字段中。
请注意,失败的索引不会中断其他索引的执行。一旦您指定了每个索引的退避限制的作业的所有索引都完成,如果其中至少有一个索引确实失败了,作业控制器会通过在状态中设置 Failed 条件来将整个作业标记为失败。即使某些索引(可能是几乎所有索引)都已成功处理,该作业也会被标记为失败。
您还可以通过设置 .spec.maxFailedIndexes
字段来限制标记为失败的索引的最大数量。当失败索引的数量超过 maxFailedIndexes
字段时,作业控制器会触发终止该作业的所有剩余运行 Pod。一旦所有 Pod 都终止,作业控制器就会通过在作业状态中设置 Failed 条件来将整个作业标记为失败。
下面是一个定义了 backoffLimitPerIndex
的作业的清单示例
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)
在上面的示例中,作业控制器允许每个索引重新启动一次。当失败索引的总数超过 5 时,整个作业将终止。
作业完成后,作业状态如下所示
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: Failed
此外,您可能希望将每个索引的退避与Pod 失败策略一起使用。使用每个索引的退避时,可以使用一个新的 FailIndex
操作,它允许您避免索引内的不必要重试。
Pod 失败策略
Kubernetes v1.26 [测试版]
注意
只有在您的集群中启用了JobPodFailurePolicy
功能门控 时,才能为作业配置 Pod 失败策略。此外,建议启用 PodDisruptionConditions
功能门控,以便能够检测和处理 Pod 失败策略中的 Pod 中断条件(另请参阅:Pod 中断条件)。这两个功能门控在 Kubernetes 1.30 中都可用。使用 .spec.podFailurePolicy
字段定义的 Pod 失败策略使您的集群能够根据容器退出代码和 Pod 条件来处理 Pod 故障。
在某些情况下,您可能希望在处理 Pod 故障时比Pod 退避失败策略(基于作业的 .spec.backoffLimit
)提供的控制更精细。以下是一些用例示例
- 为了通过避免不必要的 Pod 重新启动来优化运行工作负载的成本,您可以在其 Pod 之一因指示软件错误的退出代码而失败时立即终止作业。
- 为了确保您的作业即使在出现中断时也能完成,您可以忽略由中断(例如抢占、API 发起的驱逐或污点驱逐)引起的 Pod 故障,这样它们就不会计入
.spec.backoffLimit
重试限制。
您可以在 .spec.podFailurePolicy
字段中配置 Pod 失败策略,以满足上述用例。此策略可以根据容器退出代码和 Pod 条件来处理 Pod 故障。
下面是一个定义了 podFailurePolicy
的作业的清单示例
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 退出代码失败,则应将作业标记为失败。以下是专门针对 main
容器的规则
- 退出代码 0 表示容器成功
- 退出代码 42 表示整个作业失败
- 任何其他退出代码都表示容器失败,因此整个 Pod 也失败。如果重启总数低于
backoffLimit
,则 Pod 将重新创建。如果达到backoffLimit
,则整个作业失败。
注意
因为 Pod 模板指定了restartPolicy: Never
,所以 kubelet 不会重启该特定 Pod 中的 main
容器。Pod 失败策略的第二条规则为条件为 DisruptionTarget
的失败 Pod 指定了 Ignore
操作,该规则不将 Pod 中断计入 .spec.backoffLimit
重试限制。
注意
如果作业失败(无论是由于 Pod 失败策略还是 Pod 退避失败策略),并且作业正在运行多个 Pod,则 Kubernetes 会终止该作业中所有仍处于 Pending 或 Running 状态的 Pod。以下是 API 的一些要求和语义
- 如果要对作业使用
.spec.podFailurePolicy
字段,则还必须使用.spec.restartPolicy
设置为Never
来定义该作业的 Pod 模板。 - 您在
spec.podFailurePolicy.rules
下指定的 Pod 失败策略规则将按顺序进行评估。一旦某个规则与 Pod 失败匹配,则忽略其余规则。当没有规则与 Pod 失败匹配时,将应用默认处理。 - 您可能希望通过在
spec.podFailurePolicy.rules[*].onExitCodes.containerName
中指定其名称来将规则限制为特定容器。如果未指定,则该规则适用于所有容器。如果指定,则它应与 Pod 模板中的一个容器或initContainer
名称匹配。 - 您可以通过
spec.podFailurePolicy.rules[*].action
指定在匹配 Pod 失败策略时采取的操作。可能的值为FailJob
:用于指示应将 Pod 的作业标记为失败,并且应终止所有正在运行的 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 的终结器由作业控制器删除。注意
从 Kubernetes v1.28 开始,当使用 Pod 失败策略时,作业控制器仅在这些 Pod 达到终止Failed
阶段时才重新创建终止中的 Pod。此行为类似于 podReplacementPolicy: Failed
。有关更多信息,请参阅Pod 替换策略。成功策略
Kubernetes v1.30 [alpha]
创建索引作业时,您可以根据成功的 pod,使用 .spec.successPolicy
定义何时可以将作业声明为成功。
默认情况下,当成功的 Pod 数量等于 .spec.completions
时,作业成功。在某些情况下,您可能希望对声明作业成功进行额外的控制
- 使用不同的参数运行模拟时,您可能不需要所有模拟都成功才能使整个作业成功。
- 遵循领导者-工作者模式时,只有领导者的成功才能决定作业的成功或失败。例如 MPI 和 PyTorch 等框架。
您可以在 .spec.successPolicy
字段中配置成功策略,以满足上述用例。此策略可以根据成功的 pod 处理作业成功。作业满足成功策略后,作业控制器将终止延迟的 Pod。成功策略由规则定义。每条规则可以采用以下形式之一
- 当您仅指定
succeededIndexes
时,一旦succeededIndexes
中指定的所有索引都成功,作业控制器就会将作业标记为成功。succeededIndexes
必须是 0 到.spec.completions-1
之间的间隔列表。 - 当您仅指定
succeededCount
时,一旦成功的索引数达到succeededCount
,作业控制器就会将作业标记为成功。 - 当您同时指定
succeededIndexes
和succeededCount
时,一旦succeededIndexes
中指定的索引子集中成功的索引数达到succeededCount
,作业控制器就会将作业标记为成功。
请注意,当您在 .spec.successPolicy.rules
中指定多条规则时,作业控制器会按顺序评估这些规则。一旦作业满足一条规则,作业控制器就会忽略其余规则。
以下是具有 successPolicy
的作业的清单
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 中的任何一个成功时,作业控制器会将作业标记为成功并终止延迟的 Pod。满足成功策略的作业将获得 SuccessCriteriaMet
条件。在发出删除延迟 Pod 的指令后,作业将获得 Complete
条件。
请注意,succeededIndexes
表示为由连字符分隔的间隔。数字由序列的第一个和最后一个元素表示,并用连字符分隔。
注意
当您同时指定成功策略和一些终止策略(例如.spec.backoffLimit
和 .spec.podFailurePolicy
)时,一旦作业满足其中任何一个策略,作业控制器就会遵循终止策略并忽略成功策略。作业终止和清理
当作业完成时,不会再创建 Pod,但 Pod 通常 也不会被删除。保留它们可以让您仍然查看已完成 pod 的日志,以检查错误、警告或其他诊断输出。作业对象在其完成后也会保留,以便您可以查看其状态。用户可以在记下其状态后删除旧作业。使用 kubectl
删除作业(例如 kubectl delete jobs/pi
或 kubectl delete -f ./job.yaml
)。当您使用 kubectl
删除作业时,它创建的所有 pod 也会被删除。
默认情况下,作业将不间断地运行,除非 Pod 失败(restartPolicy=Never
)或容器出错退出(restartPolicy=OnFailure
),此时作业将遵循上述 .spec.backoffLimit
。一旦达到 .spec.backoffLimit
,作业将被标记为失败,并且任何正在运行的 Pod 都将被终止。
另一种终止作业的方法是设置活动截止时间。为此,请将作业的 .spec.activeDeadlineSeconds
字段设置为秒数。activeDeadlineSeconds
适用于作业的持续时间,无论创建了多少个 Pod。一旦作业达到 activeDeadlineSeconds
,其所有正在运行的 Pod 都将被终止,并且作业状态将变为 type: Failed
,原因是 reason: DeadlineExceeded
。
请注意,作业的 .spec.activeDeadlineSeconds
优先于其 .spec.backoffLimit
。因此,即使尚未达到 backoffLimit
,正在重试一个或多个失败 Pod 的作业也不会在达到 activeDeadlineSeconds
指定的时间限制后部署其他 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
请注意,作业规范和作业中的Pod 模板规范都有一个 activeDeadlineSeconds
字段。请确保在适当的级别设置此字段。
请记住,restartPolicy
适用于 Pod,而不适用于作业本身:一旦作业状态为 type: Failed
,就不会自动重启作业。也就是说,使用 .spec.activeDeadlineSeconds
和 .spec.backoffLimit
激活的作业终止机制会导致永久性作业失败,需要手动干预才能解决。
自动清理已完成的作业
已完成的作业通常在系统中不再需要。将它们保留在系统中会给 API 服务器带来压力。如果作业由更高级别的控制器(例如CronJobs)直接管理,则 CronJobs 可以根据指定的基于容量的清理策略清理作业。
已完成作业的 TTL 机制
Kubernetes v1.23 [stable]
另一种自动清理已完成作业(Complete
或 Failed
)的方法是使用TTL 控制器为已完成资源提供的 TTL 机制,方法是指定作业的 .spec.ttlSecondsAfterFinished
字段。
当 TTL 控制器清理作业时,它将级联删除作业,即删除其依赖对象(例如 Pod)以及作业。请注意,删除作业时,将遵循其生命周期保证,例如终结器。
例如
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
作业 pi-with-ttl
将在其完成后 100
秒后自动删除。
如果该字段设置为 0
,则作业将在完成后立即自动删除。如果未设置该字段,则此作业在完成后不会被 TTL 控制器清理。
注意
建议设置 ttlSecondsAfterFinished
字段,因为非托管作业(直接创建的作业,而不是通过其他工作负载 API(如 CronJob)间接创建的作业)的默认删除策略为 orphanDependents
,这会导致由非托管作业创建的 Pod 在该作业完全删除后被保留。即使 控制平面 最终会 垃圾回收 已删除作业中已失败或已完成的 Pod,但有时这些残留的 Pod 可能会导致集群性能下降,或者在最坏的情况下,由于这种下降而导致集群脱机。
您可以使用 LimitRanges 和 ResourceQuotas 来限制特定命名空间可以消耗的资源量。
作业模式
Job 对象可用于处理一组独立但相关的 *工作项*。这些工作项可能是要发送的电子邮件、要渲染的帧、要转码的文件、要扫描的 NoSQL 数据库中的键范围,等等。
在一个复杂的系统中,可能有多组不同的工作项。这里我们只考虑用户想要一起管理的一组工作项,即 *批处理作业*。
并行计算有几种不同的模式,每种模式都有其优点和缺点。权衡取舍如下:
- 每个工作项一个 Job 对象,还是所有工作项一个 Job 对象。每个工作项一个 Job 对象会给用户和系统带来一些开销,难以管理大量的 Job 对象。对于大量的工作项,所有工作项使用一个 Job 对象会更好。
- 创建的 Pod 数量等于工作项数量,还是每个 Pod 可以处理多个工作项。当 Pod 数量等于工作项数量时,Pod 通常需要对现有代码和容器进行较少的修改。让每个 Pod 处理多个工作项更适合于大量的工作项。
- 几种方法使用工作队列。这需要运行队列服务,并修改现有程序或容器以使其使用工作队列。其他方法更容易适应现有的容器化应用程序。
- 当 Job 与 无头服务 关联时,您可以使 Job 中的 Pod 能够相互通信以协作进行计算。
权衡取舍总结如下,第 2 列到第 4 列对应于上述权衡取舍。模式名称也是示例和更详细说明的链接。
模式 | 单个 Job 对象 | Pod 数量少于工作项数量? | 使用未修改的应用程序? |
---|---|---|---|
每个工作项一个 Pod 的队列 | ✓ | 有时 | |
可变 Pod 数量的队列 | ✓ | ✓ | |
具有静态工作分配的索引作业 | ✓ | ✓ | |
具有 Pod 间通信的作业 | ✓ | 有时 | 有时 |
作业模板扩展 | ✓ |
当您使用 .spec.completions
指定完成次数时,Job 控制器创建的每个 Pod 都有一个相同的 spec
。这意味着任务的所有 Pod 都将具有相同的命令行和相同的镜像、相同的卷以及(几乎)相同的环境变量。这些模式是安排 Pod 处理不同事物的不同方式。
此表显示了每种模式所需的 .spec.parallelism
和 .spec.completions
设置。这里,W
是工作项的数量。
模式 | .spec.completions | .spec.parallelism |
---|---|---|
每个工作项一个 Pod 的队列 | W | 任意 |
可变 Pod 数量的队列 | null | 任意 |
具有静态工作分配的索引作业 | W | 任意 |
具有 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 [稳定]
在大多数情况下,并行作业希望 Pod 在约束条件下运行,例如都在同一个区域中,或者都在 GPU 模型 x 或 y 上运行,但不能混合使用。
suspend 字段是实现这些语义的第一步。Suspend 允许自定义队列控制器决定何时启动作业;但是,一旦作业取消暂停,自定义队列控制器就无法影响作业的 Pod 实际落在哪里。
此功能允许在作业启动之前更新其调度指令,这使自定义队列控制器能够影响 Pod 放置,同时将实际的 Pod 到节点分配卸载到 kube-scheduler。这仅适用于之前从未取消暂停的暂停作业。
作业的 Pod 模板中可以更新的字段是节点关联性、节点选择器、容忍度、标签、注释和 调度门控。
指定您自己的 Pod 选择器
通常,当您创建 Job 对象时,您不会指定 .spec.selector
。系统默认逻辑会在创建 Job 时添加此字段。它会选择一个与任何其他作业都不重叠的选择器值。
但是,在某些情况下,您可能需要覆盖此自动设置的选择器。为此,您可以指定 Job 的 .spec.selector
。
执行此操作时要非常小心。如果您指定的标签选择器对于该作业的 Pod 来说不是唯一的,并且与不相关的 Pod 匹配,则可能会删除不相关作业的 Pod,或者此作业可能会将其他 Pod 计为已完成,或者一个或两个作业都可能拒绝创建 Pod 或运行到完成。如果选择了非唯一选择器,则其他控制器(例如 ReplicationController)及其 Pod 的行为也可能无法预测。当您指定 .spec.selector
时,Kubernetes 不会阻止您犯错。
以下是在这种情况下您可能想要使用此功能的示例。
假设作业 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
,因为您使用的不是系统通常自动为您生成的选择器。
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 [稳定]
控制平面会跟踪属于任何作业的 Pod,并注意是否有任何此类 Pod 从 API 服务器中删除。为此,作业控制器使用终结器 batch.kubernetes.io/job-tracking
创建 Pod。控制器仅在作业状态中已考虑 Pod 后才删除终结器,从而允许其他控制器或用户删除 Pod。
注意
如果您观察到作业中的 Pod 卡在跟踪终结器上,请参阅 我的 Pod 一直处于终止状态。弹性索引作业
Kubernetes v1.27 [测试版]
您可以通过同时改变 .spec.parallelism
和 .spec.completions
来向上或向下扩展索引作业,以便 .spec.parallelism == .spec.completions
。当 API 服务器 上的 ElasticIndexedJob
功能门控 被禁用时,.spec.completions
是不可变的。
弹性索引作业的用例包括需要扩展索引作业的批处理工作负载,例如 MPI、Horovord、Ray 和 PyTorch 训练作业。
延迟创建替换 Pod
Kubernetes v1.29 [测试版]
默认情况下,Job 控制器会在 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
,并且不允许使用其他值。有关 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
字段设置自定义值来指示协调 Job 的控制器 - 任何非 kubernetes.io/job-controller
的值。该字段的值是不可变的。
注意
使用此功能时,请确保已安装该字段指示的控制器,否则 Job 可能根本无法协调。注意
在开发外部 Job 控制器时,请注意,您的控制器需要以符合 Job 对象的 API 规范和状态字段定义的方式运行。
请在Job API中详细查看这些内容。我们还建议您运行 Job 对象的 e2e 一致性测试以验证您的实现。
最后,在开发外部 Job 控制器时,请确保它不使用为内置控制器保留的 batch.kubernetes.io/job-tracking
终结器。
警告
如果您正在考虑禁用JobManagedBy
特性门控,或者将集群降级到未启用该特性门控的版本,请检查是否存在 spec.managedBy
字段具有自定义值的 Job。如果存在此类 Job,则在操作后,它们可能会由两个控制器协调:内置 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,该 Pod 运行一个脚本,该脚本依次启动一个 Spark 主控制器(请参阅spark 示例),运行一个 Spark 驱动程序,然后清理。
这种方法的一个优点是,整个过程获得了 Job 对象的完成保证,但保持对创建哪些 Pod 以及如何将工作分配给它们的完全控制。
下一步
- 了解Pod。
- 阅读有关运行 Job 的不同方法
- 使用工作队列进行粗粒度并行处理
- 使用工作队列进行细粒度并行处理
- 使用索引 Job 进行静态工作分配的并行处理
- 根据模板创建多个 Job:使用扩展进行并行处理
- 点击自动清理已完成的 Job中的链接,详细了解您的集群如何清理已完成和/或失败的任务。
Job
是 Kubernetes REST API 的一部分。阅读Job对象定义以了解 Job 的 API。- 阅读有关
CronJob
的信息,您可以使用它来定义一系列将根据计划运行的 Job,类似于 UNIX 工具cron
。 - 根据分步示例,练习如何使用
podFailurePolicy
配置可重试和不可重试的 Pod 失败的处理。