尧图精选

Prometheus监控:如何从node_exporter海量指标中提炼生产级告警规则

🕒 发布时间:2026/9/8 15:55:25 📁 来源:尧图网络
做监控的同学几乎都有过这种感觉node_exporter的指标铺天盖地node_cpu_seconds_total、node_memory_MemAvailable_bytes、node_filesystem_avail_bytes……每一项看起来都可以用可真到要把它们变成生产环境里能信得过的告警规则门道比想象中多得多。我从裸奔式“只挂个 node_exporter 看面板”到后面把告警规则拆了重写三轮前后踩了不少坑。这篇就聊聊怎么从 node_exporter 的海量指标里提炼出真正能救命的几条规则以及规则上线之后怎么避免自己被误报短信打成筛子。这套思路不挑监控栈。只要你用的是 Prometheus 生态不管告警最后是进钉钉、企业微信还是邮件骨架都能直接用。1. 告警规则设计的第一步先想清楚什么值得告警1.1 告警的本质是“决策”不是“通知”很多团队把告警规则写成了“指标变化通知”CPU 高一点就报内存涨一点就报磁盘多写几 MB 也报。结果 Grafana 面板上的确花花绿绿但值班同学点开一看全是“高负载”根本没有动作可以执行。告警规则真正的价值在于触发人的决策这条告警出现之后值班同学需要知道现在系统处于什么状态、影响范围多大、下一步应该看哪块数据、找哪个团队。如果你设计的规则触发后人只能“哦”一声然后关掉那这不是告警是弹窗广告。生产级规则的判断标准不是“能不能识别故障”而是“能不能在正确的时间把正确的信息推给正确的人”。所以从 node_exporter 原始指标出发之前先要从这个目标倒推每一类指标能告诉你的状态是什么失去这个状态时谁会受影响。否则后面写出来的规则基本都是给自己添堵。1.2 三类必须淘汰的规则回顾我见过和写过的告警规则能稳定制造噪声的基本就三类。第一类叫“无阈值的状态广播”。典型写法是node_boot_time_seconds 0或者“进程存在性告警”这类规则绝大多数时间恒为 true对人没有任何新鲜信息量。它唯一的作用是让你在告警疲劳之后把真正重要的消息一起忽略掉。第二类是“单点指标恐慌”。只看单个瞬间值、不看趋势和持续时间。一个node_load1 8的瞬时表达式在定时任务集中跑批的凌晨几乎天天误报但机器其实没有健康问题只是负载在几秒内冲高然后回落这类误报会把运维的注意力锁死在无意义问题上。第三类是“把平均值当真相”。avg(node_cpu_seconds_total)这种写法在多核机器上常常把一个核跑满的真实故障摊平成“整体还行”。CPU 告警的正确姿势是按modeuser或modesystem拆分看单核最大值与多核均值的偏离度。1.3 给 node_exporter 指标做“可告警性”分级我每接入一台新机器会先把指标分成三个层级再决定要不要写规则。第一层是“必须告警的硬指标”。比如内存耗尽、磁盘只读、根分区快满、时钟跳变、node_exporter 自身抓取失败。这类指标一旦异常系统通常已经处于不可用边缘需要分钟级介入。第二层是“趋势告警的软指标”。比如 CPU 单核长时间 user 占用超过 90%、可用内存持续下降、磁盘 IO 等待时间持续走高。单独看某一条可能不致命但如果持续时间拉长意味着容量规划需要关注了。第三层是“不写规则的参考指标”。比如网络流量瞬时峰值、文件描述符当前数量。它们更适合放面板做趋势观察价值在于“事后复盘”而不在于“实时打断人”。分完级你会发现真正需要写告警规则的 node_exporter 指标可能只有十几条。这个数量是健康的。告警规则不是展览品写得越多平均单条可信度越低。2. 核心指标解构与阈值设计从原始 metric 到告警表达2.1 内存指标千万别用 MemFree用 MemAvailable内存告警是初学者最容易写错的第一道规则。直接写(node_memory_MemTotal_bytes - node_memory_MemFree_bytes) / node_memory_MemTotal_bytes * 100算出“内存使用率 95%”然后就报警这是典型的 Linux 内存模型知识缺位。MemFree只统计了完全空闲的物理内存页而 Linux 内核的 page cache、文件系统缓存、内核 slab 都会占用内存但这些内存大多可以在业务需要时被回收。你看一台刚启动完数据库的机器MemFree往往只有几百 MB但系统跑得毫无压力因为页缓存把磁盘读过的数据都留着随时可以让出来。真正反映“给应用还能剩多少内存”的指标是node_memory_MemAvailable_bytes它已经把可回收缓存算进去了。所以我的基础规则这样写( node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes ) / node_memory_MemTotal_bytes * 100 90这只是半边。另外半边要关注剩余内存绝对值。对一台 64GB 的机器剩 5% 可能还有 3GB业务不一定马上挂但对 8GB 的小机器剩 5% 就是 400MBGC 和 OOM 可能就在几分钟内接踵而至。生产经验是比例阈值和绝对值阈值同时保留任何一个触发都值得看一眼。举个例子( 100 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100) ) 90 unless node_memory_MemAvailable_bytes 2 * 1024 * 1024 * 1024这条表达式的意思是“总内存使用率超过 90%但可用内存绝对值大于 2GB 时不算告警”。用unless把“大内存机器上的钝感”显式表达出来误报率会低很多。2.2 磁盘指标挂载点过滤与容量阈值磁盘容量规则看起来简单写起来全是坑。node_filesystem_avail_bytes会把挂载点下面所有文件系统都列出来包括tmpfs、overlay、squashfs这些如果不加过滤你会在容器节点上收到一堆/var/lib/docker/overlay2/xxx的告警——这些挂载点的空间其实共享同一个宿主机磁盘发出 N 条重复告警毫无意义属于典型的“看起来精确、实际没信息量”。我一般用一组取值白名单约束设备类型和挂载点。生产规则通常是( node_filesystem_size_bytes{fstype!~tmpfs|overlay|squashfs|ramfs} - node_filesystem_avail_bytes{fstype!~tmpfs|overlay|squashfs|ramfs} ) / node_filesystem_size_bytes{fstype!~tmpfs|overlay|squashfs|ramfs} * 100 85但这只是第一层。真正生产环境里容量的阈值要按分区职能分开根分区/是系统盘一般只跑系统日志和安装包85% 就算高/data是业务数据盘如果业务有自动清理机制95% 依然可控。所以我更推荐把挂载点写进 rule group 的标签里而不是用一条大而全的表达式套所有机器。例如- alert: RootFilesystemSpaceLow expr: | node_filesystem_avail_bytes{mountpoint/, fstype!~tmpfs|overlay|squashfs|ramfs} / node_filesystem_size_bytes{mountpoint/, fstype!~tmpfs|overlay|squashfs|ramfs} * 100 10 for: 10m labels: severity: critical annotations: summary: 根分区剩余空间不足 10%当前剩余 {{ $value | humanizePercentage }}还有一类和容量相关但容易漏掉的指标node_filesystem_readonly。它代表文件系统是否已经是只读状态。很多老牌 Linux 机器磁盘发生硬件错误时内核会把文件系统重挂载为只读以防止数据进一步损坏这个信号比“容量满了”严重得多一旦出现基本意味着这个节点上的写入型业务已经停摆。建议用node_filesystem_readonly 1单独做一条 critical 告警甚至可以考虑路由到更高优的通知渠道。磁盘 IO 压力则要看node_disk_io_time_seconds_total的 rate。它反映磁盘有多少时间在忙可以近似看成“排队程度”。如果磁盘 io 占比长时高于 80%~90%但容量指标还健康大概率是磁盘吞吐或 IOPS 到了瓶颈扩容磁盘不做业务迟早出问题。2.3 CPU、负载与网络聚合维度定错了就是在看假数据CPU 告警的常见错法是用avg(rate(node_cpu_seconds_total{modeidle}[5m]))判断“整体空闲”。但现代物理机动不动就几十核一个计算密集型的线程打满单核avg算出来可能只有 3%告警完全不会触发。可业务线程如果绑定在那一核上延迟可能已经飙升了。所以我通常先把各核的空闲率算出来再取最小值( 1 - min by (instance, cpu) (rate(node_cpu_seconds_total{modeidle}[5m])) ) * 100 95表示“至少有一个核心在 5 分钟窗口内几乎打满”这种规则对排查单线程性能瓶颈更有现实意义。如果想找整体负载可以看node_load1但需要和 CPU 核数做对比而不是任取一个固定值。业界常用做法是算“load1是否超过核数乘以某个系数”“load1 与 load15 的差值是否拉大”前者看水位后者看趋势。网络指标要特别注意方向。node_network_receive_bytes_total是入向流量node_network_transmit_bytes_total是出向流量漏掉网卡过滤条件时lo回环网卡会把本机进程间的流量也算进来数字大得离谱但没有参考价值。我在网络类告警里一定会加device!~lo|docker.*|veth.*过滤否则 Pod 网络方案活跃的机器上虚拟网卡列表每天都是变化的规则稳定性会很差。2.4 时间同步、systemd 与进程级指标容易被忽略的生产信号除了 CPU、内存、磁盘这些“老三样”node_exporter 里还有几个指标在生产环境非常值得盯但很多人压根没写规则。第一个是时间同步。node_time_seconds和node_timex_offset_seconds能一起说明问题。机器时钟漂移超过几百毫秒时对分布式系统是灾难性的日志时间线错乱、分布式事务超时判断出错、TLS 证书校验偶发失败。我见过一次跨机房应用超时比例升高排查到后面发现是一个节点的时钟快了 2 秒导致大量基于时间的请求处理判断颠倒。写规则时建议使用abs(node_timex_offset_seconds) 0.5node_timex_offset_seconds表示系统时钟与硬件时钟/NTP 源的偏移绝对值超过 0.5 秒基本说明 NTP 同步已经出了问题。第二个是 systemd 单元状态。node_systemd_unit_state{statefailed} 1这个表达式能帮你抓出“服务进程还活着但 systemd 认为它启动失败”的边角情况。尤其是有多实例、多副本的场景一个服务的某个实例崩溃后 systemd 拉不起来如果业务侧没有做健康检查这条告警可能是你唯一能感知到的渠道。我甚至会在关键单元上单独加一个类似“node_systemd_unit_state{namexxx.service, statefailed} 1”的精确规则比扫所有 unit 更聚焦也更不容易被无关服务刷屏。第三个是文件描述符与进程数。node_filefd_allocated达到node_filefd_maximum的 80% 以上时通常意味着某些进程疯狂创建 socket 或读写文件而不释放虽然不是所有业务都触发但一旦触发就是上游问题的重要旁证。这类指标我建议给一个 warn 级别由后台系统自动跟踪不必劳烦值班人。2.5 for 参数抖动的缓冲器而不是报警的延迟器Prometheus 的for字段经常被当成“让告警晚点发”的工具这是误解。它的真正作用是确认“瞬时高值”是否延续成了“稳定异常”。for: 5m意味着查询表达式连续 5 分钟都满足条件才会把 pending 状态翻成 firing。这能过滤掉大量由 GC 停顿、定时任务跑批、批量导入造成的瞬间毛刺。但for也不是越大越好尤其是内存或磁盘这类“慢变量”内存耗尽类for: 2m基本务实因为可用内存从 10% 降到 0 的速度可能很快磁盘容量类用for: 10m甚至更长也没问题磁盘不会在几分钟内从 80% 涨到爆满除非有异常的日志写入CPU 单核打满类建议for: 15m以上因为单核抖动频繁很多批量任务在几十秒内就会结束短 for 只会让告警变成日常背景音。如果拿不准我宁可把for设长一点同时把阈值收紧也不要反过来阈值放宽 for缩得很短。后者的告警会像楼下便利店的门铃一直在响但每次都不是你家的事。3. 生产级告警规则的工程化落地3.1 规则文件组织一个 group 只讲一件事node_exporter 规则刚起步时我习惯把所有规则塞进一个node_rules.yml后来发现每次调整都极度痛苦想改磁盘规则要在一个几百行的文件里反复上下滚动还容易把某个标签写串。后面我按“子系统”拆成多个 group文件名对应报警主题。如果你管理的机器按照角色分数据库、网关、业务容器节点可以更进一步网络和基础系统类的规则放通用 group和数据库相关的放database_specific_rules.yml。每个 group 内部规则顺序也有讲究把 critical 高的规则放前面warn 级的放后面Alertmanager 在渲染报警列表时人能一眼看到最严重的部分。类似这样的组织方式groups: - name: node-basic-critical rules: # 内存耗尽、磁盘只读、时钟偏移这类直接破坏可用性的规则放这里 # for 短、severitycritical、路由到 on-call 组 - name: node-basic-warning rules: # 磁盘容量趋势、CPU 长时间高负载、网络异常增长等信息 # for 长、severitywarning、合并发送单个 group 不要超过 10 条规则这是我在线上踩出来的舒适区边界。超过这个数字时大概率你混入了不适合做告警的“观察型指标”。3.2 告警文案是半个运维百科很多人写规则时只写expr剩下全靠 Alertmanager 通用模板。结果值班同学收到一条消息“[FIRING] node_filesystem_avail_bytes ...”只能自己去 Prometheus UI 里翻表达式现场混乱程度直接翻倍。我写的每条 production 规则annotations 都必须包含四件事现象、影响、排查路径、参考依据。不要嫌字多告警信息本身就是一个操作手册的起点。比如磁盘告警的 summary 不要只写“磁盘空间不足”可以写成节点 A 的根分区 / 可用空间低于 10%当前剩余 6.2GB。可能导致系统日志无法写入、临时文件清理失败进而影响容器重建。请先使用 df -h 确认是否为大文件堆积如果没有明显大文件再用 du -x --max-depth1 / 从根目录逐层确认占用来源。没有人会在被唤醒后喜欢阅读五千字小说但这些要点式的文案让他在 5 分钟内能形成有效判断。另外我习惯把“恢复条件的预期”也写进runbook_url或 metrics 的标签里比如恢复阈值是 15% 不是 10%否则你修复后反复横跳只会加倍值班同学的血压。3.3 promtool让规则在进生产前先过一遍测试规则文件里 PromQL 表达式语法很坑一个括号位置错了告警规则直接静默失效。更麻烦的是Prometheus 在加载规则时即使发现语法错误也只会把整条规则标记为不健康不会立刻打断你已经跑起来的旧规则于是你很容易在“不知不觉中丢了一条告警”。我每次改完规则都会先跑一遍本地校验promtool check rules node_rules.yml这一步能把 YAML 结构和表达式语法问题先拦住。Prometheus 新版本还支持promtool test rules可以用单元测试的方式断言“在预设的样本数据下这条告警是否应该触发”。我的建议是宁可多写几条样本断言也好过让规则像待拆炸弹一样躺进生产环境。至少给容量类和内存类规则各配一个测试用例一个是“确实快满时应触发”一个是“瞬时尖峰不应触发”这能逼你想清楚规则的实际语义。如果没有测试文件上线后也至少观察一周的告警事件。Prometheus UI 的 Alerts 页面可以看到 pending 与 firing 状态。如果一个 warning 级别的规则从未 firing不代表它没在工作反而可能说明阈值偏松如果一个 critical 规则天天规律地在某几个时间点触发大概率是周期任务导致的“伪故障”需要重新调整 for 或阈值。3.4 Alertmanager 路由、抑制与去重规则写得再好如果 Alertmanager 的路由配置一团糟依然会收到一堆孤立告警。一条生产事故往往是“根因坏了连带一堆指标异常”。最典型的是数据库节点宕机你会同时收到进程不可用、磁盘 IO 异常、该节点上的应用接口超时等告警。如果没有抑制关系值班人会在同一时间收到十几条消息真正需要处理的那一条反而被淹没在海量事件里。Alertmanager 的抑制配置可以解决这个问题。比如当某个实例出现 critical 级的磁盘只读事件时可以抑制该实例所有 warning 级的容量告警。规则的价值是让人把注意力集中在根因上而不是做告警的搬运工。路由上也建议按团队职责切分而不是按“集群名”切分。用severitycritical的标签第一时间走高优通知渠道电话或短信severitywarning默认静默累计到工作时段统一看板带teamdb标签的规则自动路由到数据库值班组。这比让所有人收同样消息科学得多。4. 误报排查与上线调优实录4.1 典型误报一内存可用率告警挂了一晚上主机却没事最早我写的内存规则是“剩余 10% 告警”上线第一天晚上就疯了。盘点下来根因是机器上有大量的页缓存被node_memory_MemFree_bytes计算误解了。当时表达式没考虑MemAvailable导致一台跑着 Redis 的机器频繁把“有缓存但可用”的状态误判为“内存不足”。我后来把表达式全部切换成MemAvailable后同样的机器一周最多触发一次而且触发时业务侧确实在报 OOM 或 GC 频繁。排查这类问题时不要只看 Grafana 面板的平均曲线直接到那台机器上执行cat /proc/meminfo | grep -E MemTotal|MemFree|MemAvailable|Buffers|Cached|SReclaimable观察MemAvailable与MemFree的差值。如果差值很大说明回收缓存足以支撑突发内存需求内存告警大概率是阈值算法的问题。如果差值很小这时告警才真正可信可以把焦点放到应用程序的内存泄漏或 maxmemory 设置不当上。4.2 典型误报二磁盘告警里全是 overlay 和 tmpfs 的噪声容器化普及之后给 Node 节点写磁盘规则几乎必然遇到overlay和tmpfs刷屏的问题。第一次踩到的时候我在集群里收到几十条来自不同容器挂载点的告警人差点崩溃。后来才理解node_exporter 对挂载点的枚举是“见什么报什么”而容器运行时会在宿主机上创建大量虚拟挂载它们共享的是同一个宿主文件系统空间但每个挂载点都对应一条独立的node_filesystem_size_bytes序列导致重复告警看起来像出了几十次故障。解决办法是白名单式过滤而不是黑名单式追加。我的通用规则写了fstype!~tmpfs|overlay|squashfs|ramfs还不够某些环境还有autofs、tracefs、configfs推荐用运行时动态确认df -hT | awk NR1 {print $2} | sort -u然后把这台机器实际会挂载的文件系统类型放进白名单。生产环境中我只对ext4|xfs这类真实数据盘做容量告警其余类型一律不进入告警通道放面板观察就够了。4.3 典型误报三for 周期设得太短毛刺当成了故障有一类毛刺来自计划任务每天凌晨 2 点数据仓库会跑全量重算节点的 load 和 CPU 会在十分钟内飙升但通常不会导致业务失败。当时我给 CPU 单核打满设了 5 分钟持续条件结果每天早上都会收到一波“单核打满”的告警排查后没发现任何异常只能一个个确认“这是跑批任务”。后来我把这类任务执行时间窗口的指标与基础告警规则做联动直接用一个时间变量排除固定维护窗口。PromQL 里没有跨时间段的排除语法但可以在 alert rule 的 expr 中加上unless on () hour() 2 and hour() 4或者直接把 for 调整为 15 分钟以上让规则跑批后再判断是否仍然异常。两招搭配告警数量直接降了一个量级。这种问题尤其容易出现在“用测试环境的节奏写生产规则”的场景里。测试环境的指标曲线比较干净不需要 for 也能稳定触发生产环境有各种定时任务、批量脚本、外部流量突刺for 周期和噪声特征必须先摸清再定。4.4 快速排查建议与规则维护节奏如果新规则上线后误报频发我建议做三件事第一打开 Prometheus 的 Rules 页面找到目标规则点击“Query”查看当前表达式的实际数值确认阈值与真实曲线是否匹配第二在 Alertmanager 的 Silences 里给这条规则设置一个短期静默等待人工分析不急着在 Prometheus 里删规则第三把告警事件和当时的指标快照贴进复盘文档连续观察两周后回看决定是调节阈值、调整 for 还是直接删除。我这里还会用一张简单的表格维护常见误报案例时刻提醒自己别再重蹈覆辙误报现象根因解决方向内存剩余不足误报MemFree 未考虑 page cache改用 MemAvailable 计算可用内存磁盘重复推送 overlay 告警文件系统过滤条件不完整用 df -hT 确认实际 fstype 后做白名单凌晨 CPU 高负载误报批量任务导致的瞬时毛刺for 调大或排除计划任务窗口node_exporter 自身挂掉导致无数据抓取任务静默失败配置 up 0 的全局探活告警告警恢复后立刻再次触发恢复阈值靠近触发阈值引入 hysteresis恢复阈值与触发阈值拉开距离5. 写好规则之后再往前走一步规则上线不是终点持续的维护才是。我个人的习惯是按季度检查一次规则列表把半年都没触发过、且没有承载风险预期的规则下掉或者降级为记录规则避免随着业务演进旧规则变成“永远存在但没有意义的红色指示灯”。另外一个小技巧是如果有条件把 node_exporter 的指标另存一份到长期存储和告警事件打通。这样每次做容量规划时不用拍脑袋说“我们好像经常磁盘满”直接按季度查询磁盘增长斜率就能判断是不是要给某类节点提前升配了。告警只是抓手真正有价值的还是把数据还原成系统演进的轨迹。我在实际工作中最大的感受是会写 node_exporter 告警规则的人不少能把规则的误报率压到很低并对业务真正有用的人不多而这种差距往往就藏在阈值设计、指标语义和文案细节的颗粒度里。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →