本文发布已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。

Kubernetes 1.28:改进 Job 的故障处理

这篇博客讨论了 Kubernetes 1.28 中两个用于改进批处理用户的 Job 的新特性:Pod 替换策略每个索引的回退限制

这些特性延续了 Pod 失败策略 启动的工作,旨在改进 Job 中 Pod 失败的处理方式。

Pod 替换策略

默认情况下,当 Pod 进入终止状态时(例如,由于抢占或驱逐),Kubernetes 会立即创建一个替换 Pod。因此,两个 Pod 会同时运行。在 API 术语中,当一个 Pod 具有 deletionTimestamp 并且其阶段为 PendingRunning 时,该 Pod 被认为是终止的。

对于某些流行的机器学习框架(例如 TensorFlow 和 JAX)而言,在给定时间运行两个 Pod 的情况是有问题的,因为它们要求对于给定的索引,最多只能运行一个 Pod。如果为给定索引运行两个 Pod,TensorFlow 会给出以下错误。

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

有关更多详细信息,请参见(问题)。

在前一个 Pod 完全终止之前创建替换 Pod 也会在资源稀缺或预算紧张的集群中引起问题,例如

  • 对于待调度的 Pod,集群资源可能难以获得,因为 Kubernetes 可能需要很长时间才能找到可用节点,直到现有 Pod 完全终止。
  • 如果启用了集群自动扩缩器,则替换 Pod 可能会产生不希望的扩容。

如何使用它?

这是一个 alpha 功能,您可以通过在集群中启用 JobPodReplacementPolicy 功能门 来启用它。

在您的集群中启用该功能后,您可以通过创建一个新的 Job 来使用它,该 Job 指定一个 podReplacementPolicy 字段,如下所示

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

在该 Job 中,Pod 将仅在它们达到 Failed 阶段时才会被替换,而不是在它们终止时被替换。

此外,您可以检查 Job 的 .status.terminating 字段。该字段的值是 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)特别有用,它可以跟踪 Job 的运行 Pod 的配额,直到从当前正在终止的 Job 中回收资源。

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

每个索引的回退限制

默认情况下,索引作业 的 Pod 失败计入重试的全局限制,由 .spec.backoffLimit 表示。这意味着,如果存在持续失败的索引,则会重复重启它,直到耗尽限制。一旦达到限制,整个 Job 将被标记为失败,并且某些索引甚至可能永远不会启动。

对于您希望独立处理每个索引的 Pod 失败的用例,这是有问题的。例如,如果您使用索引作业来运行集成测试,其中每个索引对应一个测试套件。在这种情况下,您可能希望考虑可能的 flake 测试,允许每个套件重试 1 或 2 次。可能有一些有错误的套件,导致相应的索引持续失败。在这种情况下,您可能更喜欢限制有错误的套件的重试次数,但允许其他套件完成。

该功能允许您

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

如何使用它?

这是一个 alpha 功能,您可以通过在集群中启用 JobBackoffLimitPerIndex 功能门 来启用它。

在您的集群中启用该功能后,您可以创建一个指定了 .spec.backoffLimitPerIndex 字段的索引作业。

示例

以下示例演示如何使用此功能来确保 Job 执行所有索引(前提是没有其他原因导致 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 # this example container returns an error, and fails,
                      # when it is run as the second or third index in any Job
                      # (even after a retry)        
        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)          

现在,在作业完成后检查 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 都被重试了一次。在第二次失败后,在每个索引中,都超过了指定的 .spec.backoffLimitPerIndex,因此停止了重试。相比之下,如果禁用了每个索引的回退,那么有错误的索引将重试,直到超过全局 backoffLimit,然后整个 Job 将被标记为失败,然后再启动一些更高的索引。

如何了解更多?

参与其中

这些功能由 SIG Apps 赞助。正在 批处理工作组 中积极改进 Kubernetes 用户的批处理用例。工作组是专注于特定目标的相对短期计划。WG Batch 的目标是改善批处理工作负载用户的体验,为批处理用例提供支持,并增强 Job API 以满足常见的用例。如果您对此感兴趣,请通过订阅我们的 邮件列表 或在 Slack 上加入工作组。

致谢

与任何 Kubernetes 功能一样,从测试和提交错误到审查代码,都有多人为完成此工作做出了贡献。

如果没有 Aldo Culquicondor (Google) 在整个 Kubernetes 生态系统中提供出色的领域知识和专业知识,我们将无法实现这两个功能。