Prometheus 告警规则实战:从一条规则到 Alertmanager 路由治理

不少团队上了 Prometheus 之后,告警这块是「能响就行」:规则东一条西一条,阈值拍脑袋,半夜被抖动告警炸醒几次之后,大家开始默默屏蔽群消息。告警系统真正的难点不在写表达式,而在口径:分级怎么定、抖动怎么压、同类告警怎么合并、上游故障时下游告警怎么闭嘴。本文以 Prometheus 2.x(规则语法同样适用于 3.x)加 Alertmanager 为例,从一条规范的规则开始,一直讲到路由治理,示例指标来自 node_exporter 与 blackbox_exporter,规则文件和配置都可以直接拿去改。

前置条件

  • Prometheus 已在正常抓取 node_exporter、blackbox_exporter 及应用侧 exporter 的指标,up 指标可用;
  • 已定下告警分级口径,例如 page(电话/IM 强提醒、需立即处理)与 ticket(邮件/工单、当日处理)两级,并明确每一级的接收人;
  • 至少有一个接收渠道已打通(邮件、Webhook、IM 机器人任选其一先行验证);
  • promtool、amtool 可用(随 Prometheus 与 Alertmanager 发行包提供);
  • 规则文件与 Alertmanager 配置纳入 Git 管理,改动走评审,不在线上直接改完就跑。

第一步:一条规则的写法规范

先立规矩再堆数量。一条合格的规则至少包含四部分:expr 用聚合后的指标而不是原始计数;for 给足持续时间,让瞬时毛刺自己消失;labels 里打上统一的 severity 与 team,severity 只用事先约定的枚举值,后面路由全靠它;annotations 写清现象、影响和处理入口,最好带上 runbook 链接,让收到告警的人不用再去问「这个怎么处理」。告警名用 PascalCase 并带对象前缀,如 Node、Mysql、Cert 开头,后续检索和分组都方便。

groups:
  - name: node.rules
    rules:
      - alert: NodeHighMemoryUsage
        expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) > 0.9
        for: 10m
        labels:
          severity: page
          team: ops
        annotations:
          summary: "主机 {{ $labels.instance }} 内存使用率过高"
          description: "可用内存持续低于 10%,当前值 {{ $value | humanizePercentage }}。"
          runbook_url: "https://example.com/runbooks/node-memory"

for 的语义要吃透:它表示 expr 持续成立多久才从 pending 转为 firing,太短会被抖动打穿,太长又会漏掉真实的短时故障。经验上资源水位类给 10 分钟上下,存活类给 2 到 5 分钟,证书到期这类慢变量给 1 小时也无妨。阈值同样要讲得出理由:内存 90% 是因为再往上就逼近 OOM,而不是因为「大家都这么写」。

第二步:四类必备告警先配齐

新集群上线时,先把四类兜底告警配齐,再谈业务定制。存活类解决「监控对象没了都不知道」;资源水位类盯磁盘、内存等慢变量,要在耗尽之前留足处理时间;错误率类直接对着用户体验,比盯单个接口的状态码更靠谱,其中 http_requests_total 是应用侧指标名,按自家埋点替换;证书到期类成本最低、收益最稳,专治「证书过期当天才发现」。

