Prometheus CPU利用率计算原理与实战:从node_exporter到rate()
1. 这不是“看一眼就懂”的监控指标而是你服务器心跳的听诊器很多人第一次在 Prometheus 的 Grafana 面板上看到那个标着CPU利用率的曲线图时第一反应是“哦这个我知道不就是任务管理器里那个百分比嘛”——然后点开 node_exporter 的/metrics端点扫一眼node_cpu_seconds_total这个指标发现它长得像一串永不停歇上涨的浮点数单位还是秒瞬间懵了这玩意儿怎么跟“百分比”扯上关系更别说为什么有的面板显示 0~100%有的却显示 0~1还有的干脆画出 8 条线对应 8 核 CPU……这就是我今天要拆解的核心prometheus - node_exporter - CPU利用率(入门基础)。它不是一句“查个指标就行”的简单操作而是一套从 Linux 内核计时器、到 exporter 数据采集逻辑、再到 Prometheus 指标模型、最后经 PromQL 聚合计算才能落地为人类可读数值的完整链路。你看到的那条平滑曲线背后至少经过 4 层语义转换内核态时间片统计 → 用户态/系统态/空闲态时间累加 → exporter 暴露为 counter 类型指标 → Prometheus 抓取后用rate()做斜率计算 → 最终用100 - (avg by (instance) (rate(node_cpu_seconds_total{modeidle}[5m])) * 100)得出百分比。这个过程里任何一个环节理解偏差都会导致你误判服务器负载。比如把node_cpu_seconds_total{modeuser}直接当 CPU 使用率画出来结果永远在 0.0003~0.0007 之间波动——因为它是每秒增加约 0.0005 秒的 counter不是瞬时值又比如用irate()替代rate()计算短周期 CPU 利用率在高频率抓取如 10s 间隔下会因瞬时抖动产生大量毛刺让你误以为服务器正在“抽风”。这篇文章面向三类人刚部署完 Prometheus node_exporter、却看不懂面板数据的新手运维想搞清rate()和irate()在 CPU 场景下到底该选谁的中级工程师以及需要给开发同事讲清楚“为什么我们告警阈值设在 85% 而不是 95%”的技术负责人。我不讲抽象理论只讲你实际打开终端、敲命令、改配置、调面板时真正会遇到的问题——比如为什么node_cpu_seconds_total的mode标签有 8 种取值但你只需要关注其中 4 个为什么idle时间不能直接相减得出使用率为什么容器化环境里node_exporter采集的 CPU 数据和docker stats显示的不一致……所有答案都来自我在 32 套生产环境反复验证过的实操记录。2. 为什么 CPU 利用率不能“直出”必须走 rate() 这道工序2.1 从 Linux 内核说起CPU 时间的本质是“累计秒数”不是“瞬时百分比”要真正理解node_exporter为什么暴露node_cpu_seconds_total而不是node_cpu_usage_percent得先回到 Linux 内核的/proc/stat文件。当你执行cat /proc/stat | head -n 5你会看到类似这样的输出cpu 12345678 98765 456789 123456789 12345 67890 123456 0 0 0 cpu0 1234567 9876 45678 12345678 1234 6789 12345 0 0 0 cpu1 1234568 9877 45679 12345679 1235 6790 12346 0 0 0 ...第一行cpu是所有 CPU 核心的汇总后面每行cpu0、cpu1是单核数据。每一列代表不同模式下消耗的jiffies内核时间片通常 100Hz即 10ms/次。关键在于这些数字是单调递增的累计值不是每秒刷新的瞬时快照。例如cpu行第 4 列123456789是idle时间累计 jiffies它只会变大不会归零或跳变。node_exporter的作用就是定期默认 15s读取/proc/stat把 jiffies 换算成秒除以sysconf(_SC_CLK_TCK)通常是 100再按mode分类暴露为 Prometheus 的 counter 指标。所以node_cpu_seconds_total{modeidle}的含义是“自服务器启动以来CPU 处于空闲状态的总秒数”。它天生就是 counter 类型——只增不减且初始值不为零可能已有数万秒空闲时间。提示你可以用curl -s http://localhost:9100/metrics | grep node_cpu_seconds_total | head -n 5实际查看。注意mode标签的取值idle、user、system、iowait、irq、softirq、steal、guest。其中guest和guest_nice是虚拟机场景专用物理机部署可忽略steal在云主机上才显著宿主机偷走的 CPU 时间家用服务器几乎为 0。2.2 Counter 类型指标的致命缺陷无法直接反映“变化速率”Counter 的设计哲学是“记录总量”适合统计请求数、错误数、字节数等累积事件。但 CPU 利用率是个比率型指标ratio metric它描述的是“单位时间内 CPU 被占用的比例”。如果你直接画node_cpu_seconds_total{modeuser}得到的是一条永远向上爬升的直线斜率缓慢增加——这完全无法告诉你“此刻 CPU 是否繁忙”。举个真实案例某次线上服务响应延迟突增值班同学看 Grafana 面板上node_cpu_seconds_total{modeuser}的曲线平缓上升判断“CPU 应该没问题”结果排查两小时才发现是磁盘 I/O 阻塞导致进程卡在iowait而iowait时间被计入node_cpu_seconds_total{modeiowait}但该指标同样在缓慢爬升没人注意到它的斜率比平时高了 3 倍。问题根源就在于没对 counter 做速率计算就失去了时间维度上的敏感性。Prometheus 提供了两个核心函数解决这个问题rate(v range-vector)计算区间向量中每个时间序列的平均每秒增长率基于线性回归拟合抗抖动能力强irate(v range-vector)计算区间向量中最近两个采样点之间的瞬时增长率对突变敏感但易受采样噪声影响。对于 CPU 这种需要稳定趋势判断的指标rate()是黄金标准。它的计算逻辑是取[5m]区间内所有node_cpu_seconds_total的采样点比如抓取间隔 15s则 5 分钟有 20 个点对每个点计算与前一个点的差值除以时间差再取这些差值的平均值。最终结果单位是 “秒/秒”即无量纲的比率——0.75就代表过去 5 分钟平均 75% 的时间在工作。注意rate()的区间长度不是随便定的。太短如[1m]会导致毛刺多尤其在低频抓取30s 间隔时可能漏掉峰值太长如[30m]会掩盖突发负载。我们团队在 20 生产集群验证后确定5m是最佳平衡点既能覆盖大多数短时爆发如批量任务启动又足够平滑过滤掉单次 GC 或网络中断引起的瞬时抖动。2.3 为什么不能用(1 - idle_time / total_time) * 100——Linux 时间模型的隐藏陷阱很多教程教新手用这个公式“CPU 使用率 100% - (idle 时间 / 总时间) × 100%”。听起来很合理但直接套用rate(node_cpu_seconds_total{modeidle}[5m])会出错。原因在于node_cpu_seconds_total的mode标签是互斥的但总时间 ≠ 所有 mode 时间之和。Linux 内核定义的total时间其实是user system idle iowait irq softirq steal guest但guest和guest_nice是虚拟 CPU 时间不应计入物理 CPU 总时间。更关键的是idle时间本身包含两种状态——idle完全空闲和iowait等待 I/O 完成CPU 可调度其他任务。严格来说iowait不属于“CPU 工作”但它也不等于“空闲”因为此时 CPU 并未执行任何指令只是在等待。所以业界通用的 CPU 利用率计算公式是100 - (rate(node_cpu_seconds_total{modeidle}[5m]) / (rate(node_cpu_seconds_total{modeuser}[5m]) rate(node_cpu_seconds_total{modesystem}[5m]) rate(node_cpu_seconds_total{modeidle}[5m]) rate(node_cpu_seconds_total{modeiowait}[5m]) rate(node_cpu_seconds_total{modeirq}[5m]) rate(node_cpu_seconds_total{modesoftirq}[5m]) rate(node_cpu_seconds_total{modesteal}[5m]))) * 100但这个公式太长且iowait是否计入“空闲”存在争议有些监控体系认为iowait是资源浪费应计入使用率有些认为它本质是空闲。因此最稳妥、最广泛采用的简化公式是100 * (1 - rate(node_cpu_seconds_total{modeidle}[5m]) / rate(node_cpu_seconds_total[5m]))其中node_cpu_seconds_total无mode标签是node_exporter自动聚合的所有 mode 时间之和它等价于内核cpu行的总 jiffies 换算值。这个分母确保了分子分母的时间基准完全一致避免了手动求和引入的标签匹配错误。3. 从零开始手把手搭建可验证的 CPU 监控链路3.1 环境准备三台机器的真实拓扑拒绝 Docker 一键部署幻觉网上太多教程用docker run -d -p 9090:9090 prom/prometheus启动看似 5 分钟搞定实则埋下无数坑容器内node_exporter采集的是容器 namespace 的 CPU而非宿主机Prometheus 抓取地址写http://host.docker.internal:9100在 Linux 下根本不可用更别说scrape_configs里static_configs的targets写localhost:9100结果抓到的是 Prometheus 自己的指标……我坚持用真实物理/虚拟机环境演示这是唯一能暴露所有细节的方案。假设你有三台机器监控服务器AIP192.168.1.100装 Prometheus Grafana被监控服务器BIP192.168.1.101装 node_exporter测试客户端CIP192.168.1.102用于制造 CPU 负载。提示所有操作均以 Ubuntu 22.04 为例CentOS/RHEL 用户请将apt替换为yumsystemctl命令通用。步骤 1在 B 机部署 node_exporter# 下载最新版截至 2024 年推荐 1.6.1 wget https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux-amd64.tar.gz tar xvfz node_exporter-1.6.1.linux-amd64.tar.gz cd node_exporter-1.6.1.linux-amd64 # 创建专用用户禁止 root 运行 sudo useradd --no-create-home --shell /bin/false node_exporter sudo chown node_exporter:node_exporter node_exporter # 启动并监听 9100 端口添加 --collector.systemd 参数如需监控 systemd 服务 sudo -u node_exporter ./node_exporter \ --web.listen-address:9100 \ --collector.systemd # 设置为系统服务关键确保开机自启 sudo tee /etc/systemd/system/node_exporter.service EOF [Unit] DescriptionNode Exporter Wantsnetwork-online.target Afternetwork-online.target [Service] Typesimple Usernode_exporter Groupnode_exporter ExecStart/opt/node_exporter/node_exporter \ --web.listen-address:9100 \ --collector.systemd Restartalways RestartSec10 [Install] WantedBymulti-user.target EOF sudo systemctl daemon-reload sudo systemctl enable node_exporter sudo systemctl start node_exporter验证curl -s http://192.168.1.101:9100/metrics | grep node_cpu_seconds_total | head -n 3应返回类似# HELP node_cpu_seconds_total Seconds the cpu spent in each mode. # TYPE node_cpu_seconds_total counter node_cpu_seconds_total{cpu0,modeidle} 1.234567e06步骤 2在 A 机部署 Prometheus# 下载 Prometheus推荐 2.45.0 wget https://github.com/prometheus/prometheus/releases/download/v2.45.0/prometheus-2.45.0.linux-amd64.tar.gz tar xvfz prometheus-2.45.0.linux-amd64.tar.gz cd prometheus-2.45.0.linux-amd64 # 编辑 prometheus.yml重点配置 scrape_targets sudo tee prometheus.yml EOF global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: prometheus static_configs: - targets: [localhost:9090] - job_name: node_exporter static_configs: - targets: [192.168.1.101:9100] # 关键写被监控机 IP不是 localhost metrics_path: /metrics scheme: http EOF # 启动 Prometheus监听 9090 ./prometheus --config.fileprometheus.yml --storage.tsdb.pathdata/ --web.listen-address:9090验证访问http://192.168.1.100:9090/targets确认node_exporterjob 状态为 UP在 Graph 页面输入node_cpu_seconds_total{modeidle}应看到多条曲线对应不同 CPU 核心。步骤 3在 C 机制造可控 CPU 负载用于验证# 安装 stress-ng比老式 stress 更精准 sudo apt install stress-ng -y # 启动 4 个 CPU 核心满载 60 秒模拟真实压力 stress-ng --cpu 4 --timeout 60s --metrics-brief # 观察 B 机 top 命令确认 %Cpu(s) 显示接近 100% # 同时在 A 机 Prometheus Graph 查看 node_cpu_seconds_total{modeuser} 斜率是否陡增3.2 核心 PromQL 编写从 raw data 到可读面板的 7 种写法现在我们有了原始数据接下来是灵魂环节如何用 PromQL 把node_cpu_seconds_total变成一张清晰的 CPU 利用率面板以下是我在生产环境中验证过的 7 种写法按推荐度排序写法 1全局平均 CPU 使用率最常用100 - (avg by(instance) (rate(node_cpu_seconds_total{modeidle}[5m])) * 100)✅ 优点简洁兼容单核/多核avg by(instance)自动聚合所有 CPU 核心❌ 缺点无法看出哪颗核过载适合宏观健康检查。写法 2各 CPU 核心独立使用率排查热点100 - (rate(node_cpu_seconds_total{modeidle}[5m]) * 100)✅ 优点保留cpu0、cpu1等标签可在 Grafana 中用Legend: {{cpu}}显示每核曲线❌ 缺点曲线过多时面板拥挤需配合max_over_time()做峰值分析。写法 3剔除 iowait 的“纯计算”使用率技术负责人最爱100 * ( rate(node_cpu_seconds_total{mode~user|system|irq|softirq|steal}[5m]) / rate(node_cpu_seconds_total[5m]) )✅ 优点排除 I/O 等待时间纯粹反映 CPU 计算能力占用适合性能调优❌ 缺点iowait高时此值偏低可能掩盖磁盘瓶颈。写法 4带告警阈值的布尔表达式直接用于 Alerting Rules100 - (avg by(instance) (rate(node_cpu_seconds_total{modeidle}[5m])) * 100) 85✅ 优点结果为 0 或 1Grafana 可用 Stat 面板显示红/绿灯❌ 缺点需配合ALERTS{alertnameHighCPUUsage}做静默处理。写法 5过去 1 小时最高使用率容量规划依据max_over_time( (100 - (avg by(instance) (rate(node_cpu_seconds_total{modeidle}[5m])) * 100))[1h:] )✅ 优点max_over_time函数提取窗口内最大值避免被平均值平滑❌ 缺点计算开销略大建议仅用于 Dashboard 的 Summary Panel。写法 6区分用户态/系统态占比深度排障# 用户态占比 100 * (rate(node_cpu_seconds_total{modeuser}[5m]) / rate(node_cpu_seconds_total[5m])) # 系统态占比 100 * (rate(node_cpu_seconds_total{modesystem}[5m]) / rate(node_cpu_seconds_total[5m]))✅ 优点user高说明应用代码密集计算system高说明频繁系统调用如大量文件读写、网络收发❌ 缺点需两个查询叠加Grafana 用 Bar Gauge 显示更直观。写法 7容器化环境专用Kubernetes Node100 - ( avg by(instance) ( rate(node_cpu_seconds_total{modeidle, instance~.:9100}[5m]) ) * 100 )✅ 优点instance~.:9100匹配所有 node_exporter 实例适配 K8s Service DNS❌ 缺点需确保 K8s Service 正确配置targetPort: 9100。实操心得我在某电商大促保障中发现单纯看100 - idle_rate会漏掉steal时间。云主机上当宿主机超卖时steal时间飙升CPU 时间被隔壁租户抢占但idle时间不变导致计算出的使用率虚低。因此我们最终采用的告警规则是(100 - (avg by(instance) (rate(node_cpu_seconds_total{modeidle}[5m])) * 100)) 80 OR (avg by(instance) (rate(node_cpu_seconds_total{modesteal}[5m]))) 0.1即使用率 80% 或 steal 时间 0.1 秒/秒任一触发即告警。3.3 Grafana 面板配置不只是画图更是信息密度的艺术有了 PromQL下一步是把它变成一张真正有用的 Grafana 面板。别再满足于默认的 Time Series 图表——CPU 监控需要多维度信息融合。面板 1主 CPU 使用率Time SeriesQuery100 - (avg by(instance) (rate(node_cpu_seconds_total{modeidle}[5m])) * 100)Options → Legend{{instance}}Thresholds0, 70, 85, 100绿色→黄色→红色→深红Y-axisUnit%Min0Max100面板 2CPU 模式分解Bar GaugeQuery 1User100 * (rate(node_cpu_seconds_total{modeuser}[5m]) / rate(node_cpu_seconds_total[5m]))Query 2System100 * (rate(node_cpu_seconds_total{modesystem}[5m]) / rate(node_cpu_seconds_total[5m]))Query 3Iowait100 * (rate(node_cpu_seconds_total{modeiowait}[5m]) / rate(node_cpu_seconds_total[5m]))Options → Display → Bar Gauge → OrientationhorizontalThresholds0, 30, 60, 90不同颜色区分负载等级面板 3单核热点地图HeatmapQuery100 - (rate(node_cpu_seconds_total{modeidle}[5m]) * 100)Options → Visualization → HeatmapX-axis$__interval自动按时间分桶Y-axis{{cpu}}自动按 CPU 核心分组Color SchemeRed-Yellow-Green红热黄温绿冷面板 4历史峰值对比StatQuerymax_over_time((100 - (avg by(instance) (rate(node_cpu_seconds_total{modeidle}[5m])) * 100))[7d:])Options → Stat → Value Mappings →0-70: OK, 70-85: Warning, 85-100: CriticalPrefix7d Peak:注意事项Grafana 的$__rate_interval变量在 Prometheus 数据源中会自动替换为5m根据抓取间隔动态调整但不要依赖它。我吃过亏某次将抓取间隔从 15s 改为 30s$__rate_interval变成10m导致rate()计算窗口过大峰值被严重平滑。正确做法是硬编码[5m]并在文档中注明“此值需与抓取间隔匹配15s 间隔用[5m]30s 间隔用[10m]”。4. 排查实战那些让 Prometheus 新手抓狂的 CPU 监控异常4.1 异常现象 1“CPU 使用率永远显示 0%”——不是没数据是标签匹配失败现象Prometheus Targets 页面显示node_exporter状态为 UPnode_cpu_seconds_total查询能返回数据但100 - idle_rate结果全是 0。排查路径先确认node_cpu_seconds_total{modeidle}是否有数据curl -s http://192.168.1.101:9100/metrics | grep node_cpu_seconds_total{modeidle}。如果返回为空说明node_exporter未正确采集/proc/stat检查其日志journalctl -u node_exporter -f。如果有数据检查 PromQL 中的标签是否精确匹配。常见错误写成modeIDLE大小写敏感必须小写idle写成modeidle 末尾空格肉眼难辨在 Kubernetes 环境中instance标签可能是10.244.1.5:9100而你在 PromQL 中写了instance192.168.1.101:9100。终极验证法在 Prometheus Graph 输入count(node_cpu_seconds_total{modeidle})返回值应等于 CPU 核心数如 8 核返回 8。如果返回 0说明modeidle标签根本不存在此时用node_cpu_seconds_total无标签查看所有 mode 值确认拼写。4.2 异常现象 2“CPU 使用率曲线剧烈抖动像心电图”——irate() 的甜蜜陷阱现象面板上 CPU 曲线在 0%~100% 之间疯狂跳变5 秒内完成一次完整震荡完全无法判断真实负载。根因分析这是irate()函数的典型副作用。irate(node_cpu_seconds_total{modeidle}[5m])只取最近两个采样点计算瞬时速率。当抓取间隔为 15s而node_exporter采集/proc/stat时恰好遇到内核定时器抖动如 CPU 频率动态调整两个点的差值可能异常大或小导致irate()输出失真。解决方案✅首选改用rate()并确保[5m]区间内至少有 4 个采样点即抓取间隔 ≤ 75s。我们线上统一用15s抓取 [5m]计算效果极稳。✅次选若必须用irate()如监控超短时脉冲将区间缩短至[1m]并配合avg_over_time()平滑avg_over_time(irate(node_cpu_seconds_total{modeidle}[1m])[5m:])。❌禁用irate()直接用于 CPU 面板这是新手最大误区。4.3 异常现象 3“同一台服务器node_exporter 和 top 显示的 CPU 使用率相差 20%”——时间窗口与统计口径的战争现象top命令显示 CPU 95%而 Prometheus 面板显示 75%误差远超正常范围。真相拆解top默认显示最近 1~3 秒的瞬时平均值且刷新时会重置计时器Prometheus 的rate(node_cpu_seconds_total[5m])计算的是过去 5 分钟的平均速率更关键的是top的%Cpu(s)行包含ususer、sysystem、ninice、ididle、waiowait、hihardware irq、sisoftware irq、ststeal但它的id值是内核实时计算的而node_exporter读取/proc/stat有微小延迟毫秒级。验证方法在 B 机执行watch -n 1 cat /proc/stat | grep ^cpu 观察idle列数值每秒增长是否稳定应≈CPU 核心数 × 100因 100Hz同时运行top -b -n 1 | head -n 5对比idle百分比与 Prometheus 计算值如果差异持续存在检查node_exporter是否启用了--collector.textfile.directory某些第三方 textfile collector 会干扰/proc/stat读取。我的经验差异 ≤ 5% 属正常采样时机不同 10% 一定是配置问题。曾遇到某客户在node_exporter启动参数中误加--collector.diskstats.ignored-mount-points^/(sys|proc|dev|run)($|/)导致diskstatscollector 卡住主线程cpucollector 延迟达 2s最终rate()计算失真。移除该参数后恢复正常。4.4 异常现象 4“容器里跑的 node_exporter采集的却是宿主机 CPU”——namespace 隔离的幻觉现象在 Docker 容器中运行node_exporterPrometheus 抓取到的node_cpu_seconds_total数值巨大且与宿主机top完全一致。原理揭秘node_exporter默认使用--collector.cpu它读取的是/proc/stat而该文件在 Linux 中不属于 PID namespace 隔离范畴——容器内读取的/proc/stat就是宿主机的全局 CPU 统计。这是设计使然因为容器共享宿主机 CPU 资源监控目标本就是宿主机整体负载。正确做法✅ 如果你想监控单个容器的 CPU 使用率应该用cAdvisor或kube-state-metricsK8s 场景它们通过 cgroup 接口获取容器级 CPU 时间✅ 如果你坚持用node_exporter容器化部署只需确保它以hostNetwork: true模式运行并监听宿主机端口如hostPort: 9100这样 Prometheus 抓取的就是真实的宿主机指标❌ 不要用--pidhost参数试图“修复”这毫无意义/proc/stat本来就是全局的。4.5 常见问题速查表问题现象可能原因快速验证命令解决方案node_cpu_seconds_total查询无数据node_exporter未运行或端口被防火墙拦截telnet 192.168.1.101 9100检查systemctl status node_exporter开放防火墙sudo ufw allow 9100rate()计算结果为 NaN时间序列在[5m]内无足够采样点count(count_over_time(node_cpu_seconds_total{modeidle}[5m]))确保抓取间隔 ≤ 75s或改用更短区间如[2m]CPU 使用率长期 99%idle时间几乎不增长可能内核 hang 住dmesg -T | tail -20查看 OOM 或硬件错误重启服务器检查内存/磁盘健康状态多核服务器只显示 1 条曲线PromQL 未去除cpu标签count by(cpu)(node_cpu_seconds_total{modeidle})改用avg by(instance)或sum by(instance)聚合Grafana 面板数据延迟 2 分钟Prometheus 抓取配置scrape_timeout过短curl -s http://localhost:9090/api/v1/status/config | jq .scrape_timeout将scrape_timeout设为scrape_interval的 2 倍如15s间隔设30s超时5. 进阶思考CPU 利用率之外你真正该关注的三个衍生指标5.1 CPU Load Average比利用率更能预判雪崩的“压力计”很多人混淆CPU 使用率和Load Average。前者是“CPU 在忙什么”后者是“有多少任务在排队等 CPU”。uptime命令显示的load average: 0.12, 0.25, 0.45分别代表 1/5/15 分钟平均等待队列长度。node_exporter暴露node_load1、node_load5、node_load15它们直接对应/proc/loadavg。关键洞察当Load Average CPU 核心数说明任务已开始排队即使 CPU 使用率 100%系统已出现瓶颈Load Average高而CPU 使用率低大概率是 I/O 等待iowait高或锁竞争需perf分析我们线上告警策略是node_load1 / count(node_cpu_seconds_total{modeidle}) 1.5
上一篇/下一篇内容由系统自动关联
返回资讯列表 →