内容提要
文章记录 Kubernetes 1.36 中 kubelet 占用约 7.5 GiB 内存的排查过程。pprof 显示堆分配集中在 context,与上游 Issue #139823 的 Pod 同步 context 泄漏吻合:每轮同步新建 context 并覆盖取消函数,旧 context 被父级引用而无法回收。该回归影响 v1.36.0~v1.36.2,v1.35 不受影响,v1.36.3 和 v1.37.0 已修复。建议升级到含修复的补丁版本;暂无法升级可重启 kubelet 临时缓解,但内存仍会累积。
延伸解读
版本边界与升级选择
文章明确这处 context 泄漏是 Kubernetes 1.36 引入的回归,影响 v1.36.0~v1.36.2,v1.35 不受影响。v1.36.3 是首次包含修复的版本,v1.37.0 也已修复。因此,若节点运行在 1.36 分支,应选择该分支内包含修复的较新补丁版本,不必专门跨到 1.37。升级后需确认各节点实际运行的 kubelet 版本已更新,因为修复点在 kubelet。
pprof 证据的解读要点
文章用 pprof 的 inuse_space 展示仍存活对象的堆占用,而非累计分配量,且 heap profile 主要反映最近一次完成 GC 时的情况,与堆指标采样时刻、统计口径不同,数值不必完全一致。flat 归属函数自身,cum 包含下游调用,同一份内存不能把多层 cum 直接相加。本次分配集中在 context 相关函数,并经过 SyncPod、WaitForAttachAndMount 和 startPodSync,与上游 issue 特征接近。
临时缓解的局限
文章指出,暂时无法升级时可在维护窗口重启 kubelet 释放已积累的内存,但重启期间节点状态上报和 Pod 管理会短暂中断。重启只是临时缓解,后续 Pod 同步时内存仍可能继续累积。本次现场先通过重启缓解,尚未升级到修复版本,升级后的长期运行效果仍待验证。因此,重启不能替代升级到含修复的补丁版本。
泄漏为何难以回收
文章分析,v1.36.1 中每轮 Pod 同步都会新建 context 并覆盖 status.cancelFn,旧取消函数被覆盖后不会被补调用。父 context 仍存活并保留对子 context 的引用,旧子 context 又未取消,因此这些对象仍然可达,GC 无法回收。取消函数只在 Pod 进入终止阶段或终止宽限期缩短时调用,普通同步成功不会触发,所以普通 Pod 持续运行也可能累积内存。
Q&A
Kubernetes 1.36 中 kubelet 内存泄漏影响哪些版本?
该回归影响 Kubernetes v1.36.0~v1.36.2,v1.35 不受影响。v1.36.3 是首次包含修复的版本,v1.37.0 也已包含修复。
kubelet 内存泄漏的根本原因是什么?
在 v1.36 中,podWorkerLoop 每轮同步都通过 context.WithCancel(parentCtx) 新建 context 并覆盖 status.cancelFn,旧取消函数未被调用,旧 context 仍被父 context 引用,导致 GC 无法回收,内存随同步次数累积。
如何用 pprof 确认 kubelet 存在 context 泄漏?
通过 kubectl get --raw "/api/v1/nodes/${NODE}/proxy/debug/pprof/heap" 获取堆转储,再用 go tool pprof -top -inuse_space 分析,若内存集中在 context.(*cancelCtx).propagateCancel、context.(*cancelCtx).Done 和 context.withCancel,且调用栈经过 SyncPod、WaitForAttachAndMount 和 startPodSync,则与已知泄漏吻合。
无法立即升级时,如何临时缓解 kubelet 内存泄漏?
可以在维护窗口重启 kubelet 释放已积累的内存,但重启期间节点状态上报和 Pod 管理会短暂中断,且后续 Pod 同步时内存仍可能继续累积,这只是临时缓解。
上游修复 PR #139850 主要改了什么?
修复在 pkg/kubelet/pod_workers.go 中:一是在 podSyncStatus 中增加 ctx 字段,将 context 与取消函数一起保存;二是修改 startPodSync,改为复用已有 context(仅当 status.ctx 为空或已取消时才用 context.WithoutCancel(parentCtx) 新建),避免每轮覆盖取消函数。
升级到哪个版本可以修复 kubelet 内存泄漏?
选择所在分支中包含修复的较新补丁版本即可,例如 v1.36.3 或 v1.37.0,不必专门跨到 1.37。升级后需确认各节点实际运行的 kubelet 版本已更新。