Prometheus Alertmanager 告警收敛实战:分组 group_by、抑制 inhibit_rules、静默与路由树全流程

为什么你的告警群天天在「狼来了」

给个人站搭监控,装 Prometheus 和 node_exporter 只花十分钟,但真正让运维崩溃的是后面这一步:告警配置。默认规则没配好,结果就是半夜三点手机被震醒十几次,打开一看全是「一台服务器 CPU 超过 80%」的重复消息;等真出事(比如磁盘写满、站点 502)的时候,消息反而淹没在刷屏里被忽略掉了。

这不是 Prometheus 的问题,而是几乎所有人都会踩的坑:把 Alertmanager 当成一个「顺手转发邮件的组件」,忽略了它真正干的事情——把海量原始告警聚合成少数几条人看得懂的通知。这篇文章就专门讲 Alertmanager 的收敛机制:分组(group_by)、抑制(inhibit_rules)、静默(silence)和路由树(route),并给出一套个人站长可以直接抄走的配置。

先理清角色:Prometheus 判「有没有事」,Alertmanager 判「要不要吵你」

很多新手把这两个职责搞混,导致规则写得又长又难维护。正确的分工是:

  • Prometheus(或 VictoriaMetrics、Mimir 等兼容组件):负责按 rule_files 里的表达式判断「for 多久持续满足条件」,一旦满足就产生一条 firing 状态的告警,周期性地推给 Alertmanager。它只关心指标,不关心你手机响几次。
  • Alertmanager:负责接收这些告警,然后按 分组、抑制、静默、路由 四套规则决定:哪些合并成一条、哪些根本不用发、发给谁、用什么渠道发。

换句话说,重复告警、告警风暴、半夜被吵,全都是 Alertmanager 该解决的问题,不是靠堆 Prometheus 规则去「压制」。理解这一点,后面配置才有主线。

分组 group_by:一台机器挂了别发 20 条告警

假设一个节点宕机,Prometheus 会同时产生 CPU、内存、磁盘、网卡、进程存活等一连串告警。如果不分组,你就收到 20 条通知。分组的本质是——把「属于同一逻辑对象」的告警合并成一条通知发出去。

Alertmanager 的分组由路由树里的 group_by 决定,默认是 [alertname, cluster, service]。对个人站最实用的配置通常是按「主机 + 告警大类」聚合:

route:
  receiver: 'default-email'
  group_by: ['alertname', 'instance']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h

这里三个时间参数是收敛的关键,别照抄网上的默认值就完事:

  • group_wait:新一组告警产生后,先等 30 秒再发。这一小段等待是为了把同一波事件里的兄弟们「凑齐」一起发,避免刚发出去又来一条补一刀。
  • group_interval:同一组告警在后续有新成员加入时,最长多久发一次更新。别设太短,否则会持续刷屏。
  • repeat_interval:一条已经发过的告警,如果一直没恢复,多久重发一次提醒。个人站设 4h 到 12h 都合理;设成 1h 你就会白天黑夜都被同一件事吵。

有个常见误区:把 group_by 设成 ['...'](空数组),意思是「所有告警合并成一条」。这在小型个人站里看起来很爽,但会带来一个隐蔽后果——不同紧急度的告警被合到一条通知里,你无法一眼判断哪台机器真出事了。建议至少保留 instance。

路由树 route:让不同告警走不同渠道

路由树是 Alertmanager 配置的骨架,它是一棵「标签匹配」的树:从上往下匹配,命中子路由就继续往下走,否则走当前节点的 receiver。

一个实用的个人站路由配置长这样——严重告警走电话/短信,普通告警走邮件,纯信息类的只进汇总:

route:
  receiver: 'email-low'
  group_by: ['alertname', 'instance']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - matchers:
        - severity = "critical"
      receiver: 'phone-sms'
      group_wait: 10s
      repeat_interval: 30m
    - matchers:
        - severity = "warning"
      receiver: 'email-normal'
      group_wait: 1m
      repeat_interval: 2h

