作者:Kevin Hannon(G-Research)、Michał Woźniak(Google)
本博客讨论 Kubernetes 1.28 中用于改进批量用户作业的两个新功能:Pod 替换策略和每个索引的退避限制。
这些功能延续了 Pod 故障策略所发起的努力,以改进作业中 Pod 故障的处理。
Pod replacement policy Pod 更换策略
默认情况下,当 Pod 进入终止状态(例如由于抢占或驱逐)时,Kubernetes 会立即创建一个替换 Pod。因此,两个 Pod 同时运行。用 API 术语来说,当 pod 具有 deletionTimestamp 且具有阶段 Pending 或 Running 时,它被视为终止。
对于一些流行的机器学习框架(例如 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
功能门来启用该功能。
在集群中启用该功能后,您可以通过创建指定 podReplacementPolicy 字段的新作业来使用它,如下所示:
kind: Job
metadata:
name: new
...
spec:
podReplacementPolicy: Failed
在此作业中,Pod 仅在到达 Failed 阶段时才会被替换,而不是在它们终止时被替换。
此外,您还可以检查作业的.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)特别有用,它跟踪作业的运行 Pod 的配额,直到从当前终止的作业回收资源。
请注意,使用自定义 Pod 失败策略时,podReplacementPolicy: Failed是默认值。
每个索引限制
默认情况下,索引作业的 Pod 失败计入全局重试限制,由.spec.backoffLimit
表示。这意味着,如果存在持续失败的索引,则会重复重新启动,直到耗尽限制。一旦达到限制,整个作业将被标记为失败,并且某些索引可能永远不会启动。
对于您想要独立处理每个索引的 Pod 故障的用例来说,这是有问题的。例如,如果您使用索引作业来运行集成测试,其中每个索引对应一个测试套件。在这种情况下,您可能需要考虑可能的片状测试,允许每个套件重试 1 次或 2 次。可能存在一些有缺陷的套件,导致相应的索引始终失败。在这种情况下,您可能更愿意限制有问题的套件的重试,但允许其他套件完成。
该功能允许您:
- 尽管某些索引失败,但仍完整执行所有索引。
- 通过避免对持续失败的索引进行不必要的重试,更好地利用计算资源。
你可以如何使用它?
这是一项 alpha 功能,您可以通过打开集群中的 JobBackoffLimitPerIndex 功能门来启用该功能。
在集群中启用该功能后,您可以创建指定.spec.backoffLimitPerIndex
字段的索引作业。
Example 例子
下面的示例演示了如何使用此功能来确保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
此外,您可以查看该作业的状态:
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 为止,然后在启动一些较高的索引之前,整个作业将被标记为失败。