本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
Kubernetes 1.28:改进了 Job 的故障处理
这篇博客讨论了 Kubernetes 1.28 中为改进批处理用户的 Job 而引入的两个新特性:Pod 替换策略和逐索引的回退限制。
这些特性延续了Pod 故障策略所开启的努力,旨在改进 Job 中 Pod 故障的处理方式。
Pod 替换策略
默认情况下,当一个 Pod 进入终止(terminating)状态(例如,由于抢占或驱逐)时,Kubernetes 会立即创建一个替换 Pod。因此,两个 Pod 会同时运行。在 API 术语中,当 Pod 具有 deletionTimestamp
并且其阶段(phase)为 Pending
或 Running
时,该 Pod 就被视为正在终止。
在给定时间点有两个 Pod 正在运行的场景对于一些流行的机器学习框架来说是有问题的,例如 TensorFlow 和 JAX,它们要求对于给定的索引,在同一时间最多只有一个 Pod 在运行。如果某个索引有两个 Pod 在运行,TensorFlow 会给出以下错误。
/job:worker/task:4: Duplicate task registration with task_name=/job:worker/replica:0/task:4
更多详情请参见 (issue)。
在前一个 Pod 完全终止之前创建替换 Pod,也可能在资源稀缺或预算紧张的集群中引起问题,例如:
- 对于等待调度的 Pod 来说,集群资源可能很难获取,因为在现有 Pod 完全终止之前,Kubernetes 可能需要很长时间才能找到可用的节点。
- 如果启用了集群自动扩缩器,替换的 Pod 可能会产生不希望的扩容。
如何使用它?
这是一个 Alpha 特性,你可以通过在集群中启用 JobPodReplacementPolicy
特性门控来开启它。
一旦在你的集群中启用了该特性,你就可以通过创建一个新的 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
是默认设置。
逐索引的回退限制
默认情况下,Indexed Job 的 Pod 故障会计入全局重试限制,由 .spec.backoffLimit
表示。这意味着,如果有一个持续失败的索引,它会反复重启,直到耗尽限制。一旦达到限制,整个 Job 就会被标记为失败,并且某些索引可能从未启动过。
这对于希望独立处理每个索引的 Pod 故障的用例是有问题的。例如,如果你使用 Indexed Job 来运行集成测试,其中每个索引对应一个测试套件。在这种情况下,你可能希望考虑到可能的不稳定测试,允许每个套件重试 1 或 2 次。可能会有一些有问题的套件,导致相应的索引持续失败。在这种情况下,你可能更倾向于限制有问题的套件的重试次数,同时允许其他套件完成。
该特性允许你:
- 尽管某些索引失败,但仍能完成所有索引的执行。
- 通过避免对持续失败的索引进行不必要的重试,更好地利用计算资源。
如何使用它?
这是一个 Alpha 特性,你可以通过在集群中启用 JobBackoffLimitPerIndex
特性门控来开启它。
一旦在你的集群中启用了该特性,你就可以创建一个指定了 .spec.backoffLimitPerIndex
字段的 Indexed Job。
示例
以下示例演示了如何使用此特性来确保 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)
现在,在 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
在这里,索引 1
和 2
都各自重试了一次。在它们各自第二次失败后,指定的 .spec.backoffLimitPerIndex
被超过,因此重试被停止。作为比较,如果禁用了逐索引的回退,那么有问题的索引会一直重试,直到超过全局的 backoffLimit
,然后整个 Job 会被标记为失败,而一些更高编号的索引可能还未启动。
如何了解更多?
- 阅读Pod 替换策略、逐索引的回退限制和Pod 故障策略的用户文档。
- 阅读关于Pod 替换策略、逐索引的回退限制和Pod 故障策略的 KEP(Kubernetes Enhancement Proposal)。
参与其中
这些特性由 SIG Apps 赞助。在批处理工作组中,我们正积极为 Kubernetes 用户改进批处理用例。工作组是专注于特定目标的相对短期的倡议。WG Batch 的目标是改善批处理工作负载用户的体验,为批处理用例提供支持,并针对常见用例增强 Job API。如果你对此感兴趣,请通过订阅我们的邮件列表或在 Slack 上加入我们的工作组。
致谢
与任何 Kubernetes 特性一样,许多人都为完成这项工作做出了贡献,从测试、提交错误到代码审查。
如果没有 Aldo Culquicondor (Google) 在整个 Kubernetes 生态系统中提供卓越的领域知识和专业技术,我们无法实现这两项特性。