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

一、为什么 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 是否正常输出。

