【掘金摘要】3.18 那次 47 分钟没人发现的故障之后,我把 243 条 Prometheus 告警规则砍到 27 条,日均告警从 47 次降到 3 次,MTTD 从 47 分钟压到 4 分钟,本文是完整的 5 步操作记录,脚本和配置可直接抄。
先交代背景。3 月 18 号晚上我们出过一次故障:下单接口成功率从 99.94% 掉到 91.2%,跑了 47 分钟才被发现------不是告警发现的,是客服在群里喊人。事后复盘发现系统里躺着 243 条告警规则,真正会响的 11 条,故障当晚响的 3 条全是噪音(磁盘路过 78%、证书 45 天后到期、一个 pod 重启又自己好了)。
值班同学不是不负责。是你天天给他推 40 多条垃圾短信,他迟早学会"先看一眼不用管"。等真那一条混进来的时候,待遇跟前面 240 条一样。
这篇文章不复盘观点,只讲我之后花两周做的降噪实操。目标三个:日均告警降到个位数;补上"成功率跌破红线必须响"这类 SLI 告警;建立防复发的清理机制。环境是 Prometheus 2.45.0 + Alertmanager 0.26.0,规则都在 Git 仓库里管着。
第一步:先数数,别拍脑袋
动手砍之前,我拉了一周的数据,回答一个问题:哪条规则响过几次?
Prometheus 的 /api/v1/rules 只给当前状态,历史触发记录要从 Alertmanager 的 API 拿。写了个脚本按 alertname 聚合:
python
# count_alerts.py 统计近7天每条规则的触发次数
import requests, collections
AM = "http://alertmanager:9093"
end = int(time.time()); start = end - 7 * 86400
resp = requests.get(f"{AM}/api/v2/alerts",
params={"active": "false", "silenced": "false"})
counts = collections.Counter()
for a in resp.json():
ts = a["updatedAt"] # 仅作近似,见下文注意事项
counts[a["labels"].get("alertname", "unknown")] += 1
for name, c in counts.most_common():
print(f"{c:5d} {name}")
如果你的 Alertmanager 没开持久化(默认就没开),历史数据拿不全,那就用兜底方案:Grafana 里建个面板,count_over_time(ALERTS{alertstate="firing"}[7d]) 按 alertname 聚合,直接看一周趋势。ALERTS 这个内置指标每个触发实例都会记一条,够用了。
数完结果很难看:243 条规则里,一周触发 0 次的有 178 条,一周触发超过 20 次的有 9 条------这 9 条贡献了全部告警量的 76%。中间地带只有十几条。也就是说,这套体系里绝大部分规则是死字,少数规则在疯狂刷存在感。
第二步:三档分拣,砍掉两头
拿到数据后我把 243 条规则分成三档,逐条过:
杀掉(178 条):一周零触发,且阈值明显脱离现实。典型的就是那条 CPU 85% 持续 10 分钟------我们那批机器负载从没上过 70,这条规则存在的意义就是让规则数好看。判断标准很简单:问自己"这条响了之后我上一次真的去处理是什么时候"。答不上来就删。
改造(38 条):频繁触发但确实反映真实波动的,别删,改触发条件。两种改法:
yaml
# 改法1:拉长观察窗口,毛刺不报,持续才报
- alert: HighCPU
expr: avg(rate(node_cpu_seconds_total{mode!="idle"}[5m])) by (instance) > 0.85
for: 15m # 原来 10m,改成 15m 后误报砍半
labels: {severity: P2}
# 改法2:跟自身基线比,不跟绝对阈值比
- alert: QPSAbnormalDrop
expr: |
sum(rate(http_requests_total[5m]))
< 0.6 * avg_over_time(sum(rate(http_requests_total[5m]))[1h:5m])
for: 5m
labels: {severity: P1}
保留(27 条):杀完改完剩下的。原则是每条留存的规则必须满足:响了有人看、看了有动作、动作有文档。三样缺一样,下次清理继续砍。
这里有个取舍要说清楚:砍规则砍掉的不是"监控覆盖",是"看起来覆盖"。真正缺的那块覆盖(成功率红线)后面第四步会补回来。
第三步:Alertmanager 分级路由,P2 不许进值班群
27 条规则如果全走一个渠道,噪音还是会回来。降噪的另一半在路由层:
yaml
route:
receiver: p2-weekly
group_by: [alertname, instance]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- match_re:
severity: "P0"
receiver: p0-phone # 电话/短信,凌晨也叫
group_wait: 0s
repeat_interval: 30m
- match_re:
severity: "P1"
receiver: p1-oncall # 值班群
repeat_interval: 2h
- match_re:
severity: "P2"
receiver: p2-weekly # 邮箱汇总,每周一发周报
repeat_interval: 24h
关键动作是把原来混在值班群里的 P2 全部踢出去,改成邮件周报。我们统计过,改造前值班群日均 47 条,里面 41 条是 P2 级别的"知道了也没用"。踢出去之后值班群一天最多三四条,每条都值得点开。
repeat_interval 也很关键。原来有个磁盘告警每 2 小时重复一次,一晚上同一条响 4 次。改成 4h 之后,同一问题一天最多打扰两次,配合 group_by 合并同源告警。
第四步:补回真正该响的那条
前面都在做减法,这步是加法,也是最不能省的一步------3.18 那晚缺的就是它:下单成功率跌破 91.2%,没有任何一条规则响。
别上来就抄 Google 的 multi-burn-rate 全家桶,告警规则越复杂越容易变成新的噪音源。我们先上简化版:
yaml
- alert: OrderSuccessRateLow
expr: |
sum(rate(order_total{status="success"}[5m]))
/ sum(rate(order_total[5m])) < 0.995
for: 3m
labels: {severity: P0}
- alert: HighErrorRate5xx
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m])) > 0.01
for: 3m
labels: {severity: P0}
这两条上线后立刻加了一轮回归验证:找测试环境人为制造 5xx,确认电话打得进来、值班群 @ 得到人。告警链路本身也要测试,这个共识普及度比想象中低。
SLI 类规则控制在个位数,只放下单、支付、登录这些核心接口,再加就该克制了。
第五步:月度清理脚本,防止长回来
降噪最大的敌人是时间------半年后规则又会一点点长回来,就像没人记得 3.18 一样。所以最后一步是机制:每月 1 号跑一次清理脚本,输出"触发次数 top20 + 零触发规则清单",进月度例会必看。
bash
#!/bin/bash
# alert_report.sh 每月输出告警健康度
PROM="http://prometheus:9090"
echo "== 近30天各规则触发次数 =="
curl -s "$PROM/api/v1/query" \
--data-urlencode \
'query=topk(20, sum(count_over_time(ALERTS{alertstate="firing"}[30d])) by (alertname))' \
| jq -r '.data.result[] | "\(.value[1]) \(.metric.alertname)"'
echo "== 零触发规则数(对比在册规则总数)=="
curl -s "$PROM/api/v1/rules" | jq '[.data.groups[].rules[]] | length'
挂个 crontab:0 9 1 * * /opt/scripts/alert_report.sh | mail -s "告警月报" devops@team.com。机制不用重,存在就行------有个数字每个月被看一眼,规则就烂不回去。
效果对比
两周改完,跑了 60 天后的数据:
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 告警规则总数 | 243 条 | 27 条 |
| 日均告警推送 | 47 次 | 3 次 |
| 3.18 类故障发现方式 | 客服喊人(47 分钟) | P0 电话(4 分钟) |
| 值班群信噪比 | ~12%(3/47) | ~100% |
补充一个意外收获:规则少了之后,新人值班的上手时间明显缩短------原来看不懂 243 条规则各管什么,现在 27 条一下午能讲完。
注意事项
- 砍规则前先归档再删 。我们把 216 条下线规则挪到
rules/archive/目录留在 Git 里,谁哪天想恢复有据可查,别直接 rm。 - Alertmanager 默认不持久化触发历史 ,靠
ALERTS指标统计要注意:规则被删后历史数据也就没了,所以月报脚本要趁规则还在的时候跑。 - P0 电话渠道要单独演练。我们第一次真实触发时发现电话网关的配额只够 10 通/月,还好是在演练里发现的。
- 别追求一次到位。multi-burn-rate、分级 SLO 那套可以后置,先把死规则清掉、路由分好层,收益已经能拿走八成。
改动清单都在仓库里,243 条删 216 条这种事,评审记录就是最好的背锅侠------不对,是知识资产。
你们现在的告警规则有多少条?上次真响是什么时候?评论区聊聊,看看谁的信噪比更惨。
觉得有用点个关注,每周五更新一篇 DevOps 实战,全是踩过坑换来的。