Prometheus 告警规则设计:PromQL 与 Alertmanager 路由

监控的价值不在于"有数据",而在于"该响应的时刻有正确的告警"。Prometheus 的告警链路分为两层:告警规则在 Prometheus 端用 PromQL 持续计算,产生告警;Alertmanager 负责对告警做去重、分组、路由、抑制与静默,最后把通知送达正确的渠道。这一层设计得好坏,直接决定团队是被告警淹没,还是真正能睡个好觉。

用 PromQL 写出有意义的规则

告警规则的核心是 PromQL 表达式与 for 持续时间。官方建议遵循几个原则:

  • for 过滤抖动:像 up == 0 这种瞬时判断容易被网络抖动误触发,配合 for: 5m 表示"持续 5 分钟"才告警,能显著减少误报。
  • rate() 而不是裸计数器:对计数器类指标(如请求数、错误数)必须用 rate()increase() 计算速率,直接比较裸值几乎毫无意义。
  • 关注比率与趋势:比"错误数超过 X"更有用的是"错误率超过 1%"或"错误率在过去 15 分钟翻倍",这更贴近用户可感知的故障。

一个典型的高质量规则长这样:

groups:
  - name: api-errors
    rules:
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{job="api",status=~"5.."}[5m]))
            / sum(rate(http_requests_total{job="api"}[5m])) > 0.05
        for: 10m
        labels:
          severity: page
        annotations:
          summary: "API 5xx 错误率超过 5%"

for 的取值没有标准答案,但可以参考以下经验值:

指标类型 建议 for 理由
节点存活(up 2-5m 太短会被网络抖动误触,太长拖慢响应
错误率上升 5-10m 需要排除瞬时毛刺
资源耗尽(内存/磁盘) 10-15m 这类问题通常有前兆,不必抢告警

想表达"错误率在过去一段时间翻倍"这类趋势,可以组合 rate() 与比较运算:sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total{status=~"5.."}[15m])) > 2。趋势告警比固定阈值更贴近用户可感知的变化,也更容易与 SLO 目标对应。

Alertmanager:分组、抑制与静默

Prometheus 把产生的告警推给 Alertmanager,由它做三件事:

  • 分组(Grouping):把相似告警合并成一条通知。官方文档举例:集群里上百个实例同时失联数据库时,按 clusteralertname 分组,只发一条包含受影响实例清单的紧凑通知,而不是上百条。分组由路由树(routing tree)配置。
  • 抑制(Inhibition):当某个更高级告警(如整个集群不可达)已触发时,静默掉其它相关性较低、无助于定位的告警,避免"几百条无关告警同时轰炸"。
  • 静默(Silences):在维护窗口或已知问题上按匹配器暂时静音告警,例如发布窗口内对某服务的告警静默 30 分钟,配置在 Alertmanager 的 Web 界面中。

路由配置示例(按严重级分发到不同接收者):

route:
  group_by: ['cluster', 'alertname']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - match:
        severity: page
      receiver: pagerduty-critical
    - match_re:
        severity: warning
      receiver: slack-alerts

限流与高可用

Alertmanager 还支持两个容易忽略的运维细节:一是 --alerts.per-alertname-limit 限制单个 alertname 的最大活跃告警数,防止异常实例数量导致接收者被刷爆;二是通过 --cluster-* 参数组成高可用集群,且不要在 Prometheus 与 Alertmanager 之间做负载均衡,应让 Prometheus 指向全部 Alertmanager 实例。

标签:告警的寻址系统

告警规则里的 labels 不只是展示信息,它决定告警如何被路由、分组与抑制。建议给每条规则打上两层标签:一是业务维度(如 serviceenvironmentteam),用于按团队路由;二是严重级别(如 severity: page|warning),用于决定通知渠道与升级路径。在路由树里用 matchmatch_re 匹配这些标签,就能实现"哪个团队、什么级别、走哪个渠道"的精细控制。

另一个常见误区是把动态值塞进告警名称或标签,例如把实例 IP 写进 alertname。这会让同一条逻辑规则产生海量"看似不同"的告警,分组与抑制全部失效。正确做法是让规则模板化:动态信息放在 annotations 里展示,静态信息放在 labels 里寻址。

还有一类容易被忽视的是录制规则(recording rules):把计算成本高、被多个面板重复使用的表达式预计算成新指标,既减轻查询负担,也让告警表达式更短、更可读。比如把 sum(rate(http_requests_total[5m])) by (service) 录制成 job:http_requests:rate5m,后面所有面板与告警都直接引用它,排查时也只需理解一个名字。

一次告警风暴的复盘

某团队上线了"磁盘使用率 > 80%"的告警,没加 for,也没按节点分组。结果夜间批量任务写入日志,80 台机器的磁盘同时超过阈值,Alertmanager 直接给值班手机推了 80 条通知。等值班人关掉通知流,真正的"主库连接数暴涨"告警已经被淹没在里面。

复盘后做了三处修改:给磁盘告警加上 for: 15m,在路由里按 instance 分组、只发一条包含受影响节点清单的通知,并为"主库"这类高优先级告警单独配置更高的路由优先级。之后同类场景再也没有刷屏。这个案例说明,告警数量本身不是问题,"是否让该响应的告警被看到"才是。

参考:Prometheus 告警总览 https://prometheus.io/docs/alerting/latest/overview/,Recording Rules 文档 https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/

16IDC 观察

告警规则的打磨是一个持续过程:上线后观察一个月,把从未引起行动的告警降级或删除,把关键故障复盘沉淀为规则。可以结合 Prometheus + Grafana 基础监控Prometheus 监控系统搭建完成基础部署;规则与 On-call 分级配合见 告警疲劳治理实践,告警与 SLO 绑定可参考 SLO/SLI 落地与错误预算。Grafana 侧的新特性可关注 Grafana 13.1 发布。更多内容请查看 监控报警分类。

原文来源:https://prometheus.io/docs/alerting/latest/alerting_rules/