Prometheus 告警规则实战:从 Recording Rule 到 Alertmanager 路由

Prometheus 告警规则是让监控系统从”被动看板”升级为”主动通知”的核心机制。本文聚焦告警规则与 Alertmanager 的实战配置:用 PromQL 写出精准的触发条件,借助 for 字段过滤瞬时抖动,再用 Alertmanager 完成分组、抑制与路由分发。掌握这套链路,服务器异常就能在用户感知之前被自动推送到值班渠道。

它与监控面板是互补关系:面板(如 Grafana 大盘)用于”事后排查与趋势观察”,告警用于”事中即时响应”。如果只有面板、没有告警,你往往要在事故扩大后才发现。建议先把指标采集和看板搭好(可参考本站《Prometheus + Grafana 监控面板实战》),再补这一层告警。

告警疲劳是另一个隐形杀手:当告警多到没人看,真正危险的反而被淹没。因此好的告警设计追求”高信噪比”——每一条都应对应一个明确的”人要去做的动作”。如果一条告警触发后没人知道该干什么,它就不该存在。本文的 for、抑制与静默,本质上都是在为信噪比服务。

一、告警规则骨架:expr 与 for

一条告警规则由 alert、expr、for、labels、annotations 五部分组成。expr 是一个 PromQL 布尔表达式:结果为空表示不触发,有返回值(向量)表示触发;for 指定”持续多久才真正发出告警”,用来消除偶发抖动导致的误报。labels 给告警附加维度(如 severity),annotations 则承载给人看的信息。

groups:
- name: instance-health
  rules:
  - alert: InstanceDown
    expr: up == 0
    for: 2m
    labels:
      severity: critical
    annotations:
      summary: "实例 {{ $labels.instance }} 已宕机"
      description: "{{ $labels.instance }} 的 up 指标为 0,已持续 2 分钟"

上面这条规则监控所有抓取目标:当某个 instance 的 up 等于 0 并持续 2 分钟,就触发 critical 级告警。模板里的 summarydescription 支持 Go template,可把实例名、值等动态注入消息。annotations 中的 {{ $value }} 会代入触发时表达式的当前值,{{ $labels.xxx }} 代入对应标签;善用它们能让值班同学一眼定位,而不必再登服务器查。需要区分的是:labels 是参与路由与分组的维度(如 severity),annotations 只用于展示,不参与匹配。

二、Recording Rule:把高频查询物化

当大盘或告警反复计算同一条复杂 PromQL(例如带 rate、sum by 的聚合)时,可以用 Recording Rule 把结果预计算成一个新指标。它既加速查询,又统一了口径,避免多处写法不一致。

groups:
- name: node-recording
  interval: 30s
  rules:
  - record: job:http_requests:rate5m
    expr: sum by (job) (rate(http_requests_total[5m]))
  - record: node:cpu:utilisation
    expr: 1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))

物化后的指标 job:http_requests:rate5m 可以直接在面板或告警里引用,Prometheus 会按 interval 周期性求值。命名约定一般用 层级:指标:窗口,便于团队识别。

Recording Rule 能显著降低 PromQL 的重复计算开销:原本每个面板都要 rate 加 sum,物化后只算一次。但要注意它的输出本身也有基数(cardinality),如果 record 出来的序列维度过细,反而会增加内存与存储压力。一般只对”被多处复用且较重”的查询做物化,不要无脑把每条查询都预计算。

三、Alertmanager:分组、抑制、静默

Prometheus 本身只负责”产生告警”,真正”发给谁、怎么合并、何时屏蔽”由 Alertmanager 完成。它有三个关键能力:分组(group_by)把同类告警合并成一条避免刷屏;抑制(inhibit_rules)在高级别告警触发时压制低级别同类告警;静默(silence)在维护窗口内手动屏蔽特定告警。

理解三个时间间隔的差异很关键:group_wait 是”收到第一条告警后等多久再发通知”,给同类告警聚合留窗口;group_interval 是”已通知过的组,间隔多久再追加更新”;repeat_interval 则是”同一条没恢复,多久重复提醒一次”。三者配合得当,既能避免首报刷屏,又不会让长期故障石沉大海。

route:
  receiver: 'dingtalk'
  group_by: ['alertname', 'cluster']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
  - matchers:
      - severity = "critical"
    receiver: 'pager'
    continue: true

receivers:
- name: 'dingtalk'
  webhook_configs:
  - url: 'https://oapi.dingtalk.com/robot/send?access_token=xxxx'

inhibit_rules:
- source_match:
    severity: 'critical'
  target_match:
    severity: 'warning'
  equal: ['alertname', 'instance']

