本文发布已超过一年。较旧的文章可能包含过时的内容。请检查页面上的信息自发布以来是否已变得不正确。

Kubernetes 1.26:作业跟踪,以支持大规模并行批处理工作负载,正式发布

Kubernetes 1.26 版本包括 Job 控制器的稳定实现,该控制器可以可靠地跟踪大量具有高并行度的 Job。SIG AppsWG Batch 自 Kubernetes 1.22 以来一直在努力进行这项基础性改进。经过多次迭代和规模验证,这现在是 Job 控制器的默认实现。

与索引式完成模式配对,Job 控制器可以处理大规模并行批处理 Job,支持高达 10 万个并发 Pod。

新实现还使得Pod 失败策略的开发成为可能,该策略在 1.26 版本中处于 Beta 阶段。

我如何使用此功能?

要使用带有终结器的 Job 跟踪,请升级到 Kubernetes 1.25 或更高版本并创建新的 Job。如果您能够启用 JobTrackingWithFinalizers 功能门,则也可以在 v1.23 和 v1.24 中使用此功能。

如果您的集群运行 Kubernetes 1.26,则带有终结器的 Job 跟踪是一项稳定功能。对于 v1.25,它位于该功能门之后,并且您的集群管理员可能已明确禁用它 - 例如,如果您有不使用 Beta 功能的策略。

升级之前创建的 Job 仍将使用旧的行为进行跟踪。这是为了避免追溯地向正在运行的 Pod 添加终结器,这可能会引入竞争条件。

为了在大型 Job 上获得最大性能,Kubernetes 项目建议使用索引式完成模式。在此模式下,控制平面能够通过较少的 API 调用来跟踪 Job 进度。

如果您是批处理、HPCAIML 或相关工作负载的 Operator 开发人员,我们鼓励您使用 Job API 将精确的进度跟踪委托给 Kubernetes。如果 Job API 中缺少某些东西迫使您管理普通 Pod,工作组 Batch 欢迎您的反馈和贡献。

弃用通知

在该功能开发期间,控制平面将注释 batch.kubernetes.io/job-tracking 添加到启用该功能时创建的 Job 中。这允许旧 Job 的安全过渡,但它从未打算保留。

在 1.26 版本中,我们弃用了注释 batch.kubernetes.io/job-tracking,并且控制平面将在 Kubernetes 1.27 中停止添加它。随着该更改,我们将删除旧的 Job 跟踪实现。因此,Job 控制器将使用终结器跟踪所有 Job,并且它将忽略没有上述终结器的 Pod。

在将集群升级到 1.27 之前,我们建议您验证是否有任何正在运行的 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 状态的跟踪不可靠,因为 Pod 可能由于多种原因从 API 中删除,包括

  • 当节点关闭时,垃圾收集器删除孤立的 Pod。
  • 垃圾收集器在已终止的 Pod 达到阈值时将其删除。
  • Kubernetes 调度程序抢占 Pod 以容纳更高优先级的 Pod。
  • 污点管理器驱逐不容忍 NoExecute 污点的 Pod。
  • 外部控制器(未包含在 Kubernetes 中)或人为删除 Pod。

新实现

当控制器需要在删除对象之前对其执行操作时,它应该将终结器添加到其管理的对象中。终结器会阻止对象从 API 中删除,直到终结器被删除。一旦控制器完成了清理并说明了已删除的对象,它就可以从对象中删除终结器,并且控制平面将对象从 API 中删除。

这就是新的 Job 控制器正在做的事情:在 Pod 创建期间添加终结器,并在 Pod 终止并在 Job 状态中进行说明后删除终结器。但是,这并不是那么简单。

主要挑战是至少涉及两个对象:Pod 和 Job。虽然终结器驻留在 Pod 对象中,但记帐驻留在 Job 对象中。没有机制可以原子地删除 Pod 中的终结器并更新 Job 状态中的计数器。此外,在给定时间可能存在多个已终止的 Pod。

为了解决这个问题,我们实现了一个三阶段的方法,每个阶段都转换为一个 API 调用。

  1. 对于每个已终止的 Pod,将 Pod 的唯一 ID (UID) 添加到存储在拥有 Job 的 .status 中的短生存期列表中 (.status.uncountedTerminatedPods)。
  2. 从 Pod 中删除终结器。
  3. 原子地执行以下操作
    • 从短生存期列表中删除 UID
    • 在 Job 的 status 中增加整体 succeededfailed 计数器。

其他复杂性来自这样一个事实,即 Job 控制器可能会无序地接收步骤 1 和 2 中 API 更改的结果。我们通过为已删除的终结器添加内存中缓存来解决此问题。

尽管如此,我们在 Beta 阶段仍然遇到了一些问题,在某些情况下,一些 Pod 仍然带有终结器(#108645#109485#111646)。因此,我们决定将该功能门切换为在 1.23 和 1.24 版本中默认禁用。

一旦解决,我们重新启用了 1.25 版本的该功能。从那时起,我们收到了客户的报告,他们通过 Job API 在其集群中一次运行数万个 Pod。看到这一成功,我们决定在 1.26 中将该功能升级为稳定版,作为我们长期致力于使 Job API 成为在 Kubernetes 集群中运行大型批处理 Job 的最佳方式的一部分。

要了解有关该功能的更多信息,您可以阅读 KEP

鸣谢

与任何 Kubernetes 功能一样,多人为完成此项工作做出了贡献,从测试和提交错误到审查代码。

我谨代表 SIG Apps,特别感谢 Jordan Liggitt (Google) 帮助我调试和集思广益,解决多个竞争条件,以及 Maciej Szulik (Red Hat) 的全面审查。