Kubernetes 1.28:Job 失效处理的改进

作者: Kevin Hannon (G-Research), Michał Woźniak (Google)

译者: Xin Li (Daocloud)

本博客讨论 Kubernetes 1.28 中的两个新特性,用于为批处理用户改进 Job: Pod 更换策略基于索引的回退限制

这些特性延续了 Pod 失效策略 为开端的工作,用来改进对 Job 中 Pod 失效的处理。

Pod 更换策略

默认情况下,当 Pod 进入终止(Terminating)状态(例如由于抢占或驱逐机制)时,Kubernetes 会立即创建一个替换的 Pod,因此这时会有两个 Pod 同时运行。就 API 而言,当 Pod 具有 deletionTimestamp 字段并且处于 PendingRunning 阶段时会被视为终止。

对于一些流行的机器学习框架来说,在给定时间运行两个 Pod 的情况是有问题的, 例如 TensorFlow 和 JAX, 对于给定的索引,它们最多同时运行一个 Pod。如果两个 Pod 使用同一个索引来运行, Tensorflow 会抛出以下错误:

 /job:worker/task:4: Duplicate task registration with task_name=/job:worker/replica:0/task:4

可参考问题报告进一步了解细节。

在前一个 Pod 完全终止之前创建替换的 Pod 也可能会导致资源或预算紧张的集群出现问题,例如:

  • 对于待调度的 Pod 来说,很难分配到集群资源,导致 Kubernetes 需要很长时间才能找到可用节点, 直到现有 Pod 完全终止。
  • 如果启用了集群自动扩缩器(Cluster Autoscaler),可能会产生不必要的集群规模扩增。

如何使用?

这是一项 Alpha 级别特性,你可以通过在集群中启用 JobPodReplacementPolicy 特性门控 来启用该特性。

kind: Job
metadata:
  name: new
  ...
spec:
  podReplacementPolicy: Failed
  ...

在此 Job 中,Pod 仅在达到 Failed 阶段时才会被替换,而不是在它们处于终止过程中(Terminating)时被替换。

此外,你可以检查 Job 的 .status.termination 字段。该字段的值表示终止过程中的 Job 所关联的 Pod 数量。

kubectl get jobs/myjob -o=jsonpath='{.items[*].status.terminating}'
3 # three Pods are terminating and have not yet reached the Failed phase

这一特性对于外部排队控制器(例如 Kueue)特别有用, 它跟踪作业的运行 Pod 的配额,直到从当前终止过程中的 Job 资源被回收为止。

请注意,使用自定义 Pod 失败策略时, podReplacementPolicy: Failed 是默认值。

逐索引的回退限制

默认情况下,带索引的 Job(Indexed Job)的 Pod 失败情况会被统计下来,受 .spec.backoffLimit 字段所设置的全局重试次数限制。 这意味着,如果存在某个索引值的 Pod 一直持续失败,则会 Pod 会被重新启动,直到重试次数达到限制值。 一旦达到限制值,整个 Job 将被标记为失败,并且对应某些索引的 Pod 甚至可能从不曾被启动。

对于你想要独立处理不同索引值的 Pod 的失败的场景而言,这是有问题的。 例如,如果你使用带索引的 Job(Indexed Job)来运行集成测试,其中每个索引值对应一个测试套件。 在这种情况下,你可能需要考虑可能发生的脆弱测试(Flake Test),允许每个套件重试 1 次或 2 次。 可能存在一些有缺陷的套件,导致对应索引的 Pod 始终失败。在这种情况下, 你或许更希望限制有问题的套件的重试,而允许其他套件完成。

此特性允许你:

  • 尽管某些索引值的 Pod 失败,但仍完成执行所有索引值的 Pod。
  • 通过避免对持续失败的、特定索引值的 Pod 进行不必要的重试,更好地利用计算资源。

可以如何使用它?

这是一个 Alpha 特性,你可以通过启用集群的 JobBackoffLimitPerIndex 特性门控来启用此特性。

在集群中启用该特性后,你可以在创建带索引的 Job(Indexed Job)时指定 .spec.backoffLimitPerIndex 字段。

示例

下面的示例演示如何使用此功能来确保 Job 执行所有索引值的 Pod(前提是没有其他原因导致 Job 提前终止, 例如达到 activeDeadlineSeconds 超时,或者被用户手动删除),以及按索引控制失败次数。

apiVersion: batch/v1
kind: Job
metadata:
  name: job-backoff-limit-per-index-execute-all
spec:
  completions: 8
  parallelism: 2
  completionMode: Indexed
  backoffLimitPerIndex: 1
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: example # 当此示例容器作为任何 Job 中的第二个或第三个索引运行时(即使在重试之后),它会返回错误并失败
        image: python
        command:
        - python3
        - -c
        - |
          import os, sys, time
          id = int(os.environ.get("JOB_COMPLETION_INDEX"))
          if id == 1 or id == 2:
            sys.exit(1)
          time.sleep(1)          

现在,在 Job 完成后检查 Pod:

kubectl get pods -l job-name=job-backoff-limit-per-index-execute-all

返回的输出类似与:

NAME                                              READY   STATUS      RESTARTS   AGE
job-backoff-limit-per-index-execute-all-0-b26vc   0/1     Completed   0          49s
job-backoff-limit-per-index-execute-all-1-6j5gd   0/1     Error       0          49s
job-backoff-limit-per-index-execute-all-1-6wd82   0/1     Error       0          37s
job-backoff-limit-per-index-execute-all-2-c66hg   0/1     Error       0          32s
job-backoff-limit-per-index-execute-all-2-nf982   0/1     Error       0          43s
job-backoff-limit-per-index-execute-all-3-cxmhf   0/1     Completed   0          33s
job-backoff-limit-per-index-execute-all-4-9q6kq   0/1     Completed   0          28s
job-backoff-limit-per-index-execute-all-5-z9hqf   0/1     Completed   0          28s
job-backoff-limit-per-index-execute-all-6-tbkr8   0/1     Completed   0          23s
job-backoff-limit-per-index-execute-all-7-hxjsq   0/1     Completed   0          22s

此外,你可以查看该 Job 的状态:

kubectl get jobs job-backoff-limit-per-index-fail-index -o yaml

输出内容以 status 结尾,类似于:

  status:
    completedIndexes: 0,3-7
    failedIndexes: 1,2
    succeeded: 6
    failed: 4
    conditions:
    - message: Job has failed indexes
      reason: FailedIndexes
      status: "True"
      type: Failed

这里,索引为 12 的 Pod 都被重试了一次。这两个 Pod 在第二次失败后都超出了指定的 .spec.backoffLimitPerIndex,因此停止重试。相比之下,如果禁用了基于索引的回退, 那么有问题的、特定索引的 Pod 将被重试,直到超出全局 backoffLimit,之后在启动一些索引值较高的 Pod 之前, 整个 Job 将被标记为失败。

如何进一步了解

参与其中

这些功能由 SIG Apps 赞助。 社区正在为批处理工作组中的 Kubernetes 用户积极改进批处理场景。 工作组是相对短暂的举措,专注于特定目标。WG Batch 的目标是改善批处理工作负载的用户体验、 提供对批处理场景的支持并增强常见场景下的 Job API。 如果你对此感兴趣,请通过订阅我们的邮件列表或通过 Slack 加入进来。

致谢

与其他 Kubernetes 特性一样,从测试、报告缺陷到代码审查,很多人为此特性做出了贡献。

如果没有 Aldo Culquicondor(Google)提供出色的领域知识和跨整个 Kubernetes 生态系统的知识, 我们可能无法实现这些特性。