Prometheus 告警实战:写 alerting rules、用 Alertmanager 做路由分组与抑制
很多团队把 Prometheus 装好、Grafana 图也画得漂漂亮亮,然后就没有然后了------真出故障的时候,没人盯着大屏,报警靠用户投诉。监控的最后一公里是告警,而这一公里恰恰最容易做烂:要么规则写得太敏感,半夜狂轰乱炸把人训练成"狼来了";要么阈值太宽,真挂了也不响。
这篇文章把告警链路走一遍:Prometheus 里写 alerting rules,Alertmanager 里做分组、路由和抑制,最后落到一个能收敛噪音的可用配置。
告警链路先理清
告警分两半,职责不同,别搞混:
- Prometheus:负责"判断"。根据 PromQL 规则计算,满足条件就产生一条 alert,推给 Alertmanager。
- Alertmanager:负责"投递"。接收 alert,做分组、去重、静默、抑制,最后按路由发到钉钉/邮件/PagerDuty。
阈值和触发逻辑写在 Prometheus;"发给谁、怎么发、要不要合并"写在 Alertmanager。
第一步:写一条不会误报的 alerting rule
新手最常见的错误是没有 for 字段,导致指标瞬间抖一下就报警。
yaml
# rules/node.yml
groups:
- name: node-alerts
rules:
# ❌ 反面教材:CPU 一飙高就报,毛刺也触发
- alert: HighCpuBad
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
# ✅ 正确:持续 10 分钟高于阈值才算数
- alert: HighCpuUsage
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
for: 10m # 持续满足 10 分钟才从 pending 转 firing
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} CPU 使用率过高"
description: "CPU 已连续 10 分钟超过 80%,当前 {{ $value | printf \"%.1f\" }}%"
几个关键点:
for: 10m:表达式为真后,alert 先进入pending状态,持续满足 10 分钟才转firing真正发出。这一条能过滤掉 90% 的毛刺误报。labels:给 alert 打标签(如severity),Alertmanager 后面靠它路由。annotations:给人看的描述,支持模板。$value是表达式当前值,$labels.xxx取 series 标签。
再看一个更实用的例子------服务不可用 ,注意用 absent 处理"指标彻底消失"的情况:
yaml
- alert: InstanceDown
expr: up{job="api"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "{{ $labels.instance }} 已宕机"
# up 指标本身消失了(exporter 挂了/被删),up==0 都匹配不到
- alert: ExporterMissing
expr: absent(up{job="api"})
for: 5m
labels:
severity: critical
annotations:
summary: "api 的监控目标彻底消失了"
写完在 Prometheus 配置里挂上规则文件,并指向 Alertmanager:
yaml
# prometheus.yml
rule_files:
- "rules/*.yml"
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
改完用 promtool 校验,别直接 reload 上线:
bash
# 语法检查,CI 里也能跑
promtool check rules rules/node.yml
# 热加载,不用重启进程
curl -X POST http://localhost:9090/-/reload
第二步:Alertmanager 分组,别让 100 台机器发 100 条
假设一个机房断网,50 个实例同时 InstanceDown。如果不分组,你会收到 50 条钉钉------这就是告警疲劳的源头。group_by 让同一类告警合并成一条。
yaml
# alertmanager.yml
route:
# 按告警名 + 集群分组:同名同集群的合并成一条通知
group_by: ['alertname', 'cluster']
group_wait: 30s # 第一条来了先等 30s,攒一批一起发
group_interval: 5m # 同组已有通知后,新增成员每 5m 再发一次
repeat_interval: 4h # 一直没恢复的,每 4h 提醒一次,别刷屏
receiver: 'default-dingtalk'
receivers:
- name: 'default-dingtalk'
webhook_configs:
- url: 'http://dingtalk-webhook:8060/dingtalk/webhook/send'
group_wait:分组第一次通知前的等待,给"同批告警"一个攒齐的窗口。repeat_interval:未解决告警的重复提醒间隔,别设太短(几小时合适)。
第三步:按 severity 路由到不同渠道
warning 走群消息就行,critical 得电话叫醒人。用子路由 routes 做分流:
yaml
route:
group_by: ['alertname', 'cluster']
receiver: 'default-dingtalk'
routes:
- matchers:
- severity = "critical"
receiver: 'oncall-pager' # 严重的走电话/PagerDuty
group_wait: 10s # 严重告警等待更短,尽快发出
continue: false # 匹配到就停,不再往下走
receivers:
- name: 'default-dingtalk'
webhook_configs:
- url: 'http://dingtalk-webhook:8060/dingtalk/webhook/send'
- name: 'oncall-pager'
webhook_configs:
- url: 'http://pager-gateway:9000/trigger'
matchers 按 label 匹配,continue 决定命中后是否继续尝试后面的路由(默认 false,即命中即停)。
第四步:抑制规则,消灭"连带告警"
节点整个宕机时,它上面所有服务都会报 InstanceDown 之类的下游告警。你只想收到"节点宕机"这一条根因,不想被几十条连带告警淹没。这就是 inhibit_rules:
yaml
inhibit_rules:
# 当同一 instance 上有 critical 告警 firing 时,
# 抑制掉同一 instance 上的 warning 告警
- source_matchers:
- severity = "critical"
target_matchers:
- severity = "warning"
equal: ['instance'] # 只在 instance 相同时抑制
source 是"因",target 是"果",equal 指定"同一维度"。上面这条的含义:同一台机器上,critical 存在时就压掉它的 warning------因为你已经知道这台机器出大事了,warning 是噪音。
验证:别等真故障才发现配置错了
临时触发一条 alert 测投递链路,用 amtool:
bash
# 手动塞一条告警进 Alertmanager,验证路由和渠道通不通
amtool alert add alertname=TestAlert severity=critical instance=test-1 \
--alertmanager.url=http://localhost:9093
# 检查路由会把某条告警发到哪个 receiver,不用真触发
amtool config routes test --config.file=alertmanager.yml \
severity=critical alertname=InstanceDown
amtool config routes test 特别有用:它模拟一条带指定 label 的告警,告诉你它会命中哪个 receiver,配置对不对一目了然。
小结
- 告警链路分两半:Prometheus 判断(rules)+ Alertmanager 投递(分组/路由/抑制),阈值和渠道别写在一起。
- 每条规则都加
for,让它先 pending 再 firing,过滤毛刺误报;指标可能消失时用absent。 - 用
group_by分组 合并同类告警,repeat_interval设几小时避免刷屏。 - 用 子路由按 severity 分流 渠道,用
inhibit_rules抑制连带告警,只留根因。 - 上线前
promtool check rules校验、amtool config routes test验路由,别拿真故障当测试。
一句话记忆点:告警的价值不在"响得多",而在"该响时响、不该响时闭嘴"------for、分组和抑制,就是让它闭嘴的三把锁。