Kubernetes 扩展到 7,500 个节点
摘要
# Kubernetes 扩展到 7,500 个节点 来源:[https://openai.com/index/scaling-kubernetes-to-7500-nodes/](https://openai.com/index/scaling-kubernetes-to-7500-nodes/) OpenAI将单个 Kubernetes 集群扩展到这个规模很少见,需要特殊的关注,但好处是提供了一个简单的基础设施,让我们的机器学习研究团队能够更快地迭代并扩展,而无需改变代码。从我们之前关于[扩展到 2,500 个节点](https://openai.com/index/scaling-kube)的文章以来
我们已经将 Kubernetes 集群扩展到 7,500 个节点,为 GPT-3、CLIP 和 DALL·E 等大规模模型打造了可扩展的基础设施,同时也支持快速的小规模迭代研究,例如神经语言模型的扩展法则研究。
查看缓存全文
缓存时间:
2026/04/20 14:55
# 将 Kubernetes 扩展到 7,500 个节点
来源:https://openai.com/index/scaling-kubernetes-to-7500-nodes/
将单个 Kubernetes 集群扩展到这种规模很少有人尝试,需要特殊的处理方式,但好处是基础设施简单,允许我们的机器学习研究团队更快地迭代和扩展,而无需修改代码。
自上一篇关于[扩展到 2,500 个节点](https://openai.com/index/scaling-kubernetes-to-2500-nodes/)的文章以来,我们持续扩展了基础设施来满足研究人员的需求,并在这个过程中学到了许多额外的经验。本文总结了这些经验,使 Kubernetes 社区的其他人也能受益,最后我们提到了仍未解决的问题,这些将是我们后续重点解决的方向。
在继续之前,重要的是要描述我们的工作负载。我们用 Kubernetes 运行的应用程序和硬件与典型公司中遇到的非常不同。我们的问题和相应的解决方案可能与也可能不与你的设置相符!
大规模机器学习作业跨越多个节点,当它能够访问每个节点上的所有硬件资源时,运行效率最高。这使得 GPU 能够使用 [NVLink](https://www.nvidia.com/en-us/data-center/nvlink/) 进行直接交叉通信,或 GPU 能够使用 [GPUDirect](https://developer.nvidia.com/gpudirect) 与 NIC 直接通信。因此,对于我们的许多工作负载,单个 Pod 占用整个节点。NUMA、CPU 或 PCIe 资源争用不是调度因素。装箱或碎片化不是常见问题。我们目前的集群具有完整的等分带宽,因此我们也不考虑机架或网络拓扑。所有这一切意味着,虽然我们有许多节点,但调度器上的压力相对较小。
也就是说,kube-scheduler 的压力是间歇性的。新作业可能由数百个同时创建的 Pod 组成,然后恢复到相对较低的变动率。
我们最大的作业运行 MPI,作业中的所有 Pod 都参与单个 MPI 通信器。如果任何参与的 Pod 失败,整个作业就会停止并需要重新启动。作业定期设置检查点,重新启动时会从最后一个检查点恢复。因此,我们认为这些 Pod 是*半有状态的*——被杀死的 Pod 可以被替换,工作可以继续进行,但这样做是有破坏性的,应该尽量避免。
我们不太依赖 Kubernetes 负载均衡。我们只有很少的 HTTPS 流量,不需要 A/B 测试、蓝绿部署或金丝雀发布。Pod 通过 MPI 和 SSH 在其 Pod IP 地址上直接相互通信,而不是通过服务端点。服务"发现"功能有限;我们只是在作业启动时进行一次查询来确定哪些 Pod 参与 MPI。
大多数作业以某种形式与 blob 存储交互。它们通常要么从 blob 存储流式传输数据集的某些分片或直接检查点,要么将其缓存到快速本地临时磁盘。我们有少量 PersistentVolume 用于需要 POSIX 语义的情况,但 blob 存储的可扩展性更强,不需要缓慢的卸载/挂载操作。
最后,我们的工作本质上是研究性的,这意味着工作负载本身在不断变化。虽然超级计算团队致力于提供我们认为的"生产"级别质量的计算基础设施,但在该集群上运行的应用程序是短期的,其开发人员迭代速度快。新的使用模式可能随时出现,对我们关于趋势和适当权衡的假设提出挑战。我们需要一个可持续的系统,同时也允许我们在事情发生变化时快速做出反应。
## 网络
随着集群中节点和 Pod 数量的增加,我们发现 Flannel 在扩展所需吞吐量方面存在困难。我们切换到使用 Azure VMSSes IP 配置的原生 Pod 网络技术和相关的 CNI 插件。这使我们能够在 Pod 上获得主机级网络吞吐量。
我们改用基于别名的 IP 寻址的另一个原因是,在我们最大的集群中,任何时候可能有大约 200,000 个 IP 地址在使用。当我们测试基于路由的 Pod 网络时,我们发现我们能有效使用的路由数量存在显著限制。
避免封装增加了对底层 SDN 或路由引擎的需求,但保持了网络设置的简洁。VPN 或隧道可以无需任何额外适配器进行添加。我们不需要担心由于网络某部分具有较低 MTU 而导致的数据包碎片化。网络策略和流量监控很简单;对数据包的源和目标没有歧义。
我们在主机上使用 iptables 标记来追踪每个命名空间和 Pod 的网络资源使用情况。这让研究人员可以可视化他们的网络使用模式。特别是,由于我们很多实验都有明显不同的互联网和 Pod 内通信模式,通常能够调查任何潜在瓶颈出现在哪里是很有用的。
iptables 的 `mangle` 规则可用于任意标记与特定条件匹配的数据包。以下是我们用来检测流量是内部流量还是互联网流量的规则。`FORWARD` 规则涵盖来自 Pod 的流量,而 `INPUT` 和 `OUTPUT` 流量来自主机:
标记后,iptables 将开始计数器来追踪与此规则匹配的字节和数据包数量。你可以使用 `iptables` 本身查看这些计数器:
我们使用一个名为 [iptables-exporter](https://github.com/madron/iptables-exporter/) 的开源 Prometheus 导出器,将这些追踪的数据纳入我们的监控系统。这是追踪匹配各种不同类型条件的数据包的简单方法。
我们网络模型的一个特别之处是,我们完全向研究人员公开节点、Pod 和服务网络 CIDR 范围。我们有一个中心辐射网络模型,并使用原生节点和 Pod CIDR 范围来路由该流量。研究人员连接到中心,从那里可以访问任何单个集群(辐射)。但集群之间无法相互通信。这确保了集群保持隔离,没有可能破坏故障隔离的跨集群依赖项。
我们使用"NAT"主机来转换来自集群外部流量的服务网络 CIDR 范围。此设置为我们的研究人员在选择如何以及他们能够为其实验选择什么类型的网络配置方面提供了显著的灵活性。
## API 服务器和 etcd
Kubernetes API 服务器和 etcd 是健康工作集群的关键组件,因此我们特别关注这些系统的压力。我们使用 [kube-prometheus](https://github.com/coreos/kube-prometheus) 提供的 Grafana 仪表板,以及额外的内部仪表板。我们发现监控 API 服务器上 HTTP 状态 429(请求过多)和 5xx(服务器错误)的速率作为问题的高级信号很有用。
虽然有些人在 kube 内运行 API 服务器,但我们始终在集群外部运行它们。etcd 和 API 服务器都在各自专用节点上运行。我们最大的集群运行 5 个 API 服务器和 5 个 etcd 节点来分散负载,并最小化其中一个出现故障时的影响。自从我们在[上一篇博文](https://openai.com/index/scaling-kubernetes-to-2500-nodes/)中将 Kubernetes 事件分离到各自的 etcd 集群以来,我们在 etcd 上没有遇到任何明显问题。API 服务器是无状态的,通常易于在自愈实例组或规模集中运行。我们还没有尝试为 etcd 集群构建任何自愈自动化,因为事件已经非常罕见。
API 服务器可能会占用大量内存,这往往与集群中节点的数量成线性增长。对于我们有 7,500 个节点的集群,我们观察到每个 API 服务器使用高达 70GB 的堆,因此幸运的是这在未来应该仍然处于硬件能力范围内。
对 API 服务器的一个大压力是对端点的监视。有几个服务,例如 'kubelet' 和 'node-exporter',集群中的每个节点都是其成员。当节点被添加或从集群中移除时,此监视会触发。因为通常每个节点本身都在通过 kube-proxy 监视 `kubelet` 服务,这些响应中所需的 # 和带宽将是 N²,非常庞大,有时达到 1GB/s 或更多。[EndpointSlices](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/),在 Kubernetes 1.17 中推出,是一个巨大的好处,将此负载降低了 1000 倍。
一般来说,我们非常谨慎地对待任何与集群大小成比例的 API 服务器请求。我们试图避免任何 DaemonSet 与 API 服务器交互。在需要每个节点监视更改的情况下,引入中间缓存服务(例如 [Datadog 集群代理](https://docs.datadoghq.com/agent/cluster_agent/))似乎是避免集群范围瓶颈的好模式。
随着集群的增长,我们进行的实际集群自动扩展越来越少。但我们偶尔会在一次自动扩展太多时遇到问题。新节点加入集群时会产生许多请求,一次添加数百个节点可能会使 API 服务器容量过载。即使只是平滑几秒钟,也有助于避免停机。
## 监控
我们使用 Prometheus 收集时间序列指标,使用 Grafana 进行图表、仪表板和告警。我们从 [kube-prometheus](https://github.com/coreos/kube-prometheus) 的部署开始,它收集了多种指标,并为可视化提供了良好的仪表板。随着时间推移,我们添加了许多自己的仪表板、指标和告警。
随着节点增加越来越多,我们遇到了 Prometheus 收集的指标数量庞大的困难。虽然 kube-prometheus 公开了很多有用的数据,但其中一些我们实际上从未查看过,还有一些粒度太细,无法有效地收集、存储和查询。我们使用 [Prometheus 规则](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config)来"丢弃"这些指标中的一些,使其不被摄入。
我们曾经长期被一个问题所困扰,即 Prometheus 会消耗越来越多的内存,最终由于内存不足错误 (OOM) 而导致容器崩溃。即使向应用程序投入了大量内存容量后,这种情况似乎仍然会发生。更糟的是,当它确实崩溃时,在启动时重放预写日志文件需要许多小时才能可用。
最终我们[追踪到 OOM 的来源](https://github.com/prometheus/prometheus/issues/5547#issuecomment-592797528)是 Grafana 和 Prometheus 之间的交互,其中 Grafana 会在 Prometheus 上使用 `/api/v1/series` API,查询为 `{le!=""}` (基本上是"给我所有直方图指标")。`/api/v1/series` 的实现在时间和空间上都是无限的——对于有许多结果的查询,这会继续消耗越来越多的内存和时间。它也会在请求者已放弃并关闭连接后继续增长。对我们来说,从未有足够的内存,Prometheus 最终会崩溃。我们[修补](https://github.com/openai/prometheus/pull/1)了 Prometheus 以在上下文中限制此 API 以强制超时,这完全解决了问题。
虽然 Prometheus 崩溃的频率降低了,但在我们确实需要重新启动它的时候,WAL 重放仍然是一个问题。通常需要许多小时来重放所有 WAL 日志,然后 Prometheus 才能启动收集新指标和服务查询。在 [Robust Perception](https://www.robustperception.io/) 的帮助下,我们发现应用 `GOMAXPROCS=24` 有很大的改进。Prometheus 在 WAL 重放期间尝试使用所有核心,对于具有大量核心的服务器,争用会杀死所有性能。
我们在下面的["未解决的问题"](https://openai.com/index/scaling-kubernetes-to-7500-nodes/#unsolvedproblems)部分中探索了增加监控容量的新选项。
## 健康检查
对于这样大的集群,我们当然依赖自动化来检测和移除集群中的故障节点。随着时间推移,我们建立了许多健康检查系统。
某些健康检查是被动的,始终在所有节点上运行。这些监控基本系统资源,例如网络可达性、磁盘故障或满、或 GPU 错误。GPU 以多种不同方式表现问题,但一个容易的共同问题是"不可纠正的 ECC 错误"。Nvidia 的数据中心 GPU 管理器(DCGM)工具使查询此问题和许多其他"Xid"错误变得容易。我们追踪这些错误的一种方式是通过 [dcgm-exporter](https://github.com/NVIDIA/gpu-monitoring-tools#dcgm-exporter) 将指标摄入到 Prometheus,我们的监控系统。这将显示为 `DCGM_FI_DEV_XID_ERRORS` 指标,并设置为最近发生的错误代码。此外,[NVML 设备查询 API](https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceQueries.html#group__nvmlDeviceQueries) 公开了有关 GPU 健康和运行的更详细信息。
一旦我们检测到错误,通常可以通过重置 GPU 或系统来修复,尽管在某些情况下确实会导致底层 GPU 需要物理更换。
另一种形式的健康检查追踪来自上游云提供商的维护事件。主要云提供商中的每一个都公开了一种方法来了解当前 VM 是否即将进行维护事件,最终会导致中断。VM 可能需要重新启动以便应用底层管理程序补丁或将物理节点交换为其他硬件。
这些被动健康检查在所有节点上持续在后台运行。如果健康检查开始失败,节点会自动被隔离,以便不会有新的 Pod 被调度到节点上。对于更严重的健康检查失败,我们还会尝试 Pod 驱逐以请求所有当前运行的 Pod 立即退出。仍然由 Pod 本身决定是否允许此驱逐发生,通过可配置的 Pod 中断预算。最终,在所有 Pod 已终止后,或经过 7 天(我们 SLA 的一部分),我们将强制终止 VM。
不幸的是,并非所有 GPU 问题都表现为通过 DCGM 可见的错误代码。我们已经建立了自己的测试库来执行 GPU,以捕获额外问题并确保硬件和驱动程序表现符合预期。这些测试无法在后台运行——它们需要独占使用 GPU 几秒钟或几分钟来运行。
我们首先在启动时在节点上运行这些测试,在一个我们称为"预检"的系统中。所有节点加入集群时都应用了"预检"污点和标签。此污点防止普通 Pod 被调度到节点上。DaemonSet 配置为在所有具有此标签的节点上运行预检测试 Pod。测试成功完成后,测试本身移除污点和标签,节点随后可用于一般使用。
我们还随后在节点的生命周期中定期运行这些测试。我们将其作为 CronJob 运行,允许其落在集群中任何可用节点上。这诚然在哪些节点被测试方面有点随意和不受控制,但我们发现随着时间推移,它以最小的协调或中断提供充分的覆盖。
随着我们扩展集群,研究人员开始发现...
相似文章
OpenAI Blog
OpenAI 分享了将 Kubernetes 扩展到 2,500 个节点的基础设施经验教训,详细介绍了容器镜像拉取的优化方案,包括 kubelet 配置更改、Docker overlay2 迁移和预加载策略,以解决 Pending pod 的问题。
OpenAI Blog
OpenAI 分享了他们的深度学习基础设施方法,并开源了 kubernetes-ec2-autoscaler,一个为 Kubernetes 优化的批处理自动扩展管理器,强调基础设施质量如何倍增研究进展。
OpenAI Blog
OpenAI 展示了在分布式 GPU 集群上训练大规模神经网络的全面技术,涵盖数据并行、管道并行、张量并行和专家混合等方法,以克服工程和可扩展性挑战。
Reddit r/AI_Agents
讨论了大规模部署 AI 代理的运营挑战,并将其与 Kubernetes 解决容器编排问题的方式相类比。认为代理生态系统需要类似的基础设施突破。
NVIDIA Blog
NVIDIA正将其GPU动态资源分配(DRA)驱动捐赠给CNCF及Kubernetes社区,使其从厂商主导转变为社区所有。此次捐赠旨在简化Kubernetes中面向AI工作负载的GPU资源管理,并通过与CNCF Confidential Containers社区的协作,为Kata Containers提供GPU支持。