通过 API 流式处理提升 Kubernetes API 服务器效率
高效管理 Kubernetes 集群至关重要,尤其是在集群规模不断增长的情况下。大型集群面临的一个重大挑战是 list 请求引起的内存开销。
在现有实现中,kube-apiserver 处理 list 请求时,会在将任何数据传输给客户端之前,先在内存中组装整个响应。但是,如果响应体很大,比如数百兆字节,会怎样呢?此外,设想一下,在短暂的网络中断后,多个 list 请求同时涌入的场景。虽然API 优先级和公平性已被证明能够合理地保护 kube-apiserver 免受 CPU 过载影响,但它对内存保护的作用明显较小。这可以通过单个 API 请求资源消耗的不同性质来解释——给定时间点的 CPU 使用量受到一个常数的限制,而内存是不可压缩的,会随着处理对象数量的增加而按比例增长,并且是无界的。这种情况带来了真正的风险,可能由于内存不足 (OOM) 条件在几秒钟内压垮并崩溃任何 kube-apiserver。为了更好地可视化这个问题,我们来看下面的图表。

该图表显示了在合成测试期间 kube-apiserver 的内存使用情况(更多详细信息请参阅合成测试部分)。结果清楚地表明,增加 informer 的数量会显著提升服务器的内存消耗。值得注意的是,在大约 16:40 时,服务器在仅服务 16 个 informer 时就崩溃了。
为什么 kube-apiserver 会为 list 请求分配如此多的内存?
我们的调查显示,之所以会发生如此大的内存分配,是因为服务器在将第一个字节发送给客户端之前必须
- 从数据库中获取数据,
- 从其存储格式反序列化数据,
- 最后通过将数据转换并序列化为客户端请求的格式来构建最终响应
这个顺序导致了显著的临时内存消耗。实际使用量取决于许多因素,例如页面大小、应用的过滤器(例如标签选择器)、查询参数以及单个对象的大小。
遗憾的是,无论是API 优先级和公平性还是 Golang 的垃圾回收或 Golang 内存限制,都无法在这些条件下阻止系统耗尽内存。内存分配是突然且迅速的,只需几个请求就可以迅速耗尽可用内存,导致资源耗尽。
根据 API 服务器在节点上的运行方式,它可能在这些不受控制的内存峰值期间超出配置的内存限制时被内核通过 OOM 杀死,或者如果未配置限制,它可能对控制平面节点产生更糟糕的影响。最糟糕的是,在第一个 API 服务器发生故障后,在 HA(高可用)设置中,同样的请求很可能会冲击到另一个控制平面节点,并可能产生相同的影响。这种情况可能难以诊断且难以恢复。
流式 list 请求
今天,我们很高兴宣布一项重大改进。随着 watch list 功能在 Kubernetes 1.32 中晋级 Beta,client-go 用户可以选择加入(在明确启用 WatchListClient
feature gate 后),通过从 list 请求切换到(一种特殊类型的)watch 请求来实现流式列表。
Watch 请求由 watch cache 提供服务,watch cache 是一种内存缓存,旨在提高读取操作的可伸缩性。通过单独流式传输每个条目而不是返回整个集合,新方法保持恒定的内存开销。API 服务器受限于 etcd 中允许的最大对象大小以及一些额外的分配。与传统的 list 请求相比,这种方法显著减少了临时内存使用量,确保了系统更高效、更稳定,尤其是在给定类型对象数量庞大或平均对象大小较大的集群中,这些集群尽管使用了分页,内存消耗仍然很高。
基于从合成测试中获得的洞察(参见合成测试),我们开发了一个自动化性能测试来系统地评估 watch list 功能的影响。该测试复制了相同的场景,生成大量带有大负载的 Secret,并扩展 informer 的数量以模拟繁重的 list 请求模式。该自动化测试会定期执行,以监控启用和禁用该功能时服务器的内存使用情况。
结果显示,启用 watch list 功能后,性能得到了显著提升。启用该功能后,kube-apiserver 的内存消耗稳定在约 2 GB。相比之下,禁用该功能时,内存使用量增加到约 20GB,增长了 10 倍!这些结果证实了新的流式 API 的有效性,它减少了临时内存占用。
为你的组件启用 API 流式传输
升级到 Kubernetes 1.32。确保你的集群使用 etcd 3.4.31+ 或 3.5.13+ 版本。更改你的客户端软件以使用 watch lists。如果你的客户端代码是用 Golang 编写的,你需要为 client-go 启用 WatchListClient
。有关启用该功能的详细信息,请阅读介绍 Client-Go 中的 Feature Gates:增强灵活性和控制力。
下一步是什么?
在 Kubernetes 1.32 中,尽管仍处于 Beta 阶段,该功能在 kube-controller-manager 中默认启用。最终,该功能将扩展到其他核心组件,如 kube-scheduler 或 kubelet;可能在该功能通用可用时,或者更早。鼓励其他第三方组件在 Beta 阶段选择加入该功能,尤其是在它们可能访问大量资源或具有潜在大对象大小的资源类型时。
目前,API 优先级和公平性为 list 请求分配了一个合理的小成本。这对于平均情况而言是必要的,因为在这种情况下 list 请求足够廉价,可以允许足够的并行度。但它无法应对包含大量大对象的尖峰异常情况。一旦 Kubernetes 生态系统的绝大多数组件切换到 watch list,就可以将 list 的成本估算值更改为更大的值,而不会在平均情况下降低性能,从而增强对未来可能到达 API 服务器的此类请求的保护。
合成测试
为了重现该问题,我们进行了一次手动测试,以了解 list 请求对 kube-apiserver 内存使用的影响。在测试中,我们创建了 400 个 Secret,每个 Secret 包含 1 MB 数据,并使用 informer 获取所有 Secret。
结果令人震惊,仅需要 16 个 informer 就导致测试服务器内存不足并崩溃,这表明在这种条件下内存消耗增长得有多快。
特别感谢 @deads2k 在塑形此功能方面提供的帮助。