作者视角:15 年 SRE,长期盯 on-call 与可观测性。这篇文章不教你"怎么配告警",而是讲一个更被低估的问题——告警系统本身为什么会腐化到让你不敢信它,以及怎么把它治回来。全文给可复现的 Prometheus 规则、分类口径和一份整改清单。
一、现象与背景:on-call 同学开始对告警"条件反射式忽略"
先摆数字。我们一个 6 人值班组(3 后端 + 2 SRE + 1 数据),共用一条 PrometheusAlertmanager 通道,2025 Q4 审计告警流水:
- 每月告警事件 2147 条(均值 71 条/天,凌晨 3 点峰值 18 条/小时);
- 真正升级成 P2 及以上事故:1 起/月(一起 Redis 主从切换超时);
- 信噪比 ≈ 1 : 2000;
- 更刺眼的是:那起真事故从第一条告警到 on-call 拉群响应,耗时 23 分钟——不是没人看,是值班同学把它当成了"又一个磁盘 85% 预警",先标记了 ack,3 分钟后才意识到不对。
复盘会上一个 on-call 同学说了句大实话:"我现在收到告警第一反应是'关掉',不是'看看'。" 这句话比任何指标都说明问题——告警系统的价值不是"发出来了",而是"值得被响应"。当信噪比低于某个阈值(我们内部经验是 1:50),人的行为模式就从"排查"退化成"清通知"。
二、根因分析:告警腐化不是"配多了",是三条机制在自激
1)"可观测性"和"告警"被混为一谈。 团队里 60% 的告警规则是"把指标画出来 + 加个阈值"顺手配上的,写规则的人根本没想过"这条告警响了,我今晚要干什么"。告警的正确定义是**"需要人工介入的事件"**,而不是"指标偏离了"。CPU 90% 本身不是事故,"CPU 90% 且业务 P99 上涨"才是。
2)阈值是"拍脑袋 + 永不回看"的复合体。 抽了 40 条老规则审计,21 条的阈值注释里写的是"参考网上经验值",0 条有"上次调整时间"和"调整依据"。阈值不是配置,是假设——假设业务量、假设硬件规格、假设依赖关系,任何一个变了,假设就失效,但规则不会自己更新。
3)没有"告警 owner",改规则的动机为零。 一条规则响了 300 次没人处理,它不会自动下线;反过来,一条规则响了但每次都误报,也没有人负责把它修掉。告警规则和代码一样需要 owner、需要 review、需要下线流程,但我们从来没有这套治理。
这三条叠加形成一个正反馈:告警越多 → 人越忽略 → 越没人改规则 → 误报越多 → 越没人信。这不是一个"技术配置问题",是一个"运营治理问题"。
三、具体做法:四步把信噪比从 1:2000 治回 1:14
步骤 1:给所有告警打三档标签(1 天)
# alertmanager 路由按 severity 分流
route:
routes:
- matchers: [severity="page"] # 打电话 + IM @值班
receiver: pagerduty
- matchers: [severity="ticket"] # 只进工单,不打扰
receiver: jira
- matchers: [severity="info"] # 只进 dashboard,不通知
receiver: dashboard-only
page 级只允许 3 类:SLO 错误预算 burn rate 触线、核心链路可用性下降、数据一致性破坏。其他一律降级到 ticket 或 info。这一刀切下去,page 级告警量直接砍掉 73%(从 2147 降到 578)。
步骤 2:每条 page 告警必须带"runbook 链接"(3 天)
- alert: RedisFailoverStuck
expr: redis_master_up == 0 and (time() - redis_master_last_failover) > 300
for: 2m
labels: { severity: "page" }
annotations:
summary: "Redis 主从切换卡住超过 5 分钟"
runbook: "https://wiki/runbook/redis-failover-stuck" # 必填,否则 CI 拒绝合入
action: "1) 检查从库复制延迟 2) 手动 failover 命令 3) 15 分钟未恢复升级 P1"
我们加了一条 CI 规则:Prometheus 规则文件合入时,severity=page 但 runbook 字段为空的会被 lint 拒绝。逼着写规则的人想清楚"响了之后干什么"。
步骤 3:告警"沉默期"审计(每周自动跑)
# 每周一 cron 跑一次,找出"响了但 30 天内 0 次升级"的规则
promtool query instant \
'count by (alertname) (alerts_total{severity="page"})' \
--time-range=30d | \
awk '$3 == 0 {print $1}' > dead_alerts.txt
# 输出到值班周报,连续 2 周在名单里的规则自动降级为 ticket
这条规则上线第一个月,14 条 page 规则被降级,其中包括一条"JVM 线程数 > 500"的告警——它响了 4 个月,每次都是 GC 抖动,从没升级过。
步骤 4:SLO 错误预算 burn rate 替代"指标阈值"(2 周)
把"CPU > 90%"这类规则换成多窗口 burn rate:
- alert: SLOFastBurn
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
) > (10 * 0.001) # 10 倍错误预算,5 分钟窗口
for: 2m
labels: { severity: "page" }
核心思想:告警的触发条件从"某个指标超了"变成"错误预算在加速燃烧"。这样 CPU 90% 但业务正常 → 不告警;CPU 60% 但 5xx 翻倍 → 告警。告警和业务影响挂钩,而不是和硬件指标挂钩。
效果(上线 6 周后)
| 指标 | 治理前 | 治理后 | |---|---|---| | page 级告警/月 | 2147 | 152 | | 真事故漏响应次数 | 2 | 0 | | 平均首响时间 | 23 min | 4 min | | 值班同学主观"信噪比满意度"(1–5) | 2.1 | 4.3 |
四、踩过的坑(反直觉的地方)
坑 1:把"告警数量降下来"当 KPI 是危险的。 我们第一版治理方案把目标定成"月告警数 < 500",结果有同学为了达标,直接把 3 条真 page 规则降级到 ticket——数字好看了,风险也藏起来了。正确指标应该是"page 级告警的准确率"(响了且需要响应的比例),而不是"数量"。
坑 2:runbook 不是"写一份文档",是"可执行的操作序列"。 第一版 runbook 写的是"检查 Redis 状态",值班同学打开文档还是不知道第一步敲什么命令。runbook 必须到"复制粘贴能跑"的颗粒度,否则等于没写。
坑 3:多窗口 burn rate 的"慢燃烧"窗口容易被忽略。 我们只配了 fast burn(5 分钟窗口),结果一次持续 6 小时的小幅 5xx 上涨(每分钟 0.3%,单看 5 分钟窗口不触线)没被 page 到,最后靠用户投诉才发现。必须配 long burn(6h / 3h 双窗口),它抓的是"慢性失血",fast burn 抓的是"急性出血"。
五、结论与可复用清单
核心结论:告警系统的腐化是运营问题,不是配置问题。 没有 owner、没有下线流程、没有"响了之后干什么"的强制回答,任何告警系统都会在 3–6 个月内退化到"不敢信"的状态。
可复用清单(按优先级):
- 给所有告警打 severity 三档标签,page 级只允许 SLO / 可用性 / 数据一致性三类,其余降级(1 天见效);
- page 级告警强制带 runbook 链接,CI lint 拒绝无 runbook 的合入(3 天);
- 每周自动审计"响了但 0 次升级"的规则,连续 2 周自动降级(1 周);
- 用多窗口(fast + long)burn rate 替代单指标阈值(2 周,需要 SLO 先定义好);
- 治理指标用"page 准确率"而非"告警数量",避免为了数字好看而藏风险。
留一个开放问题给各位:你们团队有没有一条"响了 3 个月、每次都被 ack 掉、但没人敢删"的告警?它现在是不是你的隐形 P0?你怎么判断"删掉它"和"留着它"哪个风险更大?—— 这个问题的答案,可能比任何告警规则都值钱。