作者:Kevin Hannon (G-Research), Michał Woźniak (Google)
本篇文章讨论了 Kubernetes 1.28 版本中为了改进批量用户的作业而引入的两个新功能:Pod 替换策略和每个索引的退避限制。这些功能延续了由 Pod 失败策略发起的努力,以改进作业中 Pod 故障的处理。
Pod 替换策略
默认情况下,当一个 Pod 进入终止状态(例如由于抢占或驱逐),Kubernetes 会立即创建一个替代的 Pod。因此,这两个 Pod 同时运行。从 API 的角度来看,当一个 Pod 拥有 .deletionTimestampPendingRunning 时,它被认为是处于终止状态。对于一些流行的机器学习框架(例如 TensorFlow 和 JAX),在同一时刻最多只允许一个特定索引的 Pod 运行,这种情况会带来问题。如果同一索引有两个 Pod 在运行,TensorFlow 将会报错如下:
/job:worker/task:4: Duplicate task registration with task_name=/job:worker/replica:0/task:4
在前一个 Pod 完全终止之前创建替代 Pod 也可能会在资源稀缺或预算紧张的集群中引发问题,例如:
1. 集群资源可能难以获取,因为待调度的 Pod 可能需要很长时间才能找到可用的节点,直到现有的 Pod 完全终止。
2. 如果启用了集群自动缩放器,替代的 Pod 可能会导致不必要的扩容。
如何使用这些功能呢?
这是一个 alpha 功能,您可以通过在集群中启用功能开关 JobPodReplacementPolicy 来开启。一旦在您的集群中启用了该功能,您可以通过创建一个新的 Job,并指定一个字段,如下所示,来使用它:podReplacementPolicy
kind: Job
metadata:
name: new
...
spec:
podReplacementPolicy: Failed
...
在该作业中,只有当 Pod 达到阶段时才会进行替换,而不是在其处于终止状态时进行替换。此外,您可以检查作业的一个字段。该字段的值是由该作业拥有且当前正在终止的 Pod 数量。.status.terminating
kubectl get jobs/myjob -o=jsonpath='{.items[*].status.terminating}'
3 # three Pods are terminating and have not yet reached the Failed phase
对于外部排队控制器(如 Kueue),此功能可能特别有用,它会追踪作业中运行的 Pod 的配额,直到资源从当前正在终止的作业中回收。请注意,当使用自定义的 Pod 失败策略时,默认为 podReplacementPolicy: Failed。
索引的重试限制
默认情况下,对于索引作业的 Pod 失败会计入全局的重试限制,由 .spec.backoffLimit 表示。这意味着,如果某个索引持续失败,它会被重复重新启动,直到达到限制。一旦达到限制,整个作业将被标记为失败,某些索引可能甚至永远不会启动。
对于需要独立处理每个索引的 Pod 失败的用例,这是有问题的。例如,如果您使用索引作业来运行集成测试,其中每个索引对应一个测试套件。在这种情况下,您可能希望考虑可能出现的测试不稳定性,每个套件允许重试 1 次或 2 次。可能会有一些有问题的套件,导致相应的索引始终失败。在这种情况下,您可能更喜欢限制有问题的套件的重试次数,但允许其他套件完成。
此功能允许您:
- 尽管某些索引失败,但完成所有索引的执行。
- 通过避免不必要的持续失败索引重试,更有效地利用计算资源。
如何使用这个功能?
这是一个alpha版功能,您可以通过在集群中打开功能开关 JobBackoffLimitPerIndex 来启用它。一旦在您的集群中启用了该功能,您可以创建一个带有指定字段的索引作业.spec.backoffLimitPerIndex
示例
以下示例演示了如何使用此功能来确保作业执行所有索引(前提是没有其他导致作业提前终止的原因,比如达到超时时间,或被用户手动删除),并且每个索引的失败次数受到.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
此外,您还可以查看该作业的状态:
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
在这里,索引都重新尝试了一次。在每个索引的第二次失败中,都超过了指定的 backoffLimitPerIndex,因此重试被停止。相比之下,如果禁用了每个索引的退避限制,那么有问题的索引会一直重试,直到全局 backoffLimit 被超过,然后整个作业会被标记为失败,而一些较高的索引在开始之前就会失败。
如何获取更多信息?
阅读有关 Pod 替换策略、每个索引的退避限制和 Pod 失败策略的用户文档。
阅读 Pod 替换策略、每个索引的退避限制和 Pod 失败策略的 KEP(Kubernetes Enhancement Proposal)。
参与其中
这些功能由 SIG Apps 赞助。批处理工作组正在为 Kubernetes 用户积极改进批处理用例。工作组是专注于特定目标的相对短期的倡议。批处理工作组的目标是改善批处理工作负载用户的体验,为批处理用例提供支持,并针对常见用例增强作业 API。如果您对此感兴趣,请通过订阅我们的邮件列表或在 Slack 上加入工作组。
致谢
与 Kubernetes 的任何功能一样,从测试、提交错误到审查代码,许多人都为完成这项工作做出了贡献。如果没有 Aldo Culquicondor(谷歌)在整个 Kubernetes 生态系统中提供的出色领域知识和专业知识,我们不可能实现这些功能。
本文翻译自Kubernetes.io
转载请注明出处:https://www.cloudnative-tech.com/technology/5965.html