Kubernetes(简称 K8s)是开源的容器编排平台——容器是把应用连同运行环境打包好的软件单元,K8s 负责自动部署、扩容和管理成千上万个这样的容器。K8s 在 v1.37 版本把 etcd RangeStream 特性推进到 beta(测试成熟)阶段并默认开启:配合 etcd v3.7,它把「从数据库一次性读回整个大列表」改成「按块边读边释放的流式读取」,使 API server 与 etcd 的内存峰值从难以预测变为可控,降低大规模集群因内存耗尽(OOM)而崩溃的风险。

要理解这个改进,先分清两个组件。etcd 是 Kubernetes 的数据库:一个开源的分布式键值存储,集群里所有状态——有哪些节点、跑了哪些应用——都存在里面。API server 是 Kubernetes 控制面(集群管理的中枢)的总入口,所有读写集群状态的请求都由它处理。

大列表读取为什么费内存

API server 处理大部分列表查询(list,一次取回某类资源的全部对象)和监听请求(watch,持续跟踪资源变化)时,直接应答自内存中的监听缓存(watch cache)。但填充这个缓存,必须在启动时和每次重建时,从 etcd 读出某类资源的全量状态。

资源一多、单个对象一大(比如 Pod 很多的集群;Pod 是 K8s 里最小的运行单元),这次全量读取就很贵。旧实现按键数分页:一次向 etcd 要固定数量的键(键值存储中每条数据的编号),而不是一次要整个集合。问题在于按键数分页不感知对象大小——一页里可能装满大对象,这一页仍然非常大。内存占用因此难以预测,对象又大、并发读又多时,就可能触发 OOM。而且 etcd 的单次读取(unary Range)会先把整页数据拼完再发送,API server 解码期间也持有这份数据,同一份内容同时占两份内存,压力大半落在 etcd 身上。

RangeStream 怎么改

etcd v3.7 新增了流式版本的读取接口,名叫 RangeStream。它属于 RPC(远程过程调用,程序之间调用服务的方式),入参和返回结果与原来的 Range 完全一致,差别只在传输方式:etcd 不再等整页拼完,而是把结果拆成小块(chunk)边生成边发送,每块大小按实际返回内容自适应——按字节数而不是按键数切分,单个大对象组成的页不会再让内存占用失控。

API server 收到一块、解码一块、随即释放,再取下一块。整个过程中,etcd 和 API server 双方都不再同时持有整份数据,内存随读取进度逐步释放,峰值变得可控。

使用前提与开关

启用条件很简单:Kubernetes v1.37 或更高,etcd v3.7 或更高。特性开关 EtcdRangeStream 在 v1.37 中处于 beta 状态、默认开启:API server 启动时会探测 etcd 是否支持,运行中若收到不支持(Unimplemented)的返回,会自动回退到原来的分页读取,因此配老版本 etcd 的集群不受影响。如确需关闭,在 kube-apiserver 启动参数中加 --feature-gates=EtcdRangeStream=false。

如何确认生效

API server 把流式读取记录在 etcd 指标的 operation 标签下。查看指标 etcd_request_duration_seconds_count,若 operation="listStream" 的计数非零,说明 RangeStream 正在工作;若一直为零,多半是 etcd 版本低于 v3.7,仍在走分页路径。该特性对应的官方增强提案编号为 KEP-5966。

值得带走的结论

升级到 Kubernetes v1.37 + etcd v3.7 之后,RangeStream 开箱即用:大列表读取的内存峰值从「不可预测」变成「可控」,这是大规模集群降低 OOM 风险的实在收益。确认它是否生效,只需看 listStream 这一个指标。