广告位 · 页头(后台-模块管理-新建模块后替换此区域)

如何用 Node Exporter 配合 cAdvisor 实现容器资源监控?

监控容器环境时,Node Exporter 经常被当作基础组件部署,但只靠它很难回答“单个容器用了多少 CPU 和内存”这类问题。要理解这一点,先得清楚 Node Exporter 到底采集的是什么。

如何用 Node Exporter 配合 cAdvisor 实现容器资源监控?

一、为什么 Node Exporter 拿不到容器级指标

刚接触 Prometheus 监控容器的团队,第一反应常常是把 Node Exporter 直接跑在容器里。等 Prometheus 抓取之后却发现,node_cpu_seconds_total 显示的 CPU 使用率与宿主机顶部命令一致,而一个占满单核的应用容器在图表上几乎没有任何独立波动。这属于预期行为,不是采集故障。

Node Exporter 的核心逻辑是读取 Linux 伪文件系统。它打开 /proc/stat 获取 CPU 时间片、打开 /proc/meminfo 获取内存总量、打开 /sys/class/net 获取网卡流量。这些文件描述的是整个内核视角下的主机状态,并不区分进程属于哪个 cgroup。容器本质上是使用 namespace 和 cgroup 隔离的进程组,但 Node Exporter 没有读取 cgroup 目录,也没有解析容器元数据。因此运行在容器内部的 Node Exporter,和运行在宿主机上看到的是一致的物理视图。

如果需要自己验证,可以进入宿主机执行 cat /proc/stat 和 cat /sys/fs/cgroup/cpu,cpuacct/cpuacct.usage 等命令。前者是 Node Exporter 采集的数据源,后者才是 cAdvisor 等容器监控组件读取的 cgroup 级别数据。两者在时间维度、统计口径上都不一样,所以不能指望只装一个 Node Exporter 就得到容器级别的指标。

二、宿主机 Node Exporter 与 cAdvisor 的分层监控方案

在典型的单机或小规模容器环境里,推荐架构是宿主机运行 Node Exporter,同时以容器方式运行 cAdvisor。Node Exporter 暴露节点级指标,cAdvisor 暴露容器级指标,Prometheus 分别抓取两个 target,再通过标签或 Grafana 面板把数据放在一起看。

先在宿主机上部署 Node Exporter。下载二进制或使用包管理工具安装即可,启动后默认监听 9100 端口。cAdvisor 则可以用 Docker 启动,注意挂载必要的只读目录,否则可能采集不到某些容器信息。下面是一个 Docker Compose 示例:

version: '3.8'
services:
  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    restart: unless-stopped
    network_mode: host
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--path.rootfs=/rootfs'
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    restart: unless-stopped
    ports:
      - '8080:8080'
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro

这里 Node Exporter 使用了主机网络模式,避免容器网络带来的额外抓取开销。映射 /proc、/sys 和根文件系统时都加了 :ro,确保只读。cAdvisor 则需要读取 /var/lib/docker 来获取容器元数据,并读取 /var/run 下的 Docker socket。某些发行版需要额外挂载 /sys/fs/cgroup,否则会出现容器列表为空的情况。

Prometheus 配置只需把两个 job 声明出来。下面是一个最小化的 prometheus.yml 示例:

global:
  scrape_interval: 15s
scrape_configs:
  - job_name: 'node'
    static_configs:
      - targets: ['192.168.1.10:9100']
  - job_name: 'cadvisor'
    static_configs:
      - targets: ['192.168.1.10:8080']

抓取之后,可以通过 PromQL 对比两类指标。例如宿主机整体 CPU 使用率用 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100),而某个容器的 CPU 使用率可以用 rate(container_cpu_usage_seconds_total{name="nginx"}[5m])。前者看不到 nginx 单个容器的变化,后者看不到其他容器或系统进程占用的 CPU。两者叠加,才能同时看到节点和容器的资源消耗。

三、在容器中运行 Node Exporter 的正确姿势

如果必须把 Node Exporter 也放进容器运行,通常是为了简化部署或统一发布。由于前面提到的原理限制,它的指标依然不区分容器,但可以通过挂载宿主机的 proc、sys 和根文件系统来正常工作。很多人会出于方便加上 --privileged 参数,这不是必需的,还会增加安全风险。只需挂载几个只读目录,并显式指定路径参数。

