一次Prometheus告警风暴拖垮邮件与企业微信告警通道的排查实录
一、问题背景
周四下午 14:20 左右,公司内部运维群突然开始”刷屏”——每秒钟都有十几条告警消息从企业微信机器人推出来,密密麻麻的红色 ERROR 几乎把群聊淹没。我当时正在处理一个普通的工单,看到群里”炸了”的第一反应是某个核心服务挂了,但仔细扫了几条告警内容后又觉得不对:大量”Redis 慢查询”、”接口 P99 突增”、”磁盘 IO 升高”这种零散的二级告警,没有一条提到核心业务异常。
更诡异的是,我试着登录 Grafana 看核心仪表盘,结果返回 502;打开邮件客户端想看原始告警,登录后邮件列表一直转圈加载不出来。这一刻我才意识到——告警本身把告警通道打爆了。
我们的监控栈是典型的”Prometheus + Alertmanager + 企业微信/邮件”组合,部署在 2 节点的 VM 上,Prometheus 单实例抓取大约 280 个 target,覆盖 12 套业务系统。按理说应该是个很稳的架构,那天下午发生的事情彻底改变了我们对”告警治理”的认知。
二、故障现象
故障爆发后,我开始从外到内逐层观察,记录下的关键现象如下:
1. 企业微信群告警刷屏
群机器人从 14:20 开始以大约 12 条/秒的速率推送告警,告警内容主要分两类:
HighRequestLatencyP99 延迟大于 1s(业务接口 50+)RedisSlowQuery慢查询大于 100ms(Redis 实例 12+)ContainerCPUThrottledCPU 限流(Pod 30+)NodeDiskIOHigh节点磁盘 IO 高(节点 8+)
但所有告警的 severity 标签都是 warning,没有任何 critical 级别的告警出现。
2. 邮件系统卡死
公司内部邮箱(基于 Postfix + Dovecot 自建)登录后无法加载新邮件,运维组的公共收件箱 [email protected] 在 10 分钟内收到了 18,742 封邮件。我 ssh 到邮件服务器上看了一眼:
1 | $ postqueue -p | tail -5 |
队列里堆了 1.8 万封待发邮件,Postfix 的 qmgr 进程 CPU 占用飙到 380%(多核),active 队列达到 smtp_destination_concurrency_limit 的上限。
3. Grafana 不可达
通过 curl -I http://grafana.internal:3000 测试返回 502。Grafana 后端依赖的 Prometheus 数据源因为查询请求堆积而响应缓慢,Grafana 默认的超时时间是 30 秒,大量仪表盘请求堆积导致前端一直 502。
4. 真正致命的故障被淹没
事后复盘发现,在 14:15 左右(也就是告警风暴前 5 分钟),核心订单数据库的主从复制其实已经中断,从库 Seconds_Behind_Master 飙到 NULL,意味着 IO 线程已经停了。但因为告警风暴期间 Alertmanager 的处理队列被压垮,这条 critical 级别的告警根本没有被发送出来。最终这个故障是业务部门反馈”订单数据不准”后我手动查数据库才发现的,业务受影响时长接近 2 小时。
三、排查过程
我按”告警链路 → 告警源头 → 告警通道”三段式展开排查,整个过程大约用了 40 分钟。
3.1 紧急止血:先让告警通道活过来
第一件事是阻止告警继续刷屏。我直接 ssh 到 Alertmanager 节点,先停掉企业微信机器人的 webhook 投递:
1 | $ systemctl status alertmanager |
然后去邮件服务器上把堆积的队列清掉,保留最近的 200 封邮件作为证据:
1 | # 查看队列 |
止血操作大约花了 5 分钟,群里的刷屏停了,邮件系统开始慢慢恢复。Grafana 在 2 分钟后也能正常打开。
3.2 查告警源头:为什么这么多告警同时触发?
止血之后开始查根因。打开 Alertmanager 的 Web UI(http://alertmanager:9093),在 Alerts 页面看到 Active 状态的告警有 2,347 条。这显然不正常——我每秒钟大概只有 5~10 个真实异常指标会被触发,绝大部分都是抖动。
我抽样了几条告警,看它们的 Active Since 和 Labels:
| 告警名称 | 触发数 | Active Since | severity |
|---|---|---|---|
| HighRequestLatency | 487 | 14:18:42 | warning |
| RedisSlowQuery | 156 | 14:19:03 | warning |
| ContainerCPUThrottled | 312 | 14:19:18 | warning |
| NodeDiskIOHigh | 88 | 14:19:31 | warning |
所有告警的 Active Since 都集中在 14:18~14:19 这 1 分钟内。这说明不是”业务真的出了这么多问题”,而是某个时间点发生了一次”集体抖动”。
接着我去查 Prometheus 的告警规则文件 /etc/prometheus/rules/,发现一个被忽视的配置:
1 | # rules/business.yaml |
for 字段是告警持续多久才真正发送的关键参数。我之前为了”新告警规则快速生效”,把 for 字段都删掉了,意图是”指标一超标就告警”,结果就是任何一次 1~2 秒的网络抖动、一次 GC 暂停、一次容器重启,都会被立刻当成告警发送。
我去查了那 1 分钟内发生了什么事,从 Kubernetes 事件里看到:
1 | $ kubectl get events --sort-by=.lastTimestamp | head -20 |
14:18 左右,业务团队发布了一版新服务,Deployment 滚动更新导致 30+ Pod 同时重启、镜像拉取、初始化,Prometheus 在这 1 分钟内采集到了大量瞬时高 CPU、高延迟、慢查询指标,所有缺 for 的告警规则同时被触发。
3.3 查 Alertmanager 配置:为什么告警没有被合并?
光解释清楚了告警源头还不够——为什么 2347 条告警没有按”业务系统”、”告警类型”分组到一起,而是被一条条推出去?
打开 /etc/alertmanager/alertmanager.yml,看到路由配置是这样的:
1 | route: |
问题一:group_by: ['alertname'] 意味着所有同名告警会被分到一组,但每条告警的 service、pod 标签还是独立的,会作为多条记录在组里。所以 HighRequestLatency 告警虽然合并成了 1 条群消息,但群消息里包含 487 个 service 实例,消息体超大,企业微信 API 直接报错。
**问题二:group_interval: 10s**,意味着 Alertmanager 每 10 秒就重新评估并发送一次”组更新”消息。结果就是这 2 分钟内,Alertmanager 给企业微信机器人推送了 12 次/秒 的频率。
问题三:企业微信机器人限流,官方限制是每分钟最多 20 条消息。当 Alertmanager 推得比限流还快,HTTP 429 响应导致消息被丢弃,但 Alertmanager 的 retry 机制又会重试,进一步加剧了堆积。
问题四:邮件更是无差别全发,Postfix 没有针对 [email protected] 这个收件人做 rate limit,所以 1.8 万封邮件在 10 分钟内全部进队列。
3.4 查根因告警:为什么 critical 告警没发出来?
这是最让我后怕的部分。我去 Alertmanager 的日志里翻:
1 | $ journalctl -u alertmanager --since "14:00" | grep -i "notify" |
queue full, drop 才是关键。Alertmanager 的内部通知队列是有上限的(默认 10000 条),当它自己处理不过来时,新来的告警直接被丢弃。也就是说,critical 的 DB 告警不是被”挤到后面”,而是被”丢掉”了。这是最严重的告警可靠性问题。
四、解决方案
针对上面四个层面的问题,我做了一次系统性的整改。
4.1 告警规则层:加 for 持续时间和分级
把所有 warning 级别的告警都加上 for 字段,按业务特征选择不同持续时间:
1 | # rules/business.yaml(整改后) |
注意 expr 里的 rate 窗口也从 1m 改成了 5m,进一步平滑瞬时抖动。
4.2 Alertmanager 层:精细化路由 + 抑制 + 分级
重写 alertmanager.yml,核心改动:
1 | global: |
4.3 告警通道层:企业微信分群 + 邮件限流
企业微信机器人的限流是硬性 20 条/分钟,靠 Alertmanager 自己节流不够,必须在通道层做隔离:
- 创建 3 个机器人:critical、business、default,各自独立 webhook
- 创建 3 个群:核心告警群(仅 critical)、业务告警群(warning+)、综合告警群
- 邮件也分收件人:
ops-critical@、ops-business@、ops-default@,分别给 oncall 工程师、业务负责人、邮件组订阅
邮件系统层面,给 Postfix 加上发件限流:
1 | # /etc/postfix/main.cf |
4.4 可靠性层:告警持久化 + 自监控
为了防止”告警队列满后丢告警”再次发生,做了三件事:
- 启用 Alertmanager 集群模式:部署 3 个 Alertmanager 节点,Gossip 同步告警状态
- 加告警自监控:在 Prometheus 里配置一个 meta-alert,监控
alertmanager_notifications_failed_total是否在涨、alertmanager_notification_queue_capacity是否接近满 - 关键告警双通道:critical 告警同时走邮件 + 钉钉 + 电话,缺一不可
五、根因分析
事后复盘总结,根因有四个层面,由浅到深:
1. 告警规则设计缺陷(最直接)——所有 warning 告警缺失 for 持续时间,把”瞬时抖动”当成”持续故障”处理,这是导致 2347 条告警同时触发的直接原因。for 是 Prometheus 告警体系里最容易被忽略、但又最关键的一个参数。
2. Alertmanager 分组策略过粗——group_by: ['alertname'] 看似在合并,实际只是合并了告警名,每条告警的实例标签还是作为多条独立记录存在,结果就是”组很大、消息很胖”。企业微信 API 对单条消息体大小是有限制的(20KB 左右),超长消息会被截断甚至拒绝。
3. 告警通道无分层——所有 severity 都走同一通道,告警风暴时 critical 和 warning 互相挤占资源,企业微信 20 条/分钟的限流被 warning 消息吃光,critical 消息根本发不出去。
4. 缺乏告警自监控——没有监控”告警系统本身是否健康”,导致 Alertmanager 队列满、丢告警这种”次生故障”完全不可见。这是最致命的,因为告警系统一旦失效,整个监控体系等于瞎了。
六、预防措施
针对这次故障,我梳理出 5 条长期预防措施:
1. 告警规则审计常态化
把告警规则检查纳入 PR Review 流程,CI 里加一个 promtool check rules 的检查,确保每条告警都有合理的 for 字段。同时每月做一次告警规则审计,清理长期不触发或永远在触发的”僵尸告警”。
2. 告警分级通道永久隔离
critical / warning / info 必须在 Alertmanager 配置里走完全不同的 receiver 和 webhook,禁止混用通道。任何 critical 告警必须同时配邮件 + 至少一种 IM 通道,且 receiver 配 send_resolved: true 确保恢复时也能收到通知。
3. 告警降级与抑制规则
完善 inhibit_rules,critical 告警触发时自动抑制同服务同 cluster 的 warning 告警,避免”父故障触发一堆子告警”。同时为不同业务系统配置”维护窗口”,在主动发布期间自动静音相关告警(用 mute_time_intervals)。
4. 告警系统自身监控
必须给监控组件本身也加监控,至少包括:
alertmanager_notifications_failed_total增长率alertmanager_notification_queue_size/queue_capacity比值prometheus_tsdb_head_series增长趋势prometheus_target_sync_failed_total
这四个指标任何一个出问题,本身就是 critical 告警。
5. 定期演练”告警风暴”
每季度做一次”告警风暴演练”——人为在测试环境触发一个能产生 1000+ 告警的故障,验证告警系统的承载能力、通道隔离效果、抑制规则是否生效。我后来在测试环境复现了这次的场景,发现 1000 条告警经过新配置后只发出去 23 条,群里完全可控。
七、总结
这次故障给我的最大教训是:告警系统的健壮性是 SRE 工作中最容易被忽视的一环。我们花了大量精力建设监控覆盖度,却很少花时间思考”告警系统本身挂了怎么办”。
几个值得长期践行的原则:
- 告警是产品质量,不是越多越好。一条没人看的告警比没有告警更糟,因为它会消耗人对告警的敏感度。
for字段是告警规则的灵魂。没有for的告警规则就是定时炸弹,迟早会在某次发布或重启时引爆。- 告警通道必须分层。critical 告警是”救命”的通道,绝不能和 warning 混用。
- 监控监控本身。这是 SRE 领域一个朴素的哲学问题——你必须能监控到你正在监控这件事,否则就是盲人骑瞎马。
故障后我们重写了所有告警规则和 Alertmanager 配置,并补齐了告警自监控。半年后再回头看,告警群里基本只剩下真正需要人介入的告警,告警疲劳感大幅降低,运维同事的”告警响应时间”也从平均 25 分钟缩短到了 6 分钟。投资在告警治理上的时间,绝对是回报率最高的那部分。