规模不是一个数字:Kubernetes 控制平面耗尽容量的三种方式
Scale isn’t one number: three ways a Kubernetes control plane runs out of room
Kubernetes 控制平面的扩展瓶颈分为节点受限、变更受限和状态受限三类,三者相互独立,只按节点数调优会修错方向。节点受限型在 10 万节点、10 秒心跳下仅存活检测就产生约 1 万次写入/秒,lease 续约约占写入 QPS 的 70%;增加 apiserver 反而会复制整个 watch cache 而加剧内存压力。文章提出存储契约、节点存活契约、分片轴和缓存驻留四个接缝,其中 KEP-4743 定义存储接口边界,KEP-5866 引入哈希范围过滤,但缓存驻留尚未在上游实现。
正文 · AI 翻译
三个集群陷入困境,而它们看起来毫无相似之处。
第一个是训练集群。它在几千个节点的规模上停留了数月,随后一次新的训练任务落地,节点数一下子到了1.5万。Kube-apiserver开始吃内存。Etcd开始报怨。
第二个从未超过几千个节点,却过得糟糕得多。它是一个智能体平台,其对象是活的:会话被创建、挂起、恢复和销毁,每分钟发生数千次,每一次状态转换都是一次写入。
第三个既不大也不快:一个平台集群,安装了两百个operator,拥有数万个对象,而这些对象大多只是静静待着,并且每个apiserver都持有全部对象的副本。
其中两个是AI工作负载,一个不是,而这是最不有趣的部分。它们在三个不同的方向上耗尽空间,而修复其中一个对另外两个毫无帮助。
去找寻指导,你会发现……并不多。关于如何达到5000个节点的建议比比皆是。超过这个规模,或者从另一个方向看,互联网只会耸耸肩。
于是你增加更多apiservers。这是kOps中有文档记载的选项,也是大多数人会给出的标准答案。但这会让你的问题更糟,因为你每增加一个apiserver,就会复制整个watch cache。你买到了请求处理能力,却用你本就没有的内存来支付。
这些都不是研究项目。远超5000个节点的集群如今已在生产环境中运行于上游标准组件之上。没有人靠神奇的分支走到那一步;他们靠的是审慎考虑每个存储持有什么。
规模不是难点。缺的是组装说明。
三种规模
问控制平面需要多大,得到的答案总是以节点数计。第二个和第三个集群就是这样被针对一个它们并不存在的问题进行调优的。
“规模不是难点。缺的是组装说明。”
节点受限型。训练和RL集群:规模大、同构,压力来自每个kubelet的上报。在10万个节点、10秒心跳的情况下,仅存活检测就产生约每秒1万次写入,而在我们的测量中,lease续约约占写入QPS的70%。该控制平面所做的大部分事情,就是节点永远礼貌地说着“还在”。
变更受限型。智能体平台:规模适中,但一天中强度极高。将大量智能体会话打包到较小的工作pod池上的系统,会把每个actor的生命周期表达为自定义资源,因此最繁忙、最短命的对象是CRD,而写入速率与你拥有多少台机器毫无关系。
状态受限型。平台集群:种类多、对象多,其中大多数处于休眠状态。成本不在于写入;而在于常驻字节数乘以持有它们的每个apiserver。正是在这个轴上,增加apiserver的反射性做法会主动造成伤害。
它们是相互独立的:你可以把任何一个轴拉满,而另外两个轴处于最低水平。一个只知道节点数的配方会心安理得地调错东西。
接缝与配方
接缝是你能够拆解Kubernetes的地方:两个组件之间的契约,通常位于网络边界,并且是可替换的,因此你可以在不修补上游的情况下替换其背后的东西。
可以像裁缝看待夹克上的接缝那样来理解它们:在腰部收一收,在肩部放一放,还是同一件夹克。在不是接缝的地方剪开,你就做出了一块非常昂贵的抹布。
“可以像裁缝看待夹克上的接缝那样来理解它们。在不是接缝的地方剪开,你就做出了一块非常昂贵的抹布。”
配方是围绕这些接缝构建的、经过规模测试的配置:可执行的配置,要么通过其规模测试,要么失败,并由CI告诉你它们在哪一天不再成立。
四个接缝
接缝1:存储契约。哪种存储持有哪种资源类型。缓解变更压力。并非你集群中的所有状态都值得同等的持久性保证。事件和节点lease是短暂的,leader选举lease是短暂且高频的,它们都不需要与你的Deployments相同的、经Raft复制的持久存储。KEP-4743定义了存储接口边界,而--etcd-servers-overrides如今可将资源类型路由到不同的后端。etcd不是问题所在,这一切也都不主张替换它;一个通用的一致性存储不应在没有业务持久化需求时吸收流量。
缺口:--etcd-servers-overrides只认识内置资源,因此一个变更受限的平台无法路由那些正是产生其写入风暴的自定义资源。请留意未来版本中的一项增强。
接缝2:节点存活契约。存活状态如何到达控制平面。缓解节点数压力。针对那部分心跳负载有两个修复方案,而且它们可以组合。吸收它:在控制平面前面设置一个面向节点的层级,处理续约并只传播真正的状态转换。让它更便宜:通过etcd的内存lease刷新来路由续约,而不是完整的Raft提交。后者落在etcd本身,因此它有助于每个集群,直至三节点集群。
接缝 3:分片轴。控制器、缓存和存储都同意据此分片的字段。它同时缓解这三者。水平扩展意味着每一层都对对象如何划分达成一致。选错轴,你的控制器就会终其一生去够别人分片里的对象。KEP-5866 为 API 流量引入了哈希范围过滤,这是基础。协调、重新分片以及对控制器管理器进行分片不在范围内,而这三项都还需要做工作。
接缝 4:缓存驻留。apiserver 实际在内存中保留什么。缓解状态压力。还记得那个复制整个 watch 缓存的 apiserver 吗?那就是这个接缝,也是状态受限集群的全部问题所在。一个只与调度器通信的 apiserver 不需要每个对象都驻留,而一个双分片原型使每个分片的堆内存大约减半。存在一个下限:即使关闭所有可禁用的缓存,apiserver 每个 RBAC 对象仍要占用约 0.9 KB,因为授权器的 informer 无论如何都会持有它们。
缺口:驻留机制在上游尚不存在。KEP-5866 过滤的是出口流量,因此缓存仍然存储一切。原型分支和一份 KEP 更新草案已提交评审。
配方阶梯
配方规模组成接缝证据
经典高可用最多 5k 节点2 到 3 个相同的控制平面无已测量
角色拆分5k 到 20k为 API 服务、调度器、KCM、etcd 分别设置机器池4已交付,收益尚未测量
节点平面20k 到 65k角色拆分加上一个面向节点的层级来吸收 lease1、2已测量,n=1
分区存储约 100k节点平面加上将资源类型拆分到独立存储1 叠加在 2 之上已由外部演示
分片切片超出K 个分片单元,每个单元是一个控制器加上一个固定到哈希范围的 apiserver3、4尚不存在
最后一行是有意放在阶梯上的:公布一个尚无人搭建的台阶,正是它保持有用的方式。更大的缺口在于索引。你按节点数选择台阶,而这是三个轴中唯一有测量数据支撑的一个。没有人,包括我们,发布过针对变更受限或状态受限集群的经过规模测试的配方。
这一切都不是一个发行版:原版上游二进制文件,以经过 CI 测试的配置形式交付,因此任何改变这一算式的发布都会先以测试失败的形式显现出来。
你可以推动的地方
针对接缝 1 的按 CRD 存储配置。目前还没有 KEP,以便让用例来塑造它。如果你的热点对象是自定义资源,请把你的写入速率带到 SIG API Machinery。来自真实变更受限集群的数字是任何人能贡献的最有用的东西。
针对接缝 3 和 4 的 KEP-5866 和驻留扩展。原型分支已提交评审,设计尚未确定。
无人发布过的配方。超过 20,000 个节点,或者严重的变更压力,或者一个单纯很重的集群:你知道一些我们其他人不知道的东西。
我们会出席 KubeCon NA。带上你最糟糕的控制平面图,告诉我们哪个接缝我们搞错了。
训练集群、智能体平台以及不起眼的平台集群正在三个不同方向上耗尽空间,而这三者都应该用原版上游组件达到目标。大部分部件已经在工作台上。让我们把它们组装起来!
文章《规模不是一个数字:Kubernetes 控制平面耗尽容量的三种方式》首发于 The New Stack。