四、路由树与接收器

route 定义告警的流转路径:先按 group_by 分组,在 group_wait 内等待同类告警聚合,再匹配 matchers 选择 receiver。critical 通常走电话或值班系统(如 PagerDuty),warning 走钉钉群。下面给出钉钉 Webhook 接收器的最小配置。

matchers 支持 =、!=、=~、!~ 四种匹配,分别对应等于、不等于、正则匹配、正则不匹配。continue: true 表示匹配后不终止,继续走后续路由,便于一条告警同时进”值班群”和”工单系统”。路由树本质是按深度优先匹配,写规则时务必从具体排到宽泛,否则宽泛规则会提前吃掉具体告警。

receivers:
- name: 'dingtalk'
  webhook_configs:
  - url: 'https://oapi.dingtalk.com/robot/send?access_token=xxxx'
    send_resolved: true

# 在钉钉机器人一侧用「自定义机器人 + 加签」校验来源,
# 防止告警接口被外部调用(配合《服务器安全加固》思路收紧出口)

若你的 Prometheus 运行在 Kubernetes 中(参见《Kubernetes 入门:Pod、Deployment、Service》),建议用 Prometheus Operator 的 PrometheusRule / Alertmanager 自定义资源来管理规则,比裸配置文件更易做版本化。

五、上线前用 promtool 校验

规则与配置写错会导致告警”静默失灵”——最危险的不是报错,而是该响不响。发布到生产前务必本地校验语法与路由。

# 校验规则文件语法
promtool check rules /etc/prometheus/rules/*.yml

# 校验主配置
promtool check config /etc/prometheus/prometheus.yml

# 校验 Alertmanager 配置
amtool check-config /etc/alertmanager/alertmanager.yml

# 本地模拟一条告警是否匹配路由
amtool config routes test --config.file alertmanager.yml \
  --label severity=critical --label alertname=InstanceDown

六、一套可直接套用的最小告警集

以下告警级别约定能覆盖大多数中小团队。把它和面板、采集组成闭环,再把规则文件纳入 Git,通过《GitHub Actions 实战》做 CI 校验,就能保证每次变更都可回溯。

级别含义接收方式响应时效
critical业务已受损电话 / 值班系统15 分钟内
warning潜在风险钉钉群当日内
info仅供参考邮件周报不紧急
groups:
- name: node-alerts
  rules:
  - alert: HighCpuUsage
    expr: node:cpu:utilisation > 0.85
    for: 5m
    labels: { severity: warning }
    annotations:
      summary: "CPU 使用率过高 {{ $value }}"
  - alert: DiskWillFill
    expr: predict_linear(node_filesystem_avail[1h], 4*3600) < 0
    for: 10m
    labels: { severity: critical }
    annotations:
      summary: "磁盘 4 小时内将写满"

七、常见坑与治理建议

踩过最多次的坑集中在四处:for 设太短导致抖动误报;severity 不统一导致路由混乱;没有 group_by 导致同类告警刷屏;忘了 inhibit_rules 导致告警风暴。治理上建议给每条告警都写清 runbook 链接,并定期做”告警演练”验证链路真的通。

还有一类隐性坑是”告警依赖链断裂”:比如磁盘告警依赖 node_exporter 正常采集,一旦 exporter 挂了,你收到的会是 InstanceDown 而不是磁盘告警,反而掩盖了根因。治理上应为关键 exporter 本身也设存活告警,并对采集中断做显式标注,避免被上层告警遮挡。

字段作用常见错误
for持续时长才触发设 0s 引发抖动误报
labels.severity决定路由拼写不一致路由失效
group_by合并同类告警漏配导致刷屏
inhibit_rules抑制低级别漏配导致告警风暴

八、总结

告警的本质不是”越多越好”,而是”该响时响、不该响时安静”。先用 expr + for 写出精准规则,再用 Recording Rule 降本提速,最后用 Alertmanager 的分组、抑制、静默把噪声压下去。配合监控面板与 CI 校验,你就拥有了一条从指标到响应的完整可观测链路。把规则文件当代码管理,告警质量才会随团队一起成长。

最后提醒一点安全:告警 Webhook 往往是公网可达的接口,务必加签名校验并设置来源 IP 白名单,避免被外部伪造请求触发、反过来骚扰值班。告警通道本身也是攻击面,可观测性与安全性一样重要,部署时请对照《服务器安全加固》的思路收紧出口。

上一篇 前端状态管理怎么选:Zustand、Jotai 与 Redux Toolkit
下一篇 RAG 文档解析实战:从 PDF 表格到高质量切分