本文已超过一年。较旧文章可能包含过时内容。请检查页面信息自发布以来是否已不正确。
Kubernetes 1.26:作业跟踪正式发布,支持大规模并行批处理工作负载
Kubernetes 1.26 版本包含 Job 控制器的稳定实现,可以可靠地跟踪大量具有高度并行性的 Job。自 Kubernetes 1.22 起,SIG Apps 和 WG Batch 一直致力于这项基础改进。经过多次迭代和规模验证,这现在是 Job 控制器的默认实现。
结合 Indexed 完成模式,Job 控制器可以处理大规模并行批处理 Job,支持高达 10 万个并发 Pod。
新实现也促成了 Pod 失败策略 的开发,该策略在 1.26 版本中处于 Beta 阶段。
我如何使用此功能?
要使用带有 Finalizer 的 Job 跟踪功能,请升级到 Kubernetes 1.25 或更高版本并创建新的 Job。如果你可以启用 JobTrackingWithFinalizers
feature gate,你也可以在 v1.23 和 v1.24 版本中使用此功能。
如果你的集群运行 Kubernetes 1.26,使用 Finalizer 的 Job 跟踪功能是稳定功能。对于 v1.25,它隐藏在该 feature gate 后面,并且你的集群管理员可能已明确禁用它 - 例如,如果你的策略是不使用 Beta 功能。
升级前创建的 Job 仍将使用旧版行为进行跟踪。这是为了避免追溯性地向正在运行的 Pod 添加 Finalizer,这可能会引入竞态条件。
对于大型 Job 的最佳性能,Kubernetes 项目建议使用 Indexed 完成模式。在此模式下,控制平面能够通过更少的 API 调用跟踪 Job 进度。
如果你是批处理、HPC、AI、ML 或相关工作负载的 Operator 开发者,我们鼓励你使用 Job API 将准确的进度跟踪委托给 Kubernetes。如果 Job API 中缺少某些内容迫使你管理普通 Pod,Batch 工作组 欢迎你的反馈和贡献。
废弃通知
在此功能的开发过程中,控制平面将 annotation batch.kubernetes.io/job-tracking
添加到功能启用时创建的 Job 中。这为较旧的 Job 提供了安全的过渡,但它从未打算永久保留。
在 1.26 版本中,我们废弃了 annotation batch.kubernetes.io/job-tracking
,并且控制平面将在 Kubernetes 1.27 中停止添加它。随着这一改变,我们将移除旧版 Job 跟踪实现。因此,Job 控制器将使用 Finalizer 跟踪所有 Job,并忽略没有上述 Finalizer 的 Pod。
在将集群升级到 1.27 之前,我们建议你验证没有正在运行且没有该 annotation 的 Job,或者你等待这些 Job 完成。否则,你可能会看到控制平面重新创建一些 Pod。我们预计这不应影响任何用户,因为该功能自 Kubernetes 1.25 起已默认启用,为旧 Job 完成提供了足够的缓冲时间。
新实现解决了什么问题?
通常,Kubernetes 工作负载控制器,例如 ReplicaSet 或 StatefulSet,依赖于 API 中 Pod 或其他对象的存在来确定工作负载的状态以及是否需要替换。例如,如果属于 ReplicaSet 的 Pod 终止或不再存在,ReplicaSet 控制器需要创建一个替换 Pod 以满足期望的副本数量(.spec.replicas
)。
自其诞生以来,Job 控制器也依赖于 API 中 Pod 的存在来跟踪 Job 状态。Job 具有 完成 和 失败处理 策略,需要已完成 Pod 的最终状态来决定是创建替换 Pod 还是将 Job 标记为完成或失败。因此,Job 控制器依赖 Pod(即使是已终止的 Pod)留在 API 中以跟踪状态。
这种依赖使得 Job 状态跟踪不可靠,因为 Pods 可以因多种原因从 API 中删除,包括:
- 垃圾回收器在节点宕机时移除孤立 Pod。
- 垃圾回收器在已终止 Pod 达到阈值时移除它们。
- Kubernetes 调度器抢占 Pod 以容纳更高优先级的 Pod。
- Taint Manager 驱逐不容忍
NoExecute
污点的 Pod。 - 非 Kubernetes 核心的外部控制器或人工删除 Pod。
新实现
当控制器需要在对象被移除之前对其执行操作时,它应该向其管理的对象添加 finalizer。Finalizer 会阻止对象从 API 中删除,直到 Finalizer 被移除。控制器完成清理和对已删除对象的计数后,就可以从对象中移除 finalizer,然后控制平面从 API 中移除对象。
这就是新的 Job 控制器所做的工作:在 Pod 创建期间添加 finalizer,并在 Pod 终止并在 Job 状态中完成计数后移除 finalizer。然而,事情并非如此简单。
主要挑战在于至少涉及两个对象:即 Pod 和 Job。Finalizer 位于 Pod 对象中,而计数信息位于 Job 对象中。没有机制可以原子性地移除 Pod 中的 finalizer 并更新 Job 状态中的计数器。此外,在任何给定时间都可能存在多个已终止的 Pod。
为了解决这个问题,我们实施了一个三阶段方法,每个阶段对应一个 API 调用。
- 对于每个已终止的 Pod,将 Pod 的唯一 ID (UID) 添加到存储在所属 Job 的
.status
中的短期列表中(.status.uncountedTerminatedPods)。 - 从 Pod(s) 中移除 finalizer。
- 原子性地执行以下操作:
- 从短期列表中移除 UID
- 增加 Job 的
status
中的总succeeded
和failed
计数器。
其他复杂性来自于 Job 控制器可能会无序地接收步骤 1 和 2 中 API 变更的结果。我们通过为已移除的 finalizer 添加内存缓存来解决了这个问题。
然而,在 Beta 阶段,我们仍然面临一些问题,导致在某些条件下一些 Pod 陷入带有 Finalizer 的状态(#108645、#109485 和 #111646)。因此,我们决定在 1.23 和 1.24 版本中默认禁用该 feature gate。
解决后,我们在 1.25 版本中重新启用了该功能。自那时以来,我们收到了客户通过 Job API 在其集群中同时运行数万个 Pod 的报告。看到这一成功,我们决定在 1.26 版本中将该功能提升为稳定版,作为我们将 Job API 打造成在 Kubernetes 集群中运行大型批处理 Job 的最佳方式的长期承诺的一部分。
要了解有关此功能的更多信息,你可以阅读 KEP。
致谢
与任何 Kubernetes 功能一样,多人为此付出了贡献,从测试和提交 bug 到代码审查。
代表 SIG Apps,我想特别感谢 Jordan Liggitt (Google) 帮助我调试并集思广益解决了不止一个竞态条件问题,以及 Maciej Szulik (Red Hat) 提供的周到审查。