--- layout: blog title: "Kubernetes 1.28:Job 失效处理的改进" date: 2023-08-21 slug: kubernetes-1-28-jobapi-update --- **作者:** Kevin Hannon (G-Research), Michał Woźniak (Google) **译者:** Xin Li (Daocloud) 本博客讨论 Kubernetes 1.28 中的两个新特性,用于为批处理用户改进 Job: [Pod 更换策略](/zh-cn/docs/concepts/workloads/controllers/job/#pod-replacement-policy) 和[基于索引的回退限制](/zh-cn/docs/concepts/workloads/controllers/job/#backoff-limit-per-index)。 这些特性延续了 [Pod 失效策略](/zh-cn/docs/concepts/workloads/controllers/job/#pod-failure-policy) 为开端的工作,用来改进对 Job 中 Pod 失效的处理。 ## Pod 更换策略 {#pod-replacement-policy} 默认情况下,当 Pod 进入终止(Terminating)状态(例如由于抢占或驱逐机制)时,Kubernetes 会立即创建一个替换的 Pod,因此这时会有两个 Pod 同时运行。就 API 而言,当 Pod 具有 `deletionTimestamp` 字段并且处于 `Pending` 或 `Running` 阶段时会被视为终止。 对于一些流行的机器学习框架来说,在给定时间运行两个 Pod 的情况是有问题的, 例如 TensorFlow 和 [JAX](https://jax.readthedocs.io/en/latest/), 对于给定的索引,它们最多同时运行一个 Pod。如果两个 Pod 使用同一个索引来运行, Tensorflow 会抛出以下错误: ``` /job:worker/task:4: Duplicate task registration with task_name=/job:worker/replica:0/task:4 ``` 可参考[问题报告](https://github.com/kubernetes/kubernetes/issues/115844)进一步了解细节。 在前一个 Pod 完全终止之前创建替换的 Pod 也可能会导致资源或预算紧张的集群出现问题,例如: * 对于待调度的 Pod 来说,很难分配到集群资源,导致 Kubernetes 需要很长时间才能找到可用节点, 直到现有 Pod 完全终止。 * 如果启用了集群自动扩缩器(Cluster Autoscaler),可能会产生不必要的集群规模扩增。 ### 如何使用? {#pod-replacement-policy-how-to-use} 这是一项 Alpha 级别特性,你可以通过在集群中启用 `JobPodReplacementPolicy` [特性门控](/zh-cn/docs/reference/command-line-tools-reference/feature-gates/) 来启用该特性。 ```yaml kind: Job metadata: name: new ... spec: podReplacementPolicy: Failed ... ``` 在此 Job 中,Pod 仅在达到 `Failed` 阶段时才会被替换,而不是在它们处于终止过程中(Terminating)时被替换。 此外,你可以检查 Job 的 `.status.termination` 字段。该字段的值表示终止过程中的 Job 所关联的 Pod 数量。 ```shell kubectl get jobs/myjob -o=jsonpath='{.items[*].status.terminating}' ``` ``` 3 # three Pods are terminating and have not yet reached the Failed phase ``` 这一特性对于外部排队控制器(例如 [Kueue](https://github.com/kubernetes-sigs/kueue))特别有用, 它跟踪作业的运行 Pod 的配额,直到从当前终止过程中的 Job 资源被回收为止。 请注意,使用自定义 [Pod 失败策略](/zh-cn/docs/concepts/workloads/controllers/job/#pod-failure-policy)时, `podReplacementPolicy: Failed` 是默认值。 ## 逐索引的回退限制 {#backoff-limit-per-index} 默认情况下,[带索引的 Job(Indexed Job)](/zh-cn/docs/concepts/workloads/controllers/job/#completion-mode)的 Pod 失败情况会被统计下来,受 `.spec.backoffLimit` 字段所设置的全局重试次数限制。 这意味着,如果存在某个索引值的 Pod 一直持续失败,则会 Pod 会被重新启动,直到重试次数达到限制值。 一旦达到限制值,整个 Job 将被标记为失败,并且对应某些索引的 Pod 甚至可能从不曾被启动。 对于你想要独立处理不同索引值的 Pod 的失败的场景而言,这是有问题的。 例如,如果你使用带索引的 Job(Indexed Job)来运行集成测试,其中每个索引值对应一个测试套件。 在这种情况下,你可能需要考虑可能发生的脆弱测试(Flake Test),允许每个套件重试 1 次或 2 次。 可能存在一些有缺陷的套件,导致对应索引的 Pod 始终失败。在这种情况下, 你或许更希望限制有问题的套件的重试,而允许其他套件完成。 此特性允许你: * 尽管某些索引值的 Pod 失败,但仍完成执行所有索引值的 Pod。 * 通过避免对持续失败的、特定索引值的 Pod 进行不必要的重试,更好地利用计算资源。 ### 可以如何使用它? {#backoff-limit-per-index-how-to-use} 这是一个 Alpha 特性,你可以通过启用集群的 `JobBackoffLimitPerIndex` [特性门控](/zh-cn/docs/reference/command-line-tools-reference/feature-gates/)来启用此特性。 在集群中启用该特性后,你可以在创建带索引的 Job(Indexed Job)时指定 `.spec.backoffLimitPerIndex` 字段。 #### 示例 下面的示例演示如何使用此功能来确保 Job 执行所有索引值的 Pod(前提是没有其他原因导致 Job 提前终止, 例如达到 `activeDeadlineSeconds` 超时,或者被用户手动删除),以及按索引控制失败次数。 ```yaml 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: ```sh 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 的状态: ```sh kubectl get jobs job-backoff-limit-per-index-fail-index -o yaml ``` 输出内容以 `status` 结尾,类似于: ```yaml status: completedIndexes: 0,3-7 failedIndexes: 1,2 succeeded: 6 failed: 4 conditions: - message: Job has failed indexes reason: FailedIndexes status: "True" type: Failed ``` 这里,索引为 `1` 和 `2` 的 Pod 都被重试了一次。这两个 Pod 在第二次失败后都超出了指定的 `.spec.backoffLimitPerIndex`,因此停止重试。相比之下,如果禁用了基于索引的回退, 那么有问题的、特定索引的 Pod 将被重试,直到超出全局 `backoffLimit`,之后在启动一些索引值较高的 Pod 之前, 整个 Job 将被标记为失败。 ## 如何进一步了解 {#how-can-you-learn-more} - 阅读面向用户的 [Pod 替换策略](/zh-cn/docs/concepts/workloads/controllers/job/#pod-replacement-policy)文档、 [逐索引的回退限制](/zh-cn/docs/concepts/workloads/controllers/job/#backoff-limit-per-index)和 [Pod 失效策略](/zh-cn/docs/concepts/workloads/controllers/job/#pod-failure-policy) - 阅读 [Pod 替换策略](https://github.com/kubernetes/enhancements/tree/master/keps/sig-apps/3939-allow-replacement-when-fully-terminated))、 [逐索引的回退限制](https://github.com/kubernetes/enhancements/tree/master/keps/sig-apps/3850-backoff-limits-per-index-for-indexed-jobs)和 [Pod 失效策略](https://github.com/kubernetes/enhancements/tree/master/keps/sig-apps/3329-retriable-and-non-retriable-failures)的 KEP。 ## 参与其中 {#getting-Involved} 这些功能由 [SIG Apps](https://github.com/kubernetes/community/tree/master/sig-apps) 赞助。 社区正在为[批处理工作组](https://github.com/kubernetes/community/tree/master/wg-batch)中的 Kubernetes 用户积极改进批处理场景。 工作组是相对短暂的举措,专注于特定目标。WG Batch 的目标是改善批处理工作负载的用户体验、 提供对批处理场景的支持并增强常见场景下的 Job API。 如果你对此感兴趣,请通过订阅我们的[邮件列表](https://groups.google.com/a/kubernetes.io/g/wg-batch)或通过 [Slack](https://kubernetes.slack.com/messages/wg-batch) 加入进来。 ## 致谢 {#acknowledgments} 与其他 Kubernetes 特性一样,从测试、报告缺陷到代码审查,很多人为此特性做出了贡献。 如果没有 Aldo Culquicondor(Google)提供出色的领域知识和跨整个 Kubernetes 生态系统的知识, 我们可能无法实现这些特性。