要注意两点:

  • 较新版本 Alertmanager(0.22 起)推荐用 matchers(支持 =、!=、=~、!~),老的 match 是精确相等,match_re 是正则,二者不能混用同一条。
  • 路由匹配不会回溯:一旦命中某个子路由,就不会再尝试它的兄弟节点。所以越具体的规则要放越前面,兜底规则(receiver: email-low)放父节点。

抑制 inhibit_rules:有根因告警时,别让衍生告警刷屏

抑制是 Alertmanager 里被低估最深的功能。它的作用是:当某条「根因型」告警已经 firing 时,把由它引起的「衍生型」告警静音掉。

最经典的例子:如果整台主机已经 NodeDown(不可达),那么这台机器上的 CPU、内存、磁盘告警全都毫无意义——机器都没了还谈什么 CPU 使用率。配置如下:

inhibit_rules:
  - source_matchers:
      - alertname = "NodeDown"
    target_matchers:
      - severity =~ "warning|info"
    equal: ['instance']
  - source_matchers:
      - alertname = "DiskFull"
    target_matchers:
      - alertname =~ ".*WriteFail.*"
    equal: ['instance']

关键的 equal 字段表示:只有 source 和 target 在 equal 列出的标签上取值完全一致时,抑制才生效。这里用 ['instance'] 表示「同一台机器的 NodeDown 才能抑制同一台机器的警告」。如果不写 equal,它会被理解为「任意一台机器 NodeDown 就抑制所有机器的告警」,那会掩盖真实故障,非常危险。

个人站最值得加的抑制规则一般是这几条:主机不可达抑制其上所有告警;磁盘写满抑制相关写入失败告警;出口网络断抑制所有抓取失败的 Up == 0。

静默 silence:计划内维护的「一键免打扰」

静默和抑制的区别要分清:抑制是自动的(基于标签条件),静默是手动的(基于时间窗口)。当你要给服务器换内核、重启 MySQL、迁移机房时,提前建一个 silence,就能让这段时间的告警不打扰你,而不用去改 Prometheus 规则。

静默可以通过 Web UI(Alertmanager 默认 9093 端口)点几下就建好,也可以用 API 脚本建,方便写进运维流程:

curl -X POST http://127.0.0.1:9093/api/v2/silences \
  -H 'Content-Type: application/json' \
  -d '{
    "matchers": [
      {"name": "instance", "value": "10.0.0.5:9100", "isRegex": false}
    ],
    "startsAt": "2026-10-15T22:00:00Z",
    "endsAt":   "2026-10-15T23:00:00Z",
    "createdBy": "ops",
    "comment": "内核升级维护窗口"
  }'

注意 startsAt 和 endsAt 必须是 RFC3339 的 UTC 时间,写成本地时间会差几个时区,静默窗口对不上,结果维护期间照样被吵醒——这个坑很常见。

amtool:命令行验收你的告警配置

配置写完了,别急着上线,用官方提供的 amtool 先做两件事:校验配置和模拟路由。

# 1. 校验配置文件语法(能提前发现缩进、字段名拼错)
amtool check-config /etc/alertmanager/alertmanager.yml

# 2. 模拟一条告警,看它最终会走哪个 receiver
amtool config routes test \
  --config.file=/etc/alertmanager/alertmanager.yml \
  severity=critical alertname=NodeDown instance=10.0.0.5:9100

第二条命令会打印出这条告警经过路由树后命中的 receiver 名字。这是排查「为什么我的严重告警走了邮件而不是短信」最快的方法——直接看它匹配到了哪个节点,而不是靠猜。

另外可以用 amtool alert query 查看当前所有正在 firing 的告警:

amtool alert query --alertmanager.url=http://127.0.0.1:9093

一套可以直接抄的个人站完整配置