groups:
  - name: baseline.rules
    rules:
      # 1) 存活:抓取目标消失
      - alert: TargetDown
        expr: up == 0
        for: 3m
        labels: {severity: page, team: ops}
        annotations:
          summary: "目标 {{ $labels.instance }} 已失联"
      # 2) 资源水位:根分区可用空间不足
      - alert: DiskSpaceLow
        expr: node_filesystem_avail_bytes{mountpoint="/", fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{mountpoint="/"} < 0.15
        for: 10m
        labels: {severity: page, team: ops}
        annotations:
          summary: "主机 {{ $labels.instance }} 根分区可用空间低于 15%"
      # 3) 错误率:HTTP 5xx 占比过高(指标名按应用埋点替换)
      - alert: HttpHighErrorRate
        expr: sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.05
        for: 5m
        labels: {severity: page, team: app}
        annotations:
          summary: "接口 5xx 错误率持续高于 5%"
      # 4) 证书到期:提前 7 天提醒,走工单级即可
      - alert: CertExpiringSoon
        expr: (probe_ssl_earliest_cert_expiry - time()) < 7 * 24 * 3600
        for: 1h
        labels: {severity: ticket, team: ops}
        annotations:
          summary: "站点 {{ $labels.instance }} 的证书将在 7 天内到期"

第三步:录制规则把贵表达式预计算

错误率、可用性这类告警要对原始计数做多层聚合,每次评估都全量算一遍,既慢又容易和大盘口径对不上。录制规则(recording rules)就是把这类计算提前算好、写成新指标:告警和大盘都引用同一个录制指标,口径天然一致,评估开销也降下来。命名按社区惯例用冒号分层,格式为 层级:指标:窗口,如 job:http_5xx_ratio:rate5m。注意录制规则本身也有评估间隔和存储成本,只给高频复用的表达式建,别把每条 expr 都录一遍。

groups:
  - name: http.recording
    interval: 30s
    rules:
      - record: job:http_5xx_ratio:rate5m
        expr: sum by (job) (rate(http_requests_total{status=~"5.."}[5m])) / sum by (job) (rate(http_requests_total[5m]))

第四步:Alertmanager 的路由、分组与抑制

规则负责「发现」,Alertmanager 负责「别把人吵死」。三件事要配对:路由树按 severity 和 team 分发,page 级走值班通道、ticket 级走邮件工单,不要所有告警挤一个群;分组用 group_by 把同一主机的同类告警合并成一条通知,group_wait 让突发告警攒一攒再发;抑制表达因果关系——主机已经宕机(page)时,它上面的应用告警(ticket)就别再单独发了。静默是计划维护的正规手段,用 amtool 创建并写明原因和时长,到期自动失效,禁止用「把接收人删掉」这种土办法。

global:
  resolve_timeout: 5m
route:
  receiver: default-mail
  group_by: ['alertname', 'instance']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - matchers: ['severity="page"']
      receiver: oncall-webhook
      group_wait: 10s
      repeat_interval: 1h
    - matchers: ['severity="ticket"']
      receiver: team-mail
inhibit_rules:
  - source_matchers: ['severity="page"']
    target_matchers: ['severity="ticket"']
    equal: ['alertname', 'instance']
receivers:
  - name: default-mail
    email_configs:
      - to: 'ops@example.com'
  - name: oncall-webhook
    webhook_configs:
      - url: 'https://example.com/hooks/alertmanager'
  - name: team-mail
    email_configs:
      - to: 'team@example.com'

第五步:规则自检成为固定动作

规则改完先过工具再上线:promtool check rules 做语法与结构校验,amtool check-config 查路由配置;核心告警再用 promtool test rules 写单元测试,构造一段指标序列验证「持续多久触发、恢复后多久消警」是否符合预期。这一步花的时间不多,但能挡住绝大多数「表达式写错一位、告警永远不响或永远在响」的事故。

promtool check config /etc/prometheus/prometheus.yml
promtool check rules /etc/prometheus/rules/*.yml
amtool check-config /etc/alertmanager/alertmanager.yml
# 计划维护时建静默,到期自动失效,并写明原因
amtool silence add alertname=TargetDown instance=192.0.2.31:9100 --duration 2h --author ops --comment '计划维护'

验证

  • Prometheus 页面 Alerts 里能看到全部规则,状态在 inactive / pending / firing 之间流转正常;
  • 人为制造一次告警(如停掉一个测试 exporter 或压低磁盘阈值),确认通知按路由到达正确的人;
  • 同时触发 page 与 ticket 两级告警,确认抑制生效、低级别的没有重复打扰;
  • 建一条短期静默验证到期自动失效,Alertmanager 页面分组展示与预期一致。

常见问题

for 太短,告警狂发

瞬时毛刺直接触发,群里一晚几十条,最后没人看。对策是按指标的抖动特性拉长 for,错误率这类用 rate 窗口先平滑再判断;只有真正需要分钟级响应的存活类才配短 for。狂发过的告警要复盘阈值,而不是简单拉长了事。

抑制关系写反了

source 和 target 对调是最常见的坑:本想让宕机告警压住应用告警,结果把关键告警压没了。上线前用构造告警或 amtool 的路由测试功能验证一遍实际匹配结果,抑制规则一律写清 equal 的标签维度,不要图省事只按 alertname 抑制。

告警到了却没人认领

这通常不是技术问题,而是流程问题:page 级告警没有明确的值班归属、没有升级路径、处理手册缺失。解法是每条 page 告警必须挂 runbook 链接和责任团队,每月复盘一次告警清单,无人认领或从未触发过的告警要么补流程、要么降级、要么下线——告警池子的信噪比是维护出来的。

小结

Prometheus 告警的功夫在表达式之外:规则写法有规范、四类兜底先配齐、贵计算用录制规则固化口径,Alertmanager 侧用路由分级、分组降噪、抑制理清因果,再用 promtool 把自检做成习惯。这套口径立住之后,告警数量通常不增反降,而每一条响起来的,都值得有人起身处理。

发表回复

后才能评论