告警降噪实战:把 412 条 Prometheus 告警规则砍到 67 条,P1 故障反而少了

🔑 关键词:告警降噪,Prometheus告警规则,Alertmanager配置,运维工程师,MTTR

📖 摘要:一个 6 人运维团队的真实降噪记录:412 条 Prometheus 告警规则如何砍到 67 条,日均告警从 900+ 条降到 47 条,P1 故障从每天 3.2 次降到 0.4 次。包含具体的 for 时长阈值、Alertmanager 分组与抑制配置、分级路由标准和三个反直觉的发现。

先说结论:不是告警太少,是太多

图片

2023 年 9 月我接手了一个 6 人运维团队维护的 SaaS 系统。规模不算大:3 套 K8s 集群(prod / staging / dr),58 台节点,140 多个微服务,Prometheus 2.45,Alertmanager 0.26,跑在阿里云 ACK 上。

接手第一周就发现一件怪事:企业微信告警群一天能刷 900 多条消息,但真出故障那天,没人第一时间看到——因为所有人都把群设成了消息免打扰。

我拉了过去 30 天的告警数据,就一句 PromQL:

count by (alertname) (ALERTS{alertstate='firing', alertname!=''})

Top 10 的规则贡献了 78% 的告警量。第一名 NodeCPUHigh,30 天触发 4200 多次,平均每天 140 次。第二名 PodRestart,3800 多次。第三名 NodeMemoryHigh,2700 多次。

而这几个规则,一条都不是我们自己写的,全是从某个开源 helm chart 的默认 values 里带出来的。


图片

根因不是规则多,是「加规则没有成本」

我把 412 条规则按来源统计了一遍:

来源 数量
Helm chart 自带默认规则 约 180
前同事从 Grafana 模板抄的 约 90
自己手写的 约 140

真正的问题有三个,按严重程度排:

1. for 字段基本是 0m。 Prometheus 的 for 默认就是 0,意思是瞬时值一越界立刻 firing。CPU 抖一下、JVM 卡一次 Full GC、网络重传一个包,全都算。我查了下 NodeCPUHigh 的原始表达式,是 100 - avg(irate(node_cpu_seconds_total{mode='idle'}[5m])) * 100 > 80,5 分钟窗口加 80% 阈值,在任何一台跑批的机器上都必然触发。

2. Alertmanager 没有分组。 route 里的 group_by 当时是空的,等于每条告警单独发一条消息。一个节点宕机,kubelet、node-exporter、cadvisor,再加上上面跑的 20 个 Pod,能连发 30 多条。所谓「告警风暴」就是这么来的。

3. 没有抑制规则。 主库挂了,从库、应用、网关、前端一起炸,200 条告警同时冲进来,值班的人根本不知道先看哪个。

这三条其实指向同一件事:加一条告警规则的成本几乎是零,删一条的成本却很高——没人敢删,万一出事呢?于是规则只增不减,三年下来就变成 412 条。

图片


我实际做的五步(可以直接抄)

第一步,先量化,别急着删。

我用上面那句 PromQL 拉 30 天数据导出成 CSV,加了三个维度:触发次数、平均持续时长、P1 占比。触发次数大于 1000 且平均持续时长小于 5 分钟的,基本可以直接判定为噪音。这一轮筛出来 180 多条。

第二步,给每条规则补 for。

具体阈值我是这么定的:

  • 资源类(CPU / 内存 / 磁盘使用率):for: 10m
  • 应用类(错误率、P99 延迟、队列积压):for: 5m
  • 可用性类(up == 0、探针失败):for: 1m
  • 真正致命且不可逆的(证书 7 天内过期、磁盘 24 小时内会写满、主从延迟超 300s):for: 0m

图片

这里有个坑:for 太长也不行。我们有条磁盘规则设了 for: 30m,结果有台机器从磁盘写满到宕机一共才 22 分钟,告警压根没发出来。所以 for 的值应该跟「从异常到不可逆」的时间窗挂钩,不能一刀切。

第三步,Alertmanager 分组。

route:
  group_by: ['alertname', 'cluster', 'service']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 12h

group_by 从空改成三个维度,光这一项就让告警消息量掉了大约 40%。repeat_interval 从默认的 4 小时改大到 12 小时(P1 级别保持 4 小时),避免了「一个没人修的告警每小时提醒你一次」这种折磨。

第四步,分级路由。

  • P1(打电话 + 群内 @所有人):最后只留了 12 条,都是年可用性、核心交易链路、数据一致性相关的
  • P2(群消息,不 @):34 条
  • P3(只进 Grafana 看板,不推送):21 条

判断标准就一句话:这条告警响了,我要做的具体动作是什么? 说不出来的,降级或者删掉。

图片

第五步,加抑制规则。

inhibit_rules:
  - source_matchers: [severity='critical']
    target_matchers: [severity='warning']
    equal: ['cluster', 'service']
  - source_matchers: [alertname='NodeDown']
    target_matchers: [alertname=~'Pod.*|Container.*']
    equal: ['instance']

第二条的意思是:节点都挂了,上面 Pod 的告警就不用再发了。这一条大约又消掉了每天 60 条消息。


结果,以及三个我没预料到的发现

数字上:规则 412 条到 67 条。日均告警消息 900 多条到 47 条。P1 每天平均 3.2 次到 0.4 次。MTTR 从 47 分钟降到 19 分钟。这里得说清楚,同期我们还上了链路追踪,不好完全归因给告警降噪,但至少占一半。

真正有意思的是另外三件事。

第一,删掉告警之后,我们「发现」的故障反而变多了。 以前 900 条消息里,真正的问题是被淹掉的。降到 47 条之后每条都有人看,反而翻出来一个跑了快两年的内存泄漏——它就藏在那 3800 次被忽略的 PodRestart 里。

图片

第二,告警降噪最大的收益不是省时间,是让「改监控」这件事变得可行。 规则多的时候谁都不敢动;规则少而且每条都有明确动作之后,加一条新规则反而要走评审。数量下去了,质量上来了。

第三,被砍掉的 345 条规则没有真的消失。 大概 200 条转化成了 Recording rules 或者 Grafana 面板,只在排查的时候看。剩下 145 条是真删了。这里我得承认,有几条删得有点激进,两个月后补回来三条。


一点私人的看法

现在很多团队把「监控覆盖率」当 KPI,我觉得方向就错了。覆盖率从 60% 提到 95%,边际收益可能是负的——多出来的那 35% 里,大半是永远不会有人看的噪音。

另一个感受:这套方法在小团队好用(6 个人、140 个服务),服务数超过 300 个可能就得换成 SLO 驱动那套,用错误预算来决定什么时候告警。我们试过 Google SRE 的四黄金指标,说实话在小体量下算不明白,错误预算烧得太慢,一个月都不带动的,反而没人当回事。

干运维八年,我越来越觉得核心能力不是「什么都能监控」,而是「知道什么可以不用看」。前者靠堆工具,后者靠的是对业务的理解——这玩意儿,买不来。

🏷️ 标签: