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 把自检做成习惯。这套口径立住之后,告警数量通常不增反降,而每一条响起来的,都值得有人起身处理。







