作业

作业(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

这里,选择器与 Job 的选择器相同。 --output=jsonpath 选项指定了一个表达式,其中包含返回列表中每个 Pod 的名称。

查看其中一个 Pod 的标准输出

kubectl logs $pods

查看 Job 日志的另一种方式

kubectl logs jobs/pi

输出类似于:

3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901

编写 Job 规约

与其他所有 Kubernetes 配置一样,Job 需要 apiVersionkindmetadata 字段。

当控制平面为 Job 创建新的 Pod 时,Job 的 .metadata.name 是命名这些 Pod 的基础部分。Job 的名称必须是一个有效的 DNS 子域名值,但这可能会导致 Pod 主机名出现意外结果。为了获得最佳兼容性,名称应遵循更严格的 DNS 标签规则。即使名称是 DNS 子域名,其长度也必须不超过 63 个字符。

Job 还需要一个 .spec 部分

Job 标签

Job 标签将带有 batch.kubernetes.io/ 前缀,用于 job-namecontroller-uid

Pod 模板

.spec.template.spec 中唯一必需的字段。

.spec.template 是一个 Pod 模板。它与 Pod 具有完全相同的架构,只是它是嵌套的,并且没有 apiVersionkind

除了 Pod 的必需字段外,Job 中的 Pod 模板还必须指定适当的标签(请参阅Pod 选择器)和适当的重启策略。

只允许 RestartPolicy 等于 NeverOnFailure

Pod 选择器

.spec.selector 字段是可选的。在几乎所有情况下,你都不应该指定它。请参阅 指定你自己的 Pod 选择器一节。

Job 的并行执行

有三种主要类型的任务适合作为 Job 运行

  1. 非并行 Job
    • 通常只启动一个 Pod,除非 Pod 失败。
    • 一旦其 Pod 成功终止,Job 即完成。
  2. 具有**固定完成计数**的并行 Job
    • .spec.completions 指定一个非零正值。
    • Job 代表整体任务,当有 .spec.completions 个成功 Pod 时完成。
    • 当使用 .spec.completionMode="Indexed" 时,每个 Pod 会在 0 到 .spec.completions-1 范围内获得一个不同的索引。
  3. 具有**工作队列**的并行 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 [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 相互通信。有关如何配置此功能的更多信息,请参阅 Pod 间通信的 Job
    • 从容器化任务中,通过环境变量 JOB_COMPLETION_INDEX

    当每个索引有一个成功完成的 Pod 时,Job 被认为是完成的。有关如何使用此模式的更多信息,请参阅 带静态工作分配的索引 Job 进行并行处理

处理 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 的 .spec.backoffLimitPerIndex 字段,独立计算 索引式 Job 的每个索引的 Pod 失败次数(更多信息请参阅 每个索引的重试限制)。

请注意,即使你指定 .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.phaseFailedSucceeded)。但是,Job 控制器会在终止变得明显时立即创建一个替换 Pod。一旦 Pod 终止,Job 控制器会评估相关 Job 的 .backoffLimit.podFailurePolicy,并将此已终止的 Pod 考虑在内。

如果这些要求中的任何一个未满足,Job 控制器会将正在终止的 Pod 视为立即失败,即使该 Pod 随后以 phase: "Succeeded" 终止。

Pod 重试失败策略

在某些情况下,你希望在由于配置中的逻辑错误等原因多次重试后使 Job 失败。为此,请设置 .spec.backoffLimit 以指定在 Job 被视为失败之前重试的次数。

默认情况下,.spec.backoffLimit 设置为 6,除非指定了 每个索引的退避限制(仅限索引 Job)。当指定 .spec.backoffLimitPerIndex 时,.spec.backoffLimit 默认为 2147483647(MaxInt32)。

Job 控制器会以指数退避延迟(10 秒、20 秒、40 秒…)重新创建与 Job 关联的失败 Pod,上限为 6 分钟。

重试次数通过两种方式计算

  • .status.phase = "Failed" 的 Pod 数量。
  • 当使用 restartPolicy = "OnFailure" 时,所有 .status.phase 等于 PendingRunning 的 Pod 中所有容器的重试次数。

如果任一计算达到 .spec.backoffLimit,则 Job 被视为失败。

每个索引的重试限制

特性状态: Kubernetes v1.33 [stable] (默认启用:true)

当你运行索引式 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 标记为失败,方法是在 Job 状态中设置 Failed 条件。

这是一个定义了 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 将被终止。

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 Pods 终止后,Job 控制器会添加 Failed 条件,其 reasonmessage 值与 FailureTarget Job 条件相同。有关详细信息,请参阅 Job Pod 的终止

此外,你可能希望将按索引退避与 Pod 失败策略结合使用。使用按索引退避时,有一个新的 FailIndex 动作可用,允许你避免索引内不必要的重试。

Pod 失败策略

特性状态: Kubernetes v1.31 [stable] (默认启用:true)

Pod 失败策略由 .spec.podFailurePolicy 字段定义,它使你的集群能够根据容器退出代码和 Pod 条件来处理 Pod 失败。

在某些情况下,你可能希望在处理 Pod 故障时拥有比基于 Job 的 .spec.backoffLimitPod 重试失败策略所提供的控制更精细的控制。以下是一些用例示例

  • 为了优化运行工作负载的成本,你可以通过在 Pod 因指示软件错误的退出代码失败时立即终止 Job,从而避免不必要的 Pod 重启。
  • 为了保证你的 Job 即使在出现中断时也能完成,你可以忽略由中断引起的 Pod 失败(例如 抢占API 发起的逐出或基于污点的逐出),这样它们就不会计入 .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 失败策略的第二条规则,为条件为 DisruptionTarget 的失败 Pod 指定 Ignore 动作,将 Pod 中断排除在 .spec.backoffLimit 的重试限制之外。

以下是 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 的作业应被标记为失败,并且所有正在运行的 Pod 应被终止。
    • Ignore:用于指示不应增加 .spec.backoffLimit 的计数器,并且应创建替换 Pod。
    • Count:用于指示 Pod 应该以默认方式处理。.spec.backoffLimit 的计数器应该增加。
    • FailIndex:将此动作与每个索引的退避限制结合使用,以避免失败 Pod 索引内不必要的重试。

当你使用 podFailurePolicy,并且 Job 由于 Pod 匹配带有 FailJob 动作的规则而失败时,Job 控制器会通过添加 FailureTarget 条件来触发 Job 终止过程。有关更多详细信息,请参阅 Job 终止和清理

成功策略

创建 Indexed Job 时,你可以使用 .spec.successPolicy 根据成功的 Pods 来定义 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 标记为成功。
  • 当你同时指定 succeededIndexessucceededCount 时,一旦 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

在上面的示例中,succeededIndexessucceededCount 都已指定。因此,当指定的索引 0、2 或 3 中的任何一个成功时,Job 控制器将把 Job 标记为成功并终止其余的 Pod。满足成功策略的 Job 将获得 SuccessCriteriaMet 条件,其 SuccessPolicy 原因是 SuccessPolicy。发出移除剩余 Pod 的命令后,Job 将获得 Complete 条件。

请注意,succeededIndexes 表示为由连字符分隔的区间。数字由序列的第一个和最后一个元素表示,并用连字符分隔。

Job 终止与清理

当 Job 完成后,不再创建 Pod,但 Pod 通常也不会被删除。保留它们允许你仍然查看已完成 Pod 的日志,以检查错误、警告或其他诊断输出。Job 对象在完成后也仍然存在,以便你可以查看其状态。用户有责任在记录其状态后删除旧的 Job。使用 kubectl 删除 Job(例如 kubectl delete jobs/pikubectl 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 指定的时间限制后将不再部署额外的 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 规约和 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 控制器会延迟添加终止条件 FailedComplete,直到所有 Job Pods 终止。

在 Kubernetes v1.30 及更早版本中,Job 控制器会在 Job 终止过程触发且所有 Pod 终结器被移除后立即添加 CompleteFailed Job 终止条件。但是,在添加终止条件时,某些 Pod 仍可能正在运行或正在终止。

在 Kubernetes v1.31 及更高版本中,控制器**只有在**所有 Pod 终止后才添加 Job 终止条件。你可以通过使用 JobManagedByJobPodReplacementPolicy(两者默认启用)特性门控来控制此行为。

Job Pod 的终止

当 Job 满足成功或失败条件后,Job 控制器会向 Job 添加 FailureTarget 条件或 SuccessCriteriaMet 条件,以触发 Pod 终止。

诸如 terminationGracePeriodSeconds 之类的因素可能会增加从 Job 控制器添加 FailureTarget 条件或 SuccessCriteriaMet 条件到所有 Job Pod 终止且 Job 控制器添加 终止条件 (FailedComplete) 的时间量。

你可以使用 FailureTargetSuccessCriteriaMet 条件来评估 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,即连同 Job 一起删除其依赖对象,例如 Pod。请注意,当 Job 被删除时,其生命周期保证(例如 finalizers)将被遵守。

例如

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 控制器清理。

Job 模式

Job 对象可用于处理一组独立但相关的**工作项**。这些工作项可能是要发送的电子邮件、要渲染的帧、要转码的文件、要扫描的 NoSQL 数据库中的键范围等。

在一个复杂的系统中,可能存在多个不同的工作项集。这里我们只考虑用户希望一起管理的一个工作项集——一个**批处理作业**。

并行计算有几种不同的模式,每种模式都有其优点和缺点。权衡是

  • 每个工作项一个 Job 对象,与所有工作项一个 Job 对象。每个工作项一个 Job 会给用户和系统带来一些管理大量 Job 对象的开销。对于大量项目,所有工作项一个 Job 更好。
  • 创建的 Pod 数量等于工作项数量,与每个 Pod 可以处理多个工作项。当 Pod 数量等于工作项数量时,Pod 通常需要较少修改现有代码和容器。对于大量项目,每个 Pod 处理多个工作项更好。
  • 有几种方法使用工作队列。这需要运行一个队列服务,并修改现有程序或容器以使其使用工作队列。其他方法更容易适应现有的容器化应用程序。
  • 当 Job 与 无头服务关联时,你可以使 Job 中的 Pod 相互通信以协作进行计算。

这里总结了这些权衡,其中第 2 到 4 列对应于上述权衡。模式名称也是示例和更详细描述的链接。

模式单个 Job 对象Pod 数量少于工作项?使用未修改的应用程序?
队列模式(每个工作项一个 Pod)有时
队列模式(可变 Pod 数量)
带静态工作分配的索引 Job
带 Pod-to-Pod 通信的作业有时有时
Job 模板扩展

当你使用 .spec.completions 指定完成次数时,Job 控制器创建的每个 Pod 都具有相同的 spec。这意味着任务的所有 Pod 都将具有相同的命令行和相同的镜像、相同的卷以及(几乎)相同的环境变量。这些模式是安排 Pod 完成不同工作的不同方式。

此表显示了每种模式的 .spec.parallelism.spec.completions 所需设置。其中,W 是工作项的数量。

模式.spec.completions.spec.parallelism
队列模式(每个工作项一个 Pod)W任何
队列模式(可变 Pod 数量)任何
带静态工作分配的索引 JobW任何
带 Pod-to-Pod 通信的作业WW
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:
      ...

你还可以通过使用命令行修补 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 [稳定]

在大多数情况下,并行作业会希望 Pod 在受到限制的情况下运行,例如都在同一个区域,或都只在 GPU 模型 x 或 y 上,而不是两者混合。

挂起字段是实现这些语义的第一步。挂起允许自定义队列控制器决定何时启动作业;然而,一旦作业被取消挂起,自定义队列控制器对作业的 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 计为完成,或者一个或两个 Job 可能拒绝创建 Pod 或运行到完成。如果选择了非唯一的选择器,那么其他控制器(例如 ReplicationController)及其 Pod 也可能以不可预测的方式运行。Kubernetes 不会在你指定 .spec.selector 时阻止你犯错。

以下是一个你可能希望使用此功能的示例。

假设 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 会告诉系统你知道自己在做什么,并允许这种不匹配。

使用终结器跟踪 Job

特性状态: Kubernetes v1.26 [稳定]

控制平面会跟踪属于任何 Job 的 Pod,并注意是否有任何此类 Pod 从 API 服务器中移除。为此,Job 控制器会创建带有终结器 batch.kubernetes.io/job-tracking 的 Pod。控制器仅在 Pod 在 Job 状态中被记录后才移除终结器,从而允许其他控制器或用户移除 Pod。

弹性索引 Job

特性状态: Kubernetes v1.31 [stable] (默认启用:true)

你可以通过同时修改 .spec.parallelism.spec.completions 来扩展或收缩 Indexed Job,使得 .spec.parallelism == .spec.completions。在缩容时,Kubernetes 会移除索引较高的 Pod。

弹性索引 Job 的用例包括需要扩缩索引 Job 的批处理工作负载,例如 MPI、Horovod、Ray 和 PyTorch 训练作业。

延迟创建替换 Pod

特性状态: Kubernetes v1.34 [稳定] (默认启用:true)

默认情况下,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,默认的 podReplacementPolicyFailed,不允许其他值。请参阅 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。该字段的值是不可变的。

替代方案

裸 Pod

当 Pod 运行的节点重启或发生故障时,该 Pod 将被终止且不会重新启动。但是,Job 将创建新的 Pod 来替换已终止的 Pod。因此,我们建议您使用 Job 而不是裸 Pod,即使您的应用程序只需要一个 Pod。

副本控制器

Job 与副本控制器互补。副本控制器管理不期望终止的 Pod(例如 Web 服务器),而 Job 管理期望终止的 Pod(例如批处理任务)。

Pod 生命周期中所述,Job 适用于 RestartPolicy 等于 OnFailureNever 的 Pod。(注意:如果未设置 RestartPolicy,默认值为 Always。)

单个 Job 启动控制器 Pod

另一种模式是单个 Job 创建一个 Pod,然后该 Pod 创建其他 Pod,充当这些 Pod 的一种自定义控制器。这提供了最大的灵活性,但可能在入门时有些复杂,并且与 Kubernetes 的集成度较低。

此模式的一个示例是,一个 Job 启动一个 Pod,该 Pod 运行一个脚本,该脚本又启动一个 Spark 主控制器(参见Spark 示例),运行一个 Spark 驱动程序,然后进行清理。

这种方法的优点是整个过程获得了 Job 对象的完成保证,同时保持对创建哪些 Pod 以及如何将工作分配给它们的完全控制。

下一步

上次修改时间:2025 年 6 月 29 日上午 8:26 PST:[KEP-3939] - 将 JobPodReplacementPolicy 提升为 GA (bf232870d9)