← 返回信息流
AI 资讯Hacker News·20 小时前

Fixing a kubelet memory leak in Kubernetes 1.36

AI 深度解读

背景

一位工程师在将单节点测试集群升级到 Kubernetes v1.36 后,遭遇了内存泄漏问题。该集群运行在 DigitalOcean 的托管 DOKS 服务上,节点配置仅为 2 GiB RAM。正是由于内存资源紧张,这一问题被迅速暴露——在内存充裕的生产环境中,泄漏可能需要更长时间才会显现。

核心内容

问题发现

工程师首先收到集群告警,发现 Pod 被频繁重启。但通过 kubectl top pods 检查时,并未发现任何异常占用内存的 Pod,节点上运行的应用也没有内存增长,远未达到内存限制。

通过在节点上执行 htop 并查看内存占用,最终定位到 kubelet 进程本身在持续增长。执行 systemctl restart kubelet 可暂时缓解问题,但泄漏会很快复发。

排查过程

由于 Kubernetes 使用 Go 语言编写,工程师利用 Go 的 pprof 包捕获了 kubelet 的堆内存 profile:

kubectl get --raw "/api/v1/nodes/${NODE}/proxy/debug/pprof/heap?debug=0" > "kubelet_pprof_heap.pb.gz"

随后使用 go tool pprof -top 分析内存占用情况。

按对象数量分析显示,近 100 万个 context 对象占据了主要内存,其中 context.(*cancelCtx).propagateCancel 占 45.52%,context.withCancel 占 26.93%,context.(*cancelCtx).Done 占 19.57%。

按堆内存使用分析显示,context.(*cancelCtx).propagateCancel 占用 86.33MB(51.28%),context.(*cancelCtx).Done 占用 29.50MB(17.52%),context.withCancel 占用 29MB(17.23%)。

调用链追踪到 k8s.io/kubernetes/pkg/kubelet/volumemanager.(*volumeManager).WaitForAttachAndMountk8s.io/kubernetes/pkg/kubelet.(*Kubelet).SyncPod 等核心路径。

根因定位

工程师使用 Codex 辅助分析代码,迅速定位到问题根源:Kubernetes 1.36 在 2026-02-19 引入的一处代码变更。

原代码

// initialize a context for the worker if one does not exist
if status.ctx == nil || status.ctx.Err() == context.Canceled {
    status.ctx, status.cancelFn = context.WithCancel(context.Background())
}
ctx = status.ctx

新代码

ctx, status.cancelFn = context.WithCancel(parentCtx)

这段代码在每次 startPodSync(每个 Pod 的核心协调循环)时都会执行。问题在于:当 status.cancelFn 已指向之前的取消函数时,新赋值会覆盖它。如果旧的取消函数未被调用(在典型成功场景下不会调用),旧的子 context 仍保持与父 context 的连接。Go 的 context 文档明确指出:调用 CancelFunc 会移除父级对子级的引用,若不调用则会泄漏子 context,直到父 context 被取消。

由于每个 Pod 的协调循环都会触发此逻辑,几天内就累积了近 100 万个泄漏的 context,在繁忙的集群上会更加严重。

修复过程

工程师首次向 Kubernetes 项目提交贡献,发现其流程高效,能快速分流问题并支持补丁合并。

初次补丁通过了本地测试,但在 Kubernetes CI 环境的 E2E 集成测试中失败,暴露出另一个问题:处理就绪性和存活探测的 prober workers 在使用 context 时也存在不当之处。为解决内存泄漏,团队决定先回退直接问题,将更广泛的 context 修复留待后续处理。

工程师在代码中留下注释提醒后续维护者:

// Be careful not to leak contexts (see #139823).
// Be careful that long-lived goroutines (such as prober workers) outlive
// the lifetime of a single startPodSync cancellation context.

时间线

  • 2026-06-17:内存泄漏问题被报告
  • 2026-06-18:定位到回归代码

关键要点

  • 资源受限环境有助于暴露问题:2 GiB 内存的小节点因内存压力快速显现了泄漏,而内存充裕的生产环境可能延迟发现
  • kubelet 自身可能成为内存泄漏源:排查时不应仅关注用户 Pod,Kubernetes 核心组件本身也可能出现问题
  • Go context 泄漏是常见陷阱:未正确调用 CancelFunc 会导致子 context 持续累积,这是 Go 并发编程中的典型错误
  • AI 编程工具可加速陌生代码库分析:Codex 帮助工程师快速定位到正确的回归代码,无需深入理解 kubelet 实现细节
  • 修复需考虑全局影响:初次补丁虽解决直接问题,但 E2E 测试暴露出 prober workers 的 context 使用问题,说明局部修复可能引发其他隐患

意义与影响

此次事件揭示了 Kubernetes 核心组件在版本升级中可能引入的隐蔽回归问题。对于运行 Kubernetes 1.36 的用户,尤其是资源受限的环境,需要关注 kubelet 内存使用情况。

从工程实践角度看,这提醒我们:

  1. context 生命周期管理至关重要:在长期运行的协调循环中,必须确保每个创建的 context 都有对应的取消机制
  2. 测试覆盖的局限性:本地测试通过但 E2E 测试失败,说明集成测试在发现并发和资源泄漏问题上的不可替代性
  3. 小集群的测试价值:低成本测试集群在验证新版本稳定性方面具有独特优势,能够快速暴露资源相关问题
  4. AI 辅助调试的实用性:在大型复杂代码库中,AI 工具可显著缩短问题定位时间,尤其适合不熟悉代码库的开发者
查看原文 →heyoncall.com