为什么 cAdvisor 在部分节点采集不到容器的内存数据?

最近在搞跨云节点的容器监控,遇到一个非常诡异的问题:在 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 发现两个致命错误:

  1. 参数语法错误:我把 KillMode=process 写在了 ExecStart 后面,导致解析失败。
  2. 版本弃用:我用的是 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!=""})

执行逻辑:

  1. 首先尝试获取老指标。因为新节点这个值全是 0,所以 > 0 的条件会把新节点直接过滤掉。
  2. 对老节点来说,左侧条件成立,画出老指标的线。
  3. 对新节点来说,左侧为空,触发 or 逻辑,自动替补使用右侧的新指标 working_set 并在图表上画线。

保存查询,刷新面板。

总结:排查监控数据缺失问题,不要一上来就去猜参数。顺藤摸瓜,从 PID 反查 cgroup 挂载点,直接看操作系统的底层到底给了什么,从根本上解决问题。


Mac 上安装 OrbStack 替代 Docker Desktop OpenCode 配置 MiniMax 免费模型指南