为什么 cAdvisor 在部分节点采集不到容器的内存数据?
2026-04-08 tech prometheus grafana cadvisor cgroup docker 5 mins 1845 字

最近在搞跨云节点的容器监控,遇到一个非常诡异的问题:在 Grafana 面板上,有很多容器拉不到内存信息。
具体表现是,看 container_memory_max_usage_bytes{image!=""} 这个指标时,老节点的数据一切正常,能读到具体的字节数;但新节点的数据全都是 0。
仔细对比了一下指标的 id 标签:
- 有数据的节点:
/docker/5282f5... - 没数据的节点:
/system.slice/docker-153d1e...scope
路径明显不一致。为了解决这个”断流”问题,踩了几个坑,在这里记录一下排查和修复的全过程。
坑一:盲改 cAdvisor 启动参数
一开始我以为是 cAdvisor 的 cgroup 挂载路径偏移导致的。因为在新节点里,Docker 使用了 systemd 作为 Cgroup Driver。
于是我尝试修改 cadvisor.service 文件,加入了 --runtime_full_cgroup_path=true 参数。结果改完重启,Systemd 直接报错 Failed with result 'exit-code'。
查看 journalctl -u cadvisor -f 发现两个致命错误:
- 参数语法错误:我把
KillMode=process写在了ExecStart后面,导致解析失败。 - 版本弃用:我用的是
v0.46.0版本的 cAdvisor,这个版本已经完全弃用了-runtime_full_cgroup_path参数!强行加进去只会触发flag provided but not defined报错。
坑二:寻找消失的物理路径
既然参数没法调,我决定直接去宿主机上看看 cgroup 的实际目录层级。 按照以往的经验,我执行了:
ls /sys/fs/cgroup/memory/system.slice/docker-*.scope
结果提示:文件不存在。
为了精准定位,采用反向溯源的方法,先拿到容器内主进程的 PID,然后直接看它挂载在哪里:
# 拿到容器的 PID
docker inspect -f '{{.State.Pid}}' iperf-server
# 查看对应 PID 的 cgroup (假设 PID 是 12345)
cat /proc/12345/cgroup
输出结果:
0::/system.slice/docker-153d1e883aa123826...scope
真相大白:Cgroup v1 与 v2 的代沟
看到开头的 0::,懂了。这是 Cgroup v2(统一层级)的特征签名。
根本不是 cAdvisor 挂载错了,也不是参数配错了,而是底层操作系统架构变了:
- 老节点跑的是 Cgroup v1,有单独的
/memory/目录,内核提供了最大内存使用量的统计接口,所以 cAdvisor 能读到数据。 - 新节点跑的是 Cgroup v2,底层去掉了
memory目录。在 Cgroup v2 环境下,cAdvisor 获取不到历史极值,于是强行给container_memory_max_usage_bytes这个指标塞了一个0。
最终方案:在 PromQL 层面无缝缝合
既然底层都不提供这个数据了,死磕 cAdvisor 配置毫无意义。解决办法是直接在查询层更换跨版本兼容的”真实内存”指标:container_memory_working_set_bytes。
但我希望能保留老节点上 max_usage 的历史数据。
利用 PromQL 的 or 逻辑,实现新老数据的缝合替换。修改 Grafana 的查询语句如下:
(container_memory_max_usage_bytes{image!=""} > 0)
or
(container_memory_working_set_bytes{image!=""})
执行逻辑:
- 首先尝试获取老指标。因为新节点这个值全是 0,所以
> 0的条件会把新节点直接过滤掉。 - 对老节点来说,左侧条件成立,画出老指标的线。
- 对新节点来说,左侧为空,触发
or逻辑,自动替补使用右侧的新指标working_set并在图表上画线。
保存查询,刷新面板。
总结:排查监控数据缺失问题,不要一上来就去猜参数。顺藤摸瓜,从 PID 反查 cgroup 挂载点,直接看操作系统的底层到底给了什么,从根本上解决问题。