告警规则与 Alertmanager
前面几篇解决了「指标采进来、能查出来」的问题,但监控的终点不是盯着 Grafana 看图,而是出事的时候有人被叫醒。这篇把告警链路补全:Prometheus 侧的规则怎么写,Alertmanager 侧的分组、路由、抑制、静默怎么配,最后是一份告警设计原则和踩坑清单。示例基于 Prometheus 2.5x/3.x + Alertmanager 0.27+,抓取与查询的基础概念见核心概念与架构。
本篇聚焦 Prometheus 侧的告警规则和 Alertmanager 的入门配置。Alertmanager 本体还有独立系列(路由树深入、抑制静默、HA gossip 集群、通知模板),见「可观测 → Alertmanager 文档」。
告警的完整链路
一条告警从指标异常到你手机响,走的链路是这样的:
- 规则评估:Prometheus 按
evaluation_interval(默认 1m)周期性对每条规则的 PromQL 表达式求值。 - 状态流转:表达式成立,告警进入
pending(持续中);持续满for时长转firing(触发中);表达式不再成立回到inactive。 - 推送告警:处于
firing的告警被推给配置里的 Alertmanager。 - 集中处理:Alertmanager 做四件事——分组(相似的合成一条)、去重(多套 Prometheus 发来的同一条只留一份)、路由(按标签决定发给谁)、抑制与静默(该压的压掉)。
- 发出通知:活下来的告警组装成消息,发给接收器(receiver)——webhook、邮件,或经转换层到钉钉/企业微信。
一句话记住分工:Prometheus 管「该不该报」,Alertmanager 管「怎么报、报给谁」。要改触发条件去改规则文件,要改通知行为去改 alertmanager.yml,别改错地方。
告警规则写法
规则写在单独的 yaml 文件里,由 prometheus.yml 的 rule_files 引入:
rule_files:
- /etc/prometheus/rules/*.yml
规则文件按 group 组织,同一 group 里的规则按顺序评估,group 之间并发:
groups:
- name: host-alerts
interval: 30s # 本组评估间隔,不填用全局 evaluation_interval
rules:
- alert: HostHighCpuUsage
expr: 100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 5m
labels:
severity: warning
team: infra
annotations:
summary: "主机 {{ $labels.instance }} CPU 使用率持续偏高"
description: "{{ $labels.instance }} 的 CPU 使用率超过 85% 已持续 5 分钟,当前 {{ $value | humanize }}%"
runbook: "https://wiki.internal/runbooks/high-cpu"
逐字段说:
| 字段 | 作用 |
|---|---|
alert | 告警名,自动成为告警的 alertname 标签;惯例大驼峰命名 |
expr | PromQL 表达式,结果里每个非空序列都产生一条告警 |
for | 持续时长,连续成立满这个时间才从 pending 转 firing |
labels | 附加标签,合并进告警标签集;分级(severity)、归属(team)都在这里打 |
annotations | 注解,只用于通知文案展示,不参与告警标识 |
annotations 里 {{ $labels.xxx }} 引用该告警序列上的标签,{{ $value }} 是表达式的当前值,走的是 Go template 语法,humanize 这类管道函数可直接用。文案要写人话:收到告警的人半夜三点看一眼 summary 就该知道是哪台机器、什么问题,不用现查。
规则文件改完先 promtool check rules /etc/prometheus/rules/*.yml 再 reload,表达式写错、括号不匹配在加载前就能拦住,别拿生产 Prometheus 当语法检查器。
for 与 pending
for 是告警防抖的核心。没有 for,指标抖一下就触发一次,通道瞬间被刷爆;有了 for: 5m,表达式必须连续成立满 5 分钟才真正通知出去。
它和 evaluation_interval 的关系要说清:for: 5m 不是精确的「连续 300 秒为真」,而是「最近 5 分钟内的每次评估都为真」。评估间隔 1m 时,大致就是连续 5 次评估都成立。所以评估间隔别设得比 for 还离谱——interval: 10m 配 for: 5m,等于一次评估成立就触发,防抖形同虚设。
经验值:基础设施类告警 for: 5m,业务成功率类 for: 2m~5m,磁盘将满这种趋势型预测告警可以不带 for(表达式本身就是预测)。
Alertmanager 部署与配置
Prometheus 侧只需告诉它 Alertmanager 在哪:
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
Alertmanager 自己的配置全在 alertmanager.yml,骨架三块:route(路由树)、receivers(接收器)、inhibit_rules(抑制规则)。一份可以直接改改就用的完整配置:
global:
smtp_smarthost: smtp.exmail.qq.com:465
smtp_from: alert@example.com
smtp_auth_username: alert@example.com
smtp_auth_password: xxxxxxxx
smtp_require_tls: true
route:
receiver: default # 兜底接收器,所有路由都没匹配上时用它
group_by: ["alertname", "namespace", "instance"]
group_wait: 30s # 新告警组攒 30s 再首发,凑齐同组告警
group_interval: 5m # 组内有新告警加入时的补发间隔
repeat_interval: 4h # 同一告警一直没恢复时的重复提醒间隔
routes:
- matchers:
- severity="critical"
receiver: oncall
continue: true # 匹配后继续往下走,critical 同时进存档渠道
- matchers:
- severity="critical"
receiver: email-archive
- matchers:
- team="infra"
receiver: infra-webhook
receivers:
- name: default
webhook_configs:
- url: http://alert-gateway:8080/alert
- name: oncall
webhook_configs:
- url: http://oncall-gateway:8080/critical
send_resolved: true # 恢复也通知,半夜被叫醒的人需要这条
- name: email-archive
email_configs:
- to: sre@example.com
- name: infra-webhook
webhook_configs:
- url: http://alert-gateway:8080/infra
inhibit_rules:
# 同一实例同一告警已有 critical 着火时,压掉对应的 warning
- source_match:
severity: critical
target_match:
severity: warning
equal: ["alertname", "instance"]
几个要点:
- group_by 决定分组粒度:按
alertname+instance分组,同一台机器同一告警的多条触发合成一条通知;按namespace分组,一个命名空间炸了只收一条汇总。 - 抑制规则按
equal里列的标签对齐:上面例子的含义是「同一alertname、同一instance上 critical 已经着火时,对应的 warning 就别发了」。经典场景是「节点都 down 了,节点上磁盘使用率高的 warning 没必要再报」。 - 路由树从上往下匹配,默认命中一条就停,
continue: true让同一条告警同时进入多个渠道。
通知渠道
webhook 是万能姿势:Alertmanager 把告警按固定 JSON 结构 POST 到指定 URL,自己起一个几十行的 HTTP 服务接住,想转什么渠道转什么渠道。自建告警网关、接内部 IM、写工单系统都走这条路。
钉钉 / 企业微信群机器人:Alertmanager 没有原生支持,社区惯例是跑一个 webhook 转换层(比如开源的 prometheus-webhook-dingtalk,或自己写个转发服务),把 Alertmanager 的 JSON 翻译成群机器人要求的格式再 POST 给机器人地址。
邮件直接用 email_configs,SMTP 参数在 global 里配一次全局生效:
global:
smtp_smarthost: smtp.exmail.qq.com:465
smtp_from: alert@example.com
smtp_auth_username: alert@example.com
smtp_auth_password: xxxxxxxx
smtp_require_tls: true
receivers:
- name: mail
email_configs:
- to: sre@example.com
send_resolved: true
邮件延迟高、容易被淹没,适合当存档和兜底渠道,别指望它做第一响应。第一响应渠道得是能「响」的:电话、短信网关、IM 强提醒。
静默与临时豁免
silence(静默) 解决的是「我知道它在报,别烦我」:发布窗口、计划内维护、已知问题修复中。静默配在 Alertmanager 上,按标签匹配器圈定一批告警,在起止时间内这批告警照常评估、照常 firing,但不发通知,过期自动失效。
两种创建方式:
- 页面:Alertmanager Web UI(默认 9093 端口)→ Silences → New Silence,填匹配器、起止时间、创建人和备注。
- API:适合在发布流水线里自动打静默:
curl -X POST http://alertmanager:9093/api/v2/silences \
-H 'Content-Type: application/json' \
-d '{
"matchers": [{"name": "namespace", "value": "shop", "isRegex": false}],
"startsAt": "2026-01-20T02:00:00Z",
"endsAt": "2026-01-20T04:00:00Z",
"createdBy": "deploy-pipeline",
"comment": "shop 服务例行发布窗口"
}'
维护窗口的正确姿势是发布流水线自动打静默、窗口结束自动过期,而不是手动把接收器删掉、事后忘记加回来。静默范围按标签尽量圈小——能按 namespace 圈就别整个集群全静默。
静默只是捂住了通知的嘴,告警本身照常触发,照常能在 Prometheus 的 Alerts 页面看到。复盘时别以为「那段时间没告警」就是「那段时间没问题」。
告警设计原则
几条实操里攒下来的原则,新加一条告警前对照过一遍:
1. 告警必须可行动(actionable)。收到告警的人应该有明确的下一步动作。如果一条告警触发了你只需要「哦,知道了」,那它就不该是告警,顶多是 Grafana 面板上的一条曲线。
2. 报症状,少报原因。「下单成功率低于 95%」该报;「某台机器 CPU 高」多半是原因而不是症状,机器问题该靠扩缩容和自愈兜底。症状告警指向用户正在受的苦,原因留给值班人顺着 runbook 排查。
3. 分级且区别对待。severity 至少分两档:critical 半夜也叫人(电话/值班系统),warning 工作时间处理(IM 群)。分级乱打等于没有分级——什么都 critical 的结果是大家把所有通知一起静音。
4. 别轰炸。单条告警带 for 防抖,Alertmanager 侧 group_by 聚合、repeat_interval 别太短。告警通道的信噪比一旦崩了,真正要紧的告警也会被顺手划掉。
常见坑
for 计时遇上 Prometheus 重启。pending/firing 的计时存在内存里,重启后 Prometheus 会查本地 ALERTS_FOR_STATE 序列尝试恢复计时,但前提是停机时间短(落在 lookback 窗口内)。停久了计时归零重算:一条 for: 10m 的告警在第 9 分钟赶上重启,就得重新再等 10 分钟。对告警连续性敏感就上双 Prometheus 做 HA——两台各自评估,Alertmanager 侧自动去重。
group_wait 造成「延迟错觉」。告警 10:00:05 触发,通知 10:00:35 才到,有人就来问「监控是不是有延迟」。这不是故障,是 group_wait: 30s 在等同组告警凑单。排障时先看 Prometheus 的 Alerts 页确认 firing 时间点,再看通知时间,差值对得上 group_wait 就是正常行为。
repeat_interval 不是重试。它是「告警一直没恢复,隔多久再提醒你一次」,不是「发送失败重发」。有人把它当重试设成 1m,一条不恢复的告警每分钟轰炸一次通道;反向极端是图清净设成 7d,一条挂了三天的告警再没人记得。常用区间 2h~12h,按团队响应节奏定。
模板引用不存在的标签。annotations 里写了 {{ $labels.pod }},但表达式经过 sum by (namespace) 聚合后 pod 标签已经没了,通知里就渲染成 <no value>。写模板时对着表达式实际输出的标签集写,拿不准先在 Prometheus 的 Graph 页把表达式跑一遍看输出。
小结
- 链路分工:Prometheus 评估规则产生 firing 告警,Alertmanager 分组、去重、路由、抑制、静默后发通知。
- 规则五要素:
alert/expr/for/labels/annotations;for负责防抖,通知文案走 annotations 模板。 - alertmanager.yml 三块:
route决定发给谁和节奏(group_wait/group_interval/repeat_interval),receivers定义渠道,inhibit_rules让高级别压掉低级别。 - 通知渠道:webhook 万能,钉钉/企微走转换层,邮件做兜底存档。
- 静默按标签圈范围、设起止时间,流水线自动打;它只是不发通知,告警照常评估。
- 告警设计四原则:可行动、报症状、有分级、别轰炸。
下一篇换个视角:在 K8s 上这一切都不用手写——prometheus-operator 把规则、抓取配置、告警路由全部变成了 CRD。