使用 docker run 的写法如下:

docker run -d \
  --name node-exporter \
  --pid=host \
  -v /proc:/host/proc:ro \
  -v /sys:/host/sys:ro \
  -v /:/rootfs:ro \
  -p 9100:9100 \
  prom/node-exporter:latest \
  --path.procfs=/host/proc \
  --path.sysfs=/host/sys \
  --path.rootfs=/rootfs

--pid=host 让容器进程运行在宿主机 PID 命名空间中,这样 Node Exporter 读取 /host/proc 时可以看到宿主机所有进程。这个参数只影响进程号视图,不会让容器获得管理权限。挂载根文件系统为只读后,Node Exporter 可以读取 /etc/mtab 或 /proc/mounts 来报告文件系统信息。注意不要写成 -v /proc:/proc:ro,这样容器内的 /proc 会覆盖掉原本的容器进程信息,虽然也能工作,但容器内其他依赖 /proc 的程序会获取到宿主机数据,容易产生误解。

另外,Node Exporter 的 textfile collector 可以用来补充一些容器相关的业务指标。默认文本目录为 /var/lib/node_exporter/textfile,脚本只要输出符合 Prometheus 格式的文件,Node Exporter 就会自动暴露。例如可以写一个定时任务,统计当前宿主机上正在运行的容器数量并写入 container_count.prom:

#!/bin/bash
COUNT=$(docker ps -q | wc -l)
cat > /var/lib/node_exporter/textfile/container_count.prom <<EOF
# HELP host_running_containers Current number of running containers
# TYPE host_running_containers gauge
host_running_containers ${COUNT}
EOF

这样虽然不能替代 cAdvisor 的完整容器指标,但可以把一些简单的节点级容器统计纳入 Node Exporter。对于只有少量容器、只关心运行数量的场景,可以少部署一个组件。

四、告警规则与容器监控栈的选型建议

有了节点指标和容器指标,接下来就可以配置告警。常见的节点级告警包括 CPU 使用率过高、内存使用率过高、磁盘空间不足、磁盘 IO 饱和以及 Node Exporter 实例掉线。容器级告警则更关注单个容器的 CPU 使用率、内存增长、重启次数以及 OOM 事件。以下是一些 Prometheus 规则示例:

groups:
  - name: node-and-container
    rules:
      - alert: NodeHighCPU
        expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "宿主机 CPU 使用率超过 85%"
      - alert: NodeHighMemory
        expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "宿主机内存使用率超过 90%"
      - alert: ContainerHighCPU
        expr: rate(container_cpu_usage_seconds_total{name!=""}[5m]) * 100 > 300
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "容器 CPU 使用率超过 300%"
      - alert: ContainerRestarting
        expr: increase(container_restart_count_total{name!=""}[15m]) > 0
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "容器在最近 15 分钟发生重启"

阈值需要根据实际业务调整。例如容器 CPU 使用率 300% 表示使用了三个 CPU 核心,对于多核应用可能是正常的;但对于短时突发型任务,则需要结合 for 持续时间和 rate 周期来判断,避免误报。

从整体选型看,容器监控栈的最小组合理应由 Node Exporter 和 cAdvisor 组成。如果运行在 Kubernetes 环境中,还可以增加 kube-state-metrics 来采集 Deployment、Pod 等对象状态,并用 cAdvisor 或 Metrics Server 提供容器指标。Node Exporter 依然是每个节点必备的基础采集器,它提供的内核、文件和硬件指标不是 cAdvisor 能替代的。反过来,cAdvisor 解析 cgroup 数据的能力也是 Node Exporter 缺失的一环。两者不是替代关系,而是补充关系。

最后有一个容易忽略的点:无论用哪种方式部署 Node Exporter,都要保持版本与内核兼容。较新的 Linux 内核在 /proc 中增加或调整了一些字段,过旧的 Node Exporter 可能无法正确解析。建议定期升级,并在更新前在测试环境验证 node_uname_info 和 node_cpu_seconds_total 是否正常输出。

广告位 · 文章底部(后台-模块管理替换此区域)