路由与接收器
安装与快速上手里跑通了一条「告警进来 → 发到默认渠道」的最短链路。真实环境没这么简单:critical 要打电话、warning 进 IM 群、不同团队的告警各回各家,还得控制通知的节奏,别一条抖动的告警把群刷爆。这些全归路由(route)和接收器(receiver)管,也是 Alertmanager 配置里最重要、最容易配错的一块。这篇把路由树的匹配规则、group_by 分组、三个时间参数和常用接收器逐个拆开讲。示例基于 Alertmanager 0.27+,基础三段式配置见 [告警规则与 Alertmanager](/docs/observability/prometheus/告警规则与 Alertmanager),不重复展开。
路由树怎么工作
告警进入 Alertmanager 后,带着一整套标签(alertname、severity、team、instance……)从路由树的根节点往下走:
- 每个 route 节点上挂着匹配条件,告警标签全部满足该节点的条件才算命中;
- 命中后默认就地停下,用这个节点指定的 receiver 发通知;
- 节点上写了
continue: true的话,命中之后继续往下匹配兄弟节点,一条告警可以进多个渠道; - 一路没命中任何子节点,就落回根路由的默认 receiver——所以根路由必须配 receiver,它是兜底。
结构上一棵典型的路由树长这样:
route:
receiver: default # 兜底,必须有
routes:
- matchers: [severity="critical"]
receiver: oncall
- matchers: [team="infra"]
receiver: infra-group
匹配语法分新旧两代。0.22+ 推荐 matchers,四种操作符写在字符串里,一条一个条件,同一路由下多条是 AND 关系:
matchers:
- severity="critical" # 等于
- env!="test" # 不等于
- service=~"order|pay" # 正则匹配(RE2,隐含首尾锚点全匹配)
- instance!~"10\\.0\\.1\\..+" # 正则不匹配
旧写法是 match(精确等于)和 match_re(正则),在 0.22 之前是唯一的匹配方式,现在标记为遗留写法(deprecated),官方已不推荐。老配置还能跑,但新写的配置一律用 matchers,混着写只会给自己添乱。
和 Prometheus relabel 一个规矩:service=~"order" 只匹配值恰好是 order,想匹配「order 开头」要写 service=~"order.*"。写完拿不准就用 amtool 验证,后面常见坑一节有具体命令。
分组 group_by
Alertmanager 的核心价值之一是把相似的告警聚成一条通知。group_by 声明按哪些标签聚合:标签组合相同、且落在同一路由节点下的告警,会被塞进同一个通知组(group),一次发送。
route:
group_by: ["alertname", "namespace"]
上面这个配法的效果:同一个告警名、同一个 namespace 的告警合成一条——「shop 命名空间的 HighErrorRate 告警(涉及 3 个实例)」是一条通知,而不是三条。
分组的粒度选错,两个极端都难受:
- 太粗:比如
group_by: ["severity"],全集群的 critical 揉成一条通知,消息里几十条不相关的告警混在一起,值班人得自己在长列表里翻重点,跟没分组差不多; - 太细:比如
group_by: ["alertname", "instance", "pod", "container"],Pod 级别的标签一切换就换个组,每条告警各占一条通知,一次故障抖出几十条消息,群直接被刷穿。
经验值是按「谁能处理、处理单元是什么」来分:基础设施工单通常是 ["alertname", "instance"](一台机器一个事),K8s 应用告警通常是 ["alertname", "namespace"] 或 ["alertname", "namespace", "service"]。
group_by 可以写在任意路由节点上,子节点不配就继承父节点。根上配一个通用值,个别路由(比如 critical 电话通道)按需覆盖成更细的粒度,是常见姿势。
三个时间参数
分组定了「和谁一起发」,三个时间参数定「什么时候发、多久再发」。它们都挂在路由节点上,子路由不填就继承父路由:
| 参数 | 默认值 | 含义 |
|---|---|---|
group_wait | 30s | 一个新通知组的第一条告警到达后,等这么久攒批再首发 |
group_interval | 5m | 组里已有告警在发,又来了新成员时,隔这么久合并补发一次 |
repeat_interval | 4h | 同一条告警一直没恢复,隔这么久重复提醒一次 |
画一条时间轴看它们的配合(假设 group_wait=30s、group_interval=5m、repeat_interval=4h):
时间 00:00 00:30 02:00 05:00 05:30 09:30
事件 告警A firing 告警B firing
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
新组产生 首发通知 静默期 静默期 新告警B进组 重复提醒
开始攒批 (含A) (无事发生) (无事发生) 补发(含A+B) (A仍未恢复)
│◄ group_wait ►│ │◄─ group_interval ─►│
│ 距上次发送满5m │
│◄───────────────── repeat_interval=4h ─────────────────►│
几个直觉容易错的地方:
group_wait只在新组首发时生效,组已经存在后来新告警,走的是group_interval,不再等 group_wait;group_interval是从上次发送时间起算,不是从新告警到达起算;repeat_interval针对的是「一直没恢复的同一条告警」,通常要求repeat_interval >= group_interval,配反了行为会很迷惑。
调参思路:critical 通道 group_wait 调短(比如 10s)抢时间,repeat_interval 别太短免得轰炸;warning 通道 group_wait 可以放宽到 1~2m 多攒点,repeat_interval 按半天到一天。
实战:多团队分级路由
把上面几块拼起来,一份多团队场景下可以直接改改用的完整配置。需求是:critical 既进对应的 IM 群又打电话值班;warning 只进各团队的钉钉群;按 team 标签分流向:
global:
smtp_smarthost: smtp.exmail.qq.com:465
smtp_from: alert@example.com
smtp_auth_username: alert@example.com
smtp_auth_password: xxxxxxxx
route:
receiver: default # 没匹配上的兜底,进 SRE 大群
group_by: ["alertname", "namespace"]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
# 第一层:按 severity 分流
- matchers: [severity="critical"]
group_wait: 10s # critical 抢时间,少攒一会儿
repeat_interval: 2h
routes:
# 第二层:critical 既打电话(continue 继续往下走)
- receiver: oncall-phone
continue: true
# 再按 team 嵌套分到各组群
- matchers: [team="order"]
receiver: order-dingtalk
- matchers: [team="pay"]
receiver: pay-dingtalk
- matchers: [team="infra"]
receiver: infra-dingtalk
- matchers: [severity="warning"]
repeat_interval: 12h # warning 不催命
routes:
- matchers: [team="order"]
receiver: order-dingtalk
- matchers: [team="pay"]
receiver: pay-dingtalk
- matchers: [team="infra"]
receiver: infra-dingtalk
receivers:
- name: default
webhook_configs:
- url: http://alert-gateway:8080/dingtalk/sre
- name: oncall-phone
webhook_configs:
- url: http://oncall-gateway:8080/phone
send_resolved: true
- name: order-dingtalk
webhook_configs:
- url: http://alert-gateway:8080/dingtalk/order
- name: pay-dingtalk
webhook_configs:
- url: http://alert-gateway:8080/dingtalk/pay
- name: infra-dingtalk
webhook_configs:
- url: http://alert-gateway:8080/dingtalk/infra
读法要点:
- 嵌套路由就是父子关系:critical 分支内部再挂一层 routes,只有先进了 critical 分支的告警才会看到这一层;
- continue 的位置:oncall-phone 节点不带 matchers,等于 critical 分支里的告警全命中,打完电话
continue: true让它继续往下走、再进对应的团队群——实现「既打电话又进群」; - 各团队 receiver 可以复用同一个转换层地址、只换路径,网关侧按路径路由到不同群机器人,省得每个群起一套服务。
接收器逐个讲
Alertmanager 原生支持的接收器不少(email、webhook、PagerDuty、Opsgenie、Slack、企业微信 wechat_configs 等),国内自建环境里最常用的是 email 和 webhook 两个。注意一个 receiver 可以挂多种配置——比如同一个 receiver 里同时写 email_configs 和 webhook_configs,命中时两边都发。
email:兜底存档
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 # 默认 true;465 端口是 SMTPS,必须开着
receivers:
- name: mail-archive
email_configs:
- to: sre@example.com, oncall@example.com # 逗号分隔多个收件人
send_resolved: true
headers:
subject: "[告警] {{ .CommonLabels.alertname }}"
smtp_require_tls: true 是默认值也是该保持的值——关掉它意味着账密和告警内容明文过 SMTP,只有内网自建的纯明文中继才考虑关。邮件延迟高、容易被淹没,定位是存档和兜底,别当第一响应渠道。
webhook:万能接口
webhook 是最通用的接收器:Alertmanager 把通知组按固定 JSON 结构 POST 到指定 URL,剩下的翻译、转发、打电话全由你自己实现。配置本身没几行:
receivers:
- name: alert-gateway
webhook_configs:
- url: http://alert-gateway:8080/dingtalk/order
send_resolved: true
max_alerts: 20 # 单条 webhook 请求最多带 20 条告警,超出截断
http_config:
basic_auth:
username: alertmanager
password: gateway-secret
收到的是这样的 JSON(结构稳定,直接按字段解析):
{
"status": "firing",
"groupLabels": { "alertname": "HighErrorRate", "namespace": "shop" },
"commonLabels": { "severity": "critical", "team": "order" },
"alerts": [
{
"status": "firing",
"labels": { "alertname": "HighErrorRate", "instance": "10.0.1.11:8080" },
"annotations": { "summary": "下单接口错误率超阈值" },
"startsAt": "2026-10-25T13:00:05.000Z",
"endsAt": "0001-01-01T00:00:00Z"
}
]
}
转换层的思路就是几十行代码的 HTTP 服务:接住这个 JSON → 拼成目标渠道的格式 → 转发。接钉钉群机器人就拼成它的 markdown/text 消息体 POST 给机器人地址(加签密钥别忘了);接企业微信、飞书同理,只是消息体结构不同;接电话值班系统就调值班平台的 API。
钉钉、飞书没有原生 receiver,企业微信有 wechat_configs 但群机器人场景普遍还是走 webhook 转换层——开源的 prometheus-webhook-dingtalk 或自写网关都行,推荐自写一个统一网关,所有渠道收拢在一处,改格式只改一处。
max_alerts 单独说一句:默认 0 表示不截断,一次大故障组里几百条告警会把机器人消息顶爆(钉钉单条消息有长度上限,超长直接发送失败)。设个 10~20,超出的在转换层里加一句「另有 N 条告警省略」最稳妥。
常见坑
路由写了但没生效,十有八九是标签对不上。 告警上根本没有 team 这个标签,路由里 matchers: [team="order"] 自然永远不命中,告警悄悄落进 default 兜底。别靠肉眼对配置,用 amtool 拿真实标签集验证:
amtool config routes test --config.file=alertmanager.yml \
--verify.receivers=order-dingtalk \
alertname=HighErrorRate severity=critical team=order namespace=shop
命令会打印这条标签集最终命中的路由路径和 receiver,和预期不符就是标签或匹配器写错了。先 amtool check-config alertmanager.yml 过一遍语法再上。
group_wait 造成「告警延迟感」。 告警 14:00:05 在 Prometheus 页面已经 firing,通知 14:00:35 才到群里,就有人问监控是不是慢了。这不是故障,是 group_wait: 30s 在等同组告警凑单。critical 通道嫌慢就把这个节点的 group_wait 调小,别全局调。
repeat_interval 默认 4h 轰炸。 一条不恢复的告警每 4 小时准点报一次,夜里响完早上响,修完的人忘了还有这条、没修的人被吵麻。warning 通道放宽到 12h~24h;真正图清净的正确姿势是静默或修根因,不是把 repeat_interval 拉到 7d——拉到 7d 等于这条告警彻底失声,挂三天没人记得。
continue 滥用导致重复通知。 continue 是让「一条告警进多个不同渠道」用的,不是兜底保险。见过有人每条路由都习惯性加 continue: true,结果一条 critical 触发电话、三个团队群、default 大群,五个渠道一起响。规则很简单:只有明确需要「同时走多个 receiver」的节点才加 continue,加之前想清楚它继续往下走会命中谁。
小结
- 路由树自上而下匹配,命中即停,
continue: true才会继续走兄弟节点;根路由的 receiver 是兜底,必须配。 - 匹配用 0.22+ 的
matchers(=!==~!~四种,AND 关系);旧的match/match_re是遗留写法,新配置别再用。 group_by决定聚合粒度,按「处理单元」选标签,太粗太细都失效;三个时间参数各司其职:group_wait 管首发攒批、group_interval 管补发节奏、repeat_interval 管未恢复重复提醒。- 多团队分级路由的标准姿势:severity 分流 + team 嵌套 + critical 用 continue 双通道(电话 + 群)。
- 接收器里 email 做兜底存档(
smtp_require_tls保持开),webhook 是万能接口,钉钉/飞书靠 webhook 转换层,max_alerts记得截断防爆群。 - 改完配置先
amtool check-config查语法、amtool config routes test验路由,再上生产。
下一篇讲让告警「闭嘴」的三种机制:抑制、静默与免打扰时段——路由解决「发给谁」,它们解决「什么时候不发」。