近日,Kubernetes社区在发布1.36版本后不久,收到大量生产环境用户反馈:集群中部分节点出现kubelet进程内存异常增长,最终触发OOM(内存耗尽)导致节点不可用。经过社区核心维护团队两周的紧急排查,确认该问题为kubelet内部一处因新特性引入的内存泄漏漏洞。目前官方已发布补丁,并建议所有运行1.36版本的用户立即升级。

问题表现:节点“失联”背后的元凶

据多位运维工程师在GitHub Issue中描述,集群升级至Kubernetes 1.36后,原本稳定的节点会经历“缓慢死亡”过程:kubelet内存占用从初始的200-300 MiB逐步攀升至数GB,在达到节点内存上限后触发OOM Killer,该节点上的Pod全部被驱逐,状态变为NotReady。由于泄漏过程通常持续数小时至数天,具有极强的隐蔽性,不少团队在排查时一度误判为Pod资源分配不当或监控系统故障。

CNCF(云原生计算基金会)的SIG Node团队在收到报告后迅速复现了该问题。首席工程师Jordan Liggitt表示:“在1.36中,我们为支持动态资源分配引入了新的pods状态缓存机制。该机制在特定场景下——尤其是节点同时管理大量静态Pod和动态Pod时——存在引用计数未释放的缺陷,导致每次Pod状态同步都会泄漏约4KB内存。一个管理200个Pod的节点,运行一周后内存占用就会超出正常值的10倍。”

根因分析:新特性与传统逻辑的冲突

深入分析后发现,泄漏点位于kubelet的核心组件——Pod管理模块中的listAndWatch循环。1.36版本为了优化Pod状态查询性能,增加了一层LRU缓存来存储从API Server获取的Pod List。但设计者忽略了当存在“孤儿Pod”(即由kubelet管理但未在API Server注册的静态Pod)时,缓存中对应的条目不会随Pod生命周期结束而被清理。

更具体地说,静态Pod的生命周期与Node本地配置绑定,而非通过API Server创建。kubelet在每次同步时会将这些Pod计入缓存,但退出清理逻辑只覆盖了通过API Server创建的标准Pod。当管理员频繁修改静态Pod清单或重启kubelet时,历史缓存条目持续累积,最终“撑爆”内存。

“这本质上是一个‘遗忘的角落’——新代码没有考虑到传统静态Pod的特殊性。”SIG Node reviewer Tim Allclair在社区会议中坦言。该问题在1.36 Alpha阶段曾因测试集群规模较小而未暴露,但在百节点甚至千节点集群中迅速显现。

修复方案:两行代码与一个配置参数

官方在1.36.1补丁版本中提供了双重修复:

  1. 代码层面:在缓存清理函数中加入对静态Pod特殊处理,确保任何类型的Pod在生命周期结束后都能及时释放其缓存条目。修复仅涉及pkg/kubelet/pod/kubelet_pods.go中的两行代码。

  2. 配置层面:新增kubelet启动参数--pod-status-cache-max-entries(默认值0表示无上限),允许管理员明确限制缓存最大条目数。当缓存超限时,kubelet会主动淘汰最旧的条目,而非无限制增长。

此外,社区建议用户同时启用Pod状态指标的memory_working_set_bytes监控,并设置告警阈值。一旦kubelet内存超过基线的两倍,应立即触发节点排空与重启。

升级建议:先治标,再治本

对于已经受到影响的集群,SRE专家推荐以下紧急处理步骤:

  • 立即对出现高内存的节点执行kubectl drain并重启kubelet(需确认节点上运行的是可中断工作负载)。
  • 回退至Kubernetes 1.35.x版本,直至完成1.36.1升级。
  • 如果暂时无法回退,可在kubelet配置文件中手动设置--pod-status-cache-max-entries=500作为临时缓解措施,但需注意这可能导致Pod状态偶尔延迟。

截至发稿时,Kubernetes 1.36.1已进入发布流程,预计48小时内可通过官方渠道下载。CNCF提醒所有生产环境用户,鉴于内存泄漏的隐蔽性和破坏性,应尽快规划升级窗口。同时,社区正在将针对此问题的单元测试与集成测试纳入CI管道,防止类似问题在未来版本中复现。

Kubernetes生态的成熟,离不开每一次细小缺陷的修补。对于1.36的用户而言,这次“内存泄漏”经历无疑是一次痛苦的教训,但也再次印证了社区快速响应、透明沟通的价值观。云原生的航道上,暗礁常在,但好的舵手总能及时修正航向。