导航带设备的 Pod 中的故障
Kubernetes 是容器编排的事实标准,但在处理 GPU 和其他加速器等专用硬件时,事情会变得有点复杂。这篇博文深入探讨了在 Kubernetes 中操作带有设备的 Pod 时管理故障模式的挑战,基于 Sergey Kanzhelev 和 Mrunal Patel 在 KubeCon NA 2024 上的演讲的见解。您可以点击以下链接查看幻灯片和录音。
AI/ML 繁荣及其对 Kubernetes 的影响
AI/ML 工作负载的兴起给 Kubernetes 带来了新的挑战。这些工作负载通常严重依赖专用硬件,任何设备故障都可能显著影响性能并导致令人沮丧的中断。正如 2024 年 Llama 论文所强调的那样,硬件问题,特别是 GPU 故障,是 AI/ML 训练中断的主要原因。您还可以通过 Ryan Hallisey 和 Piotr Prokop 的 KubeCon 演讲“您的所有 GPU 都属于我们:NVIDIA 自愈 GeForce NOW 基础设施的内部探秘”(录音)了解 NVIDIA 在处理设备故障和维护方面付出了多少努力,因为他们每天每 1000 个节点就会收到 19 个修复请求!我们还看到数据中心提供现货消费模型和电力超额分配,这使得设备故障变得司空见惯,成为商业模式的一部分。
然而,Kubernetes 对资源的看法仍然非常静态。资源要么存在,要么不存在。如果存在,则假定它将保持完全正常运行——Kubernetes 缺乏对处理完全或部分硬件故障的良好支持。这些长期存在的假设加上设置的整体复杂性导致了各种故障模式,我们在此讨论。
了解 AI/ML 工作负载
通常,所有 AI/ML 工作负载都需要专用硬件,具有挑战性的调度要求,并且在空闲时成本高昂。AI/ML 工作负载通常分为两类——训练和推理。以下是对这些类别特征的过度简化视图,它们与 Web 服务等传统工作负载不同
- 培训
- 这些工作负载是资源密集型的,通常会占用整个机器并作为一组 Pod 运行。训练作业通常是“运行完成”——但这可能需要数天、数周甚至数月。单个 Pod 中的任何故障都可能需要重新启动所有 Pod 的整个步骤。
- 推理
- 这些工作负载通常是长期运行或无限期运行的,并且可以小到消耗节点设备的一个子集,也可以大到跨多个节点。它们通常需要下载包含模型权重的巨大文件。
这些工作负载类型特别打破了许多过去的假设
过去 | 现在 |
---|---|
可以获得更好的 CPU,应用程序将运行得更快。 | 需要一个特定的设备(或一类设备)才能运行。 |
当出现问题时,只需重新创建它。 | 分配或重新分配成本高昂。 |
任何节点都可以工作。Pod 之间无需协调。 | 以特殊方式调度——设备通常以跨节点拓扑连接。 |
每个 Pod 如果失败都可以即插即用地替换。 | Pod 是更大任务的一部分。整个任务的生命周期取决于每个 Pod。 |
容器镜像轻巧且易于获取。 | 容器镜像可能非常大,需要特殊处理。 |
漫长的初始化可以通过缓慢的推出抵消。 | 初始化可能很漫长,应该进行优化,有时是许多 Pod 同时进行。 |
计算节点商品化且相对便宜,因此一些空闲时间是可以接受的。 | 带有专用硬件的节点可能比没有专用硬件的节点贵一个数量级,因此空闲时间非常浪费。 |
现有的故障模型依赖于旧的假设。它可能仍然适用于新的工作负载类型,但它对设备的了解有限,并且对它们来说非常昂贵。在某些情况下,甚至贵得令人望而却步。您将在本文后面看到更多示例。
为什么 Kubernetes 仍然至高无上
本文不深入探讨以下问题:为什么不为
AI/ML 工作负载从头开始,因为它们与传统的 Kubernetes 工作负载如此不同。尽管面临诸多挑战,Kubernetes 仍然是 AI/ML 工作负载的首选平台。其成熟度、安全性和丰富的工具生态系统使其成为一个引人注目的选择。虽然存在替代方案,但它们通常缺乏 Kubernetes 提供的多年开发和完善。Kubernetes 开发人员正在积极解决本文及其他地方发现的差距。
设备故障处理的现状
本节概述了不同的故障模式以及当今使用的最佳实践和 DIY(自己动手)解决方案。下一节将描述改进这些故障模式的路线图。
故障模式:K8s 基础设施
为了了解与 Kubernetes 基础设施相关的故障,您需要了解将 Pod 调度到节点上涉及多少个移动部件。当 Pod 调度到节点上时,事件序列如下
- 设备插件调度到节点上
- 设备插件通过本地 gRPC 注册到 kubelet
- Kubelet 使用设备插件监视设备并更新节点的容量
- 调度器根据更新后的容量将用户 Pod 放置到节点上
- Kubelet 要求设备插件为用户 Pod 分配设备
- Kubelet 创建一个用户 Pod,并将分配的设备附加到它
此图显示了其中一些涉及的参与者
由于存在如此多的相互关联的参与者,它们中的每一个以及每个连接都可能遇到中断。这会导致许多通常被认为是故障的异常情况,并可能导致严重的工作负载中断
- Pod 在其生命周期的各个阶段拒绝准入
- Pod 无法在完全正常的硬件上运行
- 调度耗时出乎意料的长
Kubernetes 的目标是使这些组件之间的中断尽可能可靠。Kubelet 已经实现了重试、宽限期和其他技术来改进它。路线图部分详细介绍了 Kubernetes 项目跟踪的其他边缘情况。然而,所有这些改进只有在遵循这些最佳实践时才有效
- 尽早配置并重新启动 kubelet 和容器运行时(如 containerd 或 CRI-O),以免中断工作负载。
- 监控设备插件健康状况并仔细规划升级。
- 不要用不重要的工作负载使节点超载,以防止设备插件和其他组件中断。
- 配置用户 Pod 容忍度以处理节点就绪性闪烁。
- 仔细配置和编写优雅终止逻辑,以免长时间阻塞设备。
另一类与 Kubernetes 基础设施相关的问题是驱动程序相关问题。对于 CPU 和内存等传统资源,无需在应用程序和硬件之间进行兼容性检查。对于硬件加速器等特殊设备,存在新的故障模式。节点上安装的设备驱动程序
- 必须与硬件匹配
- 与应用程序兼容
- 必须与其他驱动程序(如 nccl 等)一起工作
处理驱动程序版本的最佳实践
- 监控驱动程序安装程序健康状况
- 规划基础设施和 Pod 的升级以匹配版本
- 尽可能进行金丝雀部署
遵循本节中的最佳实践并使用来自受信任和可靠来源的设备插件和设备驱动程序安装程序通常可以消除此类故障。Kubernetes 正在跟踪工作以使这一领域变得更好。
故障模式:设备故障
Kubernetes 目前对设备故障的处理很少。设备插件仅通过更改可分配设备的数量来报告设备故障。Kubernetes 依靠活性探针或容器故障等标准机制,让 Pod 将故障情况传达给 kubelet。然而,Kubernetes 不会将设备故障与容器崩溃相关联,除了在连接到同一设备时重新启动容器外,不提供任何缓解措施。
这就是为什么存在许多插件和 DIY 解决方案来根据各种信号处理设备故障。
健康控制器
在许多情况下,设备故障将导致无法恢复且非常昂贵的节点处于空闲状态。一个简单的 DIY 解决方案是节点健康控制器。该控制器可以比较设备的分配计数和容量,如果容量更大,则启动一个计时器。一旦计时器达到阈值,健康控制器将终止并重新创建节点。
健康控制器方法存在问题
- 通常不知道设备故障的根本原因
- 控制器不了解工作负载
- 故障设备可能未使用,您希望保持其他设备运行
- 由于它非常通用,检测可能太慢
- 节点可能是更大节点集的一部分,不能在没有其他节点的情况下单独删除
存在解决上述部分问题的健康控制器变体。这里的主题是,为了最好地处理故障设备,您需要针对特定工作负载进行定制处理。Kubernetes 尚未提供足够的抽象来表达设备对节点、集群以及它所分配的 Pod 的重要性。
Pod 故障策略
另一种处理设备故障的 DIY 方法是针对故障设备进行逐个 Pod 的反应。此方法适用于实现为作业的训练工作负载。
Pod 可以为设备故障定义特殊的错误代码。例如,每当遇到意外设备行为时,Pod 会以特殊的退出代码退出。然后,Pod 故障策略可以以特殊方式处理设备故障。阅读更多关于 使用 Pod 故障策略处理可重试和不可重试的 Pod 故障
作业的Pod 故障策略方法存在一些问题
- 没有众所周知的设备故障条件,因此此方法不适用于通用 Pod 情况
- 错误代码必须仔细编写,在某些情况下很难保证。
- 由于 Pod 故障策略功能的限制,仅适用于带有
restartPolicy: Never
的作业。
因此,此解决方案的适用性有限。
自定义 Pod 观察器
一个更通用的方法是实现 Pod 观察器作为 DIY 解决方案,或使用提供此功能的第三方工具。Pod 观察器最常用于处理推理工作负载的设备故障。
由于 Kubernetes 只是将 Pod 分配给设备,即使设备据报道不健康,其想法是使用 Pod 观察器检测这种情况并应用一些修复。它通常涉及获取设备健康状态及其使用节点上的 Pod 资源 API 到 Pod 的映射。如果设备故障,它可以删除附加的 Pod 作为修复措施。副本集将处理健康设备上的 Pod 重新创建。
实现此观察器的其他原因
- 没有它,Pod 将永远保持分配给故障设备。
- 对于带有
restartPolicy=Always
的 Pod,没有调度。 - 没有内置控制器会删除 CrashLoopBackoff 中的 Pod。
自定义 Pod 观察器的问题
- 获取 Pod 观察器的信号成本高昂,并且涉及一些特权操作。
- 它是一个自定义解决方案,它假设设备对 Pod 的重要性。
- Pod 观察器依赖外部控制器重新调度 Pod。
还有更多处理设备故障或即将维护的 DIY 解决方案变体。总的来说,Kubernetes 有足够的扩展点来实现这些解决方案。然而,一些扩展点需要比用户可能习惯的更高权限或破坏性太大。路线图部分详细介绍了处理设备故障的具体改进。
故障模式:容器代码故障
当容器代码失败或发生内存不足等问题时,Kubernetes 知道如何处理这些情况。要么重新启动容器,要么如果 Pod 具有 restartPolicy: Never
,则 Pod 崩溃并将其调度到另一个节点上。Kubernetes 对什么是故障(例如,非零退出代码或活性探针失败)以及如何对这种故障做出反应(主要是始终重新启动或立即使 Pod 失败)的表达能力有限。
这种表达能力通常不足以应对复杂的 AI/ML 工作负载。AI/ML Pod 最好在本地甚至就地重新调度,因为这样可以节省镜像拉取时间和设备分配。AI/ML Pod 通常相互连接,需要一起重新启动。这增加了另一个复杂性级别,优化它通常可以为运行 AI/ML 工作负载带来巨大的节省。
有各种 DIY 解决方案来处理 Pod 故障编排。最典型的方法是使用一些编排器将主可执行文件包装在容器中。当由于某个其他 Pod 失败而需要重新启动作业时,此编排器将能够重新启动主可执行文件。
像这样的解决方案非常脆弱且复杂。当用于大型训练作业时,与常规 JobSet 删除/重新创建周期相比,它们通常值得节省的资金。通过在 Kubernetes 中开发新的钩子和扩展点,使这些解决方案不那么脆弱且更简化,将使其易于应用于较小的作业,从而使所有人受益。
故障模式:设备降级
并非所有设备故障都对整体工作负载或批处理作业是致命的。随着硬件堆栈变得越来越复杂,硬件堆栈层之一的配置错误或驱动程序故障可能导致设备功能正常,但性能滞后。一个滞后的设备会减慢整个训练作业。
我们看到此类案例的报告越来越多。Kubernetes 今天无法表达此类故障,并且由于它是最新类型的故障模式,硬件供应商没有提供太多用于检测的最佳实践,也没有第三方工具用于修复这些情况。
通常,这些故障是根据观察到的工作负载特征检测的。例如,AI/ML 训练步骤在特定硬件上的预期速度。这些问题的补救措施高度依赖于工作负载需求。
路线图
如上文所述,Kubernetes 提供了许多扩展点,用于实现各种 DIY 解决方案。AI/ML 领域发展迅速,需求和使用模式不断变化。SIG Node 正在采取一种有节制的方法,通过启用更多扩展点来实现特定于工作负载的场景,而不是引入新的语义来支持特定场景。这意味着优先提供有关故障的信息,而不是为那些可能只适用于部分工作负载的故障实施自动修复。
这种方法确保工作负载处理没有剧烈变化,这可能会破坏现有、运行良好的 DIY 解决方案或现有更传统工作负载的体验。
当今使用的许多错误处理技术适用于 AI/ML,但成本高昂。SIG Node 将投资于扩展点,以降低这些成本,并认识到 AI/ML 的降价至关重要。
以下是我们为各种故障模式设想的一系列具体投资。
故障模式路线图:K8s 基础设施
Kubernetes 基础设施领域最容易理解,并且对于即将到来的从设备插件到 DRA 的过渡非常重要。SIG Node 正在跟踪该领域的许多工作项,其中最值得注意的是以下几项
- 集成 kubelet 与 systemd 看门狗 · Issue #127460
- DRA:检测过时的 DRA 插件套接字 · Issue #128696
- 支持设备管理器/设备插件的接管 · Issue #127803
- Kubelet 插件注册可靠性 · Issue #127457
- 如果失败,重新创建设备管理器 gRPC 服务器 · Issue #128167
- 在设备插件 gRPC 失败时重试 Pod 准入 · Issue #128043
基本上,Kubernetes 组件的每次交互都必须通过 kubelet 改进或插件开发和部署中的最佳实践来实现可靠性。
故障模式路线图:设备故障
对于设备故障,在 Kubernetes 可以支持的常见场景中已经出现了一些模式。然而,第一步是更轻松地获取有关故障设备的信息。这里的第一步是 KEP 4680 中的工作(将资源健康状态添加到设备插件和 DRA 的 Pod 状态)。
更长期的想法包括待测试
- 将设备故障集成到 Pod 故障策略中。
- 节点本地重试策略,为带有 restartPolicy=OnFailure 的 Pod 启用 Pod 故障策略,并可能超出此范围。
- 能够取消调度 Pod,包括带有
restartPolicy: Always
的 Pod,以便它可以获得新分配的设备。 - 将设备健康状况添加到用于在 DRA 中表示设备的 ResourceSlice,而不是简单地从 ResourceSlice 中撤回不健康的设备。
故障模式路线图:容器代码故障
处理 AI/ML 工作负载容器代码故障的主要改进都旨在降低错误处理和恢复的成本。成本降低主要来自于尽可能多地重用预分配资源。从就地重新启动容器以重用 Pod,到尽可能在节点本地重新启动容器而不是重新调度,再到快照支持,以及重新调度时优先选择同一节点以节省镜像拉取时间。
考虑一下这个场景:一个大型训练作业需要运行 512 个 Pod。其中一个 Pod 失败了。这意味着所有 Pod 都需要中断并同步以重新启动失败的步骤。实现这一点的最有效方法通常是就地重新启动尽可能多的 Pod,同时替换失败的 Pod 以清除其中的错误。如下图所示
可以实现此场景,但由于 Kubernetes 缺乏某些扩展点,所有实现此场景的解决方案都很脆弱。在 Kubernetes 路线图上,添加这些扩展点以实现此场景。
故障模式路线图:设备降级
这方面的工作很少——没有明确的检测信号,故障排除工具非常有限,也没有内置语义来表达 Kubernetes 上的“降级”设备。曾讨论过在 DRA 用于表示设备的 ResourceSlice 中添加设备性能或降级数据,但尚未明确定义。还有像 node-healthcheck-operator 这样的项目可以用于某些场景。
我们期待硬件供应商和云提供商在这一领域的发展,并且预计在不久的将来会看到主要是 DIY 解决方案。随着越来越多的用户接触到 AI/ML 工作负载,这是一个需要反馈模式的领域。
加入对话
Kubernetes 社区鼓励反馈和参与塑造设备故障处理的未来。加入 SIG Node,为正在进行的讨论做出贡献!
这篇博文提供了 Kubernetes 中设备故障管理面临的挑战和未来方向的概要。通过解决这些问题,Kubernetes 可以巩固其作为 AI/ML 工作负载领先平台的地位,确保依赖专用硬件的应用程序的弹性和可靠性。