把前面的片段组合起来,就是下面这份最小可用配置。它假设你只有一台 VPS 加一个站点,告警分 critical 和 warning 两级:

global:
  resolve_timeout: 5m
  smtp_smarthost: 'smtp.example.com:587'
  smtp_from: 'alert@example.com'
  smtp_auth_username: 'alert@example.com'
  smtp_auth_password: 'YOUR_TOKEN'
  smtp_require_tls: true

templates:
  - '/etc/alertmanager/templates/*.tmpl'

route:
  receiver: 'email-normal'
  group_by: ['alertname', 'instance']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - matchers:
        - severity = "critical"
      receiver: 'phone-sms'
      group_wait: 10s
      repeat_interval: 30m

inhibit_rules:
  - source_matchers:
      - alertname = "NodeDown"
    target_matchers:
      - severity =~ "warning|info"
    equal: ['instance']

receivers:
  - name: 'email-normal'
    email_configs:
      - to: 'me@example.com'
        send_resolved: true
  - name: 'phone-sms'
    webhook_configs:
      - url: 'http://127.0.0.1:8060/sms'
        send_resolved: true

配置改动后热加载,不需要重启进程(Alertmanager 支持 SIGHUP 重载):

amtool check-config /etc/alertmanager/alertmanager.yml
curl -X POST http://127.0.0.1:9093/-/reload

webhook 接收端:把告警接进你的机器人

个人站很多时候不想用短信,而是想接进企业微信、钉钉或者飞书群。Alertmanager 本身不内置这些渠道,做法是起一个中间的 webhook 小服务,接收 Alertmanager 推送的 JSON,再转发成机器人能识别的消息格式。核心就是配置里的 webhook_configs,它会把告警以固定 JSON 结构 POST 给你的服务:

{
  "version": "4",
  "status": "firing",
  "alerts": [
    {
      "status": "firing",
      "labels": {"alertname": "NodeDown", "instance": "10.0.0.5:9100"},
      "annotations": {"summary": "主机不可达"},
      "startsAt": "2026-10-15T22:10:00Z"
    }
  ],
  "groupLabels": {"alertname": "NodeDown"},
  "commonLabels": {"severity": "critical"}
}

你的 webhook 只要读 status 决定是「告警」还是「恢复」,再从 alerts[].annotations.summary 里取人话标题就够了。注意这里是 POST 请求,而且 Alertmanager 会等你的服务返回 2xx,超时就会重试,所以 webhook 服务必须轻量、快速返回,别在里面做耗时操作(比如同步调外部 API),否则会拖垮 Alertmanager 的推送队列。

最容易踩的五个坑

  • 忘记 equal 导致抑制过度:一台上游设备异常,把所有机器的告警都压掉了,出了事你完全不知道。所有抑制规则都要有 equal,且想清楚「相等」到底该在哪个标签上。
  • 路由没有兜底 receiver:如果某条告警没匹配到任何子路由,会走父节点的 receiver;但如果父节点也没写 receiver,Alertmanager 直接拒绝启动或丢告警。
  • repeat_interval 设太短:有人为了「不漏告警」设成 5 分钟,结果一条持续了一夜的告警发了 100 多遍,最后你在手机上把它静音了,等于自废武功。
  • 静默时间用了本地时区:API 只认 UTC,差 8 小时窗口就完全错位。
  • 没有 send_resolved:故障恢复了却没有「已恢复」通知,你还以为一直没修好,会白折腾很久。

小结

告警系统做得好不好,标准不是「能收到消息」,而是「出事时你能被准确叫醒,没事时它完全不打扰你」。Prometheus 负责发现异常,Alertmanager 负责把异常翻译成人能消化的一条条通知。把分组、路由、抑制、静默这四套机制按本文的思路配好之后,你会发现告警群从「天天狼来了」变成了「响一次就一定是真的有事」——这才是监控真正该有的样子。

Last modification:October 15th, 2026 at 01:24 pm

Leave a Comment