不知道你有没有经历过这种场面半夜告警群里突然开始刷屏几十上百条告警消息像子弹一样连成片弹出来值班工程师揉着眼睛一看源头其实只有一个——某台核心机器宕了其余全是它牵连出来的连锁故障。刚开始大家还绷着神经一条一条看到后面干脆麻木把群消息设成免打扰。真正的关键故障混在告警海洋里反而成了最后被注意到的那一个。告警策略如果只是停留在“能发出去”的阶段那Alertmanager充其量是个通知转发器它真正值钱的地方是那套告警降噪能力。这套能力全部藏在Alertmanager的配置里。今天我就用Prometheus生态最常见的组合把告警策略从路由、分组到抑制、静默逐个拆开讲清楚最后给出几套我踩过坑之后才沉淀下来的告警降噪最佳实践。不管你是在公司里刚搭好Prometheus、正被告警轰炸搞到失眠的运维新人还是已经在用Alertmanager但总觉得通知又乱又吵的“老手”这篇内容都能让你把配置文件改成看得见的效果。1. 告警链路拆解Alertmanager到底在什么位置1.1 从规则触发到通知送达一条告警要过几道关卡一条告警从产生到你手机上收到推送中间要经过好几道关卡。首先Prometheus按照rule文件里定义的表达式周期性地进行计算当结果持续满足阈值并且维持了for字段设定的持续时间后告警状态才会从pending进入firing。注意这个设计很关键它本身就是一个天然的低通滤波器用来拦截瞬时抖动带来的假告警。接下来Prometheus把处于firing状态的告警通过HTTP接口推送到Alertmanager。这里需要在prometheus.yml里显式声明Alertmanager的地址通常是alerting.alertmanagers配置段指向一个或多个host:port。很多人配置完之后发现告警没出来第一步就在这查Prometheus是不是真的把告警发出去了。Alertmanager拿到告警后并不会直接原样转发它要依次执行几件事先查静默规则看看这批告警是不是被计划内维护的窗口临时屏蔽了再走路由树匹配确定这批告警该去哪个接收者然后再跑抑制检查把那些已经被更高级别故障“代表”的衍生告警压下去。最后根据匹配到的接收者配置渲染通知内容通过Email、Webhook、Slack、PagerDuty等渠道发出去。任何一个环节断掉你看到的现象都是同一个该响的没响。所以排查告警问题时一定要有这个全局视角。我曾经见过一个团队Prometheus的Alertmanager地址写错了端口Prometheus那边的ALERTS指标都已经是firing了但因为推送失败告警压根没进Alertmanager最后折腾了一整天根因就是一行配置。链路思维越清晰排查方向就越准。1.2 别指望它替你写规则先说清Alertmanager的职责边界很多人刚开始用Alertmanager时会有一个误解觉得它应该负责生成告警。这是最需要扭转过来的认知Alertmanager不评估指标不判断阈值不决定“到底是不是有问题”。它只处理Prometheus已经判定为firing的告警。你完全可以把Prometheus和rule文件想象成哨兵Alertmanager则是那个坐镇后方的总指挥负责把战况整理成值得回报的消息。同样Alertmanager也不是历史告警存储。它的界面里能看到当前活跃的告警、静默记录但它不会长期保存已经解决的告警明细。想要做历史告警统计和趋势分析你得靠Prometheus里的ALERTS指标或者引入其他支持长期存储的组件。别指望在Alertmanager里找到“上周总共发了多少条”这种问题它的职责是处理后立即通知不是归档。把边界划清楚再回来看它的配置就清爽很多。你需要关心的核心配置块无非四个route、receivers、inhibit_rules以及通过UI或amtool管理的silences。route决定告警去哪里receivers决定怎么发inhibit_rules决定哪些告警要被闭嘴silences则是对部分告警临时按暂停键。接下来的几个部分我就按照这四个块逐个展开每块都配上实际场景解释比单纯贴一份默认配置有用得多。2. 路由与分组告警怎么找到人又怎么合并成一条2.1 路由树告警该发给谁由matchers和continue决定route是Alertmanager配置的灵魂它结构上是一棵树。最外层是根路由子路由放在它的routes列表下面子路由下面还可以继续嵌套子路由。Alertmanager拿到一条告警后从根开始自上而下匹配matchers匹配规则有几个必须记住的要点。第一子路由优先。如果某个子路由匹配上了就会使用这个子路由上定义的接收者如果它还有更深的子路由还会继续往下一层找。第二当所有子路由都没有匹配成功时会回退到当前这个父路由的接收者。这个回退机制很容易踩坑很多人只在根路由上写了receiver又在下面挂了两三个子路由匹配不同团队结果没匹配到任何子路由的告警最终还是从根路由发出来看起来就像“我明明没想让所有人收到怎么还是发了”。实际上逻辑没有错只是你没意识到回退的存在。第三continue字段决定了告警匹配到一个子路由之后是停下来还是继续找下一个平级路由。默认是false也就是匹配到就算数后面的平级路由不再看。如果你希望同一条告警既发给值班组又发给对应业务线就在子路由上开continue: true。这东西使用要克制开得越多重复通知越多降噪效果就越差。下面是我实际在用的一个路由骨架route: group_by: [alertname, cluster] group_wait: 30s group_interval: 5m repeat_interval: 4h receiver: default routes: - matchers: - severity critical receiver: pager continue: false - matchers: - team infra - alertname ~ NodeDown|DiskFull|HighCPU receiver: infra-webhook新版Alertmanager推荐用matchers用表示等于~表示正则匹配。旧版配置里的match和match_re字段不要再用迟早要迁移。另外路由树的层级不要堆得太深生产环境一般两层到三层就够层级越多排查“这条告警为什么发给了A而不是B”的难度就越大。2.2 分组参数把散弹枪的子弹装进同一个信封告警分组是降噪的绝对核心。可以把它理解成寄快递group_by就是按什么地址打包group_wait是门口集合等人齐的时间group_interval是发出下一批包裹的间隔repeat_interval是包裹送达之后每隔多久再催你一次。group_by是最容易影响告警量的一个参数。假设有10台机器的进程都因为一台数据库宕机而失败Prometheus会生成10条ProcessDown告警。如果group_by: [alertname]这10条告警会合并到同一个组里最终发送一条通知里面列出所有受影响的实例如果你把instance也放进group_by每台机器就会各自成组10条告警拆成10组告警群瞬间被刷屏。有人为了看到每台机器的明细把instance加进group_by结果反而亲手制造了一场小型告警风暴。group_wait默认30秒意思是当一个新分组里出现第一条告警后先等30秒再发送目的是把差不多同时到达的同类告警凑在一起发一条合并通知。如果你的网络抖动频繁想进一步聚合同一波短促故障可以适当调到1到2分钟但别设得太长否则关键告警会延迟很久才到人手里。group_interval默认5分钟它控制的是分组内出现新告警时的发送频率。比如组里已经有3条告警第5分钟又进来1条如果此时距离上次通知超过group_interval就会再发一条合并更新。这个值太短会频繁骚扰太长则可能导致组内新告警信息滞后。repeat_interval默认4小时它负责处理那些一直没恢复、组内也没有新增告警的情况。每过4小时Alertmanager会重新把当前组内的告警汇总通知一遍相当于“提醒你还挂着这个事”。我建议值班制团队至少从4小时起步重要告警可以缩短到1到2小时但不要设成30分钟否则你会被同一条告警反复磨到麻木。参数整理成表格更直观参数默认值作用经验取值group_byalertname按哪些标签合并告警组alertname加业务维度如cluster、appgroup_wait30s新组等待时间用于聚合同波告警30s到2mingroup_interval5m组内新告警再次通知的最小间隔5m到10mrepeat_interval4h组内无变化时重复通知的周期4h起步重要告警1到2h2.3 接收者与模板通知内容决定人要不要理你receivers这一块没有太多玄学但选型和配置直接影响告警是否真正被看见。除了Email、Slack、PagerDuty这些内置类型国内团队用得最多的其实是webhook通过它对接飞书、钉钉、企业微信的机器人。Alertmanager负责把选中的告警渲染成一条结构化消息POST到Webhook地址后面的人员、跳转链接、自动建单交给中间层去处理。我建议所有接收者都打开send_resolved: true否则告警恢复了人还不知道值班人员只能一遍遍刷新页面确认。配置大概长这样receivers: - name: infra-webhook webhook_configs: - url: http://172.16.0.10:8080/hook send_resolved: true通知模板这件事很容易被忽略但它决定了告警到达手机后人能不能在10秒内判断出该不该立刻处理。我要求团队在Prometheus规则的annotations里至少写明三件事这个故障影响的是什么、你现在该去哪里查看现场、完整的runbook文档链接。模板用Go template语法渲染核心思路是summary必须用一句话说清故障对象和影响description才用来展开细节。别把一坨日志全文塞进告警消息里手机屏幕放不下人也根本不会读。3. 抑制与静默把噪音告警掐在源头3.1 抑制规则让连锁告警闭嘴抑制解决的是“这个告警已经被另一个告警代表”的问题。最经典的场景是主机宕机一台机器挂了上面的所有服务自然不可用于是ProcessDown、PortDown、HTTP5xx高这些告警会一起冒出来。如果全都发到群里值班人看到的是一堆重复信息真正的根因反而被淹没。inhibit_rules就是为了掐断这条连锁反应链而存在的。它的逻辑是当存在一个满足source_matchers的活跃告警时那些满足target_matchers的告警会被暂时压住除非两者在equal字段指定的标签上值不同。我这里有一个真实的配置示例inhibit_rules: - source_matchers: - alertname NodeDown - severity critical target_matchers: - severity warning equal: - instance意思是只要某台机器上有NodeDown且级别是critical的告警在活跃状态那么同一台实例上所有severitywarning的告警先不通知。equal: [instance]保证了抑制只影响同一台机器不会因为跨实例把所有机器都压掉。这里最容易犯的错就是equal字段里的标签名对不上。比如你在某个告警规则里把机器标识写成host在另一个规则里写成instance哪怕它们语义上都是同一台机器抑制也不会生效因为标签字面值就不一样。标签命名标准化不只是为了好看它是抑制规则能不能工作的前提。还有一个细节抑制的源和目标都必须是活跃告警。如果源告警恢复了被抑制的目标告警如果还在firing会恢复通知。另外抑制只作用于通知阶段不代表告警消失了你在Alertmanager UI上依然能看到这些告警处于suppressed状态。这正是降噪的正确姿势消息层面藏起来事实层面留痕。3.2 静默给计划内维护按暂停键静默和抑制是两回事。抑制是自动规则静默是人工临时操作。最常见的用法是计划内维护你凌晨要重启机器升级内核提前在Alertmanager里挂一条针对这台机器的静默时间窗口内它的告警全部不通知避免半夜把值班人喊起来处理一场本来就知道要发生的故障。操作方式有两种Web UI和amtool命令行。UI适合单次点选命令行适合写进运维手册和脚本。我习惯把常用静默命令整理成团队文档这样临时有人接替操作也不会手忙脚乱amtool silence add instance10.0.0.5 --duration3h --comment例行升级维护 amtool silence list静默必须写comment和expire过期时间。没有comment一周之后没人记得当时为什么屏蔽不设置过期时间就相当于给这批告警判了无期等它失效时故障可能已经悄悄长成一个大坑。Alertmanager的静默到期会自动过期这是设计上的自我保护别去突破它。建议团队定一个规矩所有静默都要关联一个工单号或者变更单号写进comment里。这样审计时能追溯也能避免一个人挂了静默、另一个人不知道最后互相猜疑“为什么告警没响”。4. 告警降噪最佳实践从规则源头到故障现场的完整闭环4.1 源头降噪for持续时间与告警分级才是根子上的事Alertmanager再厉害也只能在Prometheus送来的一堆告警里做文章。如果源头每秒都在产生垃圾告警后面再怎么分组抑制都是亡羊补牢。真正的降噪第一刀必须砍在Prometheus rule上。最有价值的一个参数是for。它表示表达式结果持续为真的时间。比如磁盘使用率超过95%这件事IO尖峰可能瞬间冲到97%下一秒又掉回80%这种瞬时抖动根本不该打扰人。加上for: 10m只有使用率持续95%以上达到10分钟才触发告警绝大多数假告警在源头就被过滤掉了。一个成熟的告警规则几乎每条都应该配for。其次是标签和分级。我建议每一条rule都带上severity和team两个标签取值保持全国统一critical表示需要立即处理warning表示需要关注但不至于半夜把人喊醒info只做记录通知。Alertmanager的路由和抑制规则都围绕这两个标签做文章命名一旦混乱后面所有配置都会跟着乱。第三个点是表达式不要一杆子打一片。比如检查内存使用率时用by (instance)做聚合避免一个分组里的任何实例超标就把整组状态都判定为异常。尽量让每条告警都能定位到具体对象这样路由和抑制才有正确的标签可用。示例规则groups: - name: node-exporter.rules rules: - alert: NodeHighMemoryUsage expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) 0.95 for: 10m labels: severity: critical team: platform annotations: summary: {{ $labels.instance }} 内存使用率超过95% runbook: https://runbook.example.com/memory这里的for: 10m已经把绝大多数瞬时抖动过滤掉了送到Alertmanager那里的告警量自然就少了一大截。4.2 一个故障场景的降噪推演从60条轰炸到1条有效通知光讲参数太抽象我用一个具体故障场景把前面所有机制串一遍。假设现在是上午10点数据库节点DB-1宕机影响到了30个微服务实例。如果完全不配置降噪策略Prometheus会产生30条ProcessDown、30条HTTP5xx高再加1条NodeDown总共60多条告警在几秒内涌进Alertmanager。默认配置下它们可能被按alertname分组但processdown和http5xx是两组总共会发出去至少两条大消息加上恢复时又是一轮值班群基本就被刷屏了。加上合理的策略之后流程是这样的。第一层Prometheus侧给所有规则都配了for那些只抖动了十几秒的假告警已经没了。第二层Alertmanager里把NodeDown判定为critical走pager接收者把ProcessDown和HTTP5xx设置成warning走普通Webhook。第三层inhibit_rules里配置了源告警NodeDown抑制目标告警severitywarning且equal字段用instance关联所以前面那60条warning告警全部被压住一条都不会往外发。第四层剩余的critical告警经过group_wait的30秒聚合窗口把同一波故障里的几条关键消息合并成一组。最终值班手机收到的可能只有一条NodeDownDB-1不可达附带runbook链接。恢复之后再收到一条DB-1已恢复。这才是有效告警该有的样子信息密集到人可以直接做决策而不是让人在60条消息里做阅读理解。降噪不是把告警藏起来而是把信息密度压缩到人只需要看点不多的比例。告警数量多不代表重要一条精炼的告警往往比一百条描述性信息更能推动处理。4.3 告警治理定期给告警规则过节食告警降噪不是配置一劳永逸的事。系统在变流量在变业务在变半年前定下的阈值和规则半年后大概率有一半已经不合身了。我建议每个月做一次告警体检把当前还在firing或频繁触发的告警拉出来排个序看看谁是最能刷存在感的“话痨”。可以在Prometheus里直接用这个查询按告警名统计当前活跃数量sum by (alertname) (ALERTS{alertstatefiring})把排名前20的告警拿出来逐条过三关这条告警触发后值班人有没有按预期去处理没有说明它是噪音或者阈值设得太敏感。有但处理动作和告警内容对不上说明标注和描述还不够清晰要继续打磨。它是不是已经被另一条高等级告警覆盖了如果是就应该顺手把这条低频的降级或者删掉而不是让它继续重复打扰人。在一个业务稳定、值班压力可控的团队里告警规则的养护应该和代码重构一样进入定期的迭代清单而不是上线之后就再也不管。我自己每个季度至少会做一次全量规则审计把那些“当初设置时很有道理、现在完全没人在意”的告警清出去。告警集合越小、越准每一条发出来的通知才越有分量。5. 常见问题排查告警不响、重复轰炸的现场实录5.1 告警没送达链路好几段先查自己家“告警没收到”是我被问得最多的问题。排查思路一定要沿着链路走不要上来就猜。第一步先确认Prometheus侧是不是真的触发了。去Prometheus的Rules页面看这条告警当前是什么状态。如果连pending都不是说明规则本身都没满足那跟Alertmanager完全没关系回去查阈值和表达式。如果已经是pending但迟迟不转firing那就是for还没到时间属于正常现象。第二步确认告警有没有推送成功。在Alertmanager的UI上Status页面能看到它接收到的告警以及当前处理状态。如果你看到alertmanager_alerts相关的指标没有增长就要回到prometheus.yml检查alertmanagers配置段的地址和端口最常见的错误就是端口写成了Prometheus自己的9090而不是Alertmanager的9093。第三步检查是不是被静默或抑制吞掉了。在Alertmanager的Silences页面里看一圈有没有那种特别宽泛的静默规则比如匹配severity~.*的它会把所有告警都静默掉。抑制规则如果写得太宽也会导致告警在UI上显示suppressed消息却一直出不去。在UI上看到状态是suppressed基本就可以锁定是这里的问题。最后如果你确实想知道每一步发生了什么把Alertmanager加上--log.leveldebug重新启动日志里会详细记录路由匹配、分组等待、接收者调用的过程。日志读起来确实啰嗦但排查问题的时候它比任何猜都可靠。5.2 告警反复轰炸参数、路由和抑制规则逐个排查告警轰炸通常逃不出几个原因我整理成一个速查表症状常见原因解决方向同一条告警每隔几分钟重复发repeat_interval太短调大到4小时或以上一个故障刷出十几条group_by粒度太细instance被放进去按alertname或业务标签聚合一个故障同时从多个渠道收到多条continue: true被滥用或路由匹配多个receiver非必要不要开continue检查接收者配置恢复通知和故障通知一起轰炸send_resolved在多处重复开启统一在接收者配置里控制确认不重复排查时要记住一个反直觉的点group_by聚合之后组内告警不会一条条单独通知而是以组为单位。比如组里有10条告警恢复了5条Alertmanager可能发一条合并的恢复通知。这是分组机制的正常行为不是丢告警。如果团队不习惯这种粒度可以通过调整group_interval和group_by的组合来改变体验但很难做到既合并又逐条清晰。另一个常见的隐性坑是路由continue: true。我见过有人为了让critical告警同时发给值班组和业务组在子路由上开了continue结果每个receiver对应的Webhook都处理了一次同一条消息被飞书、钉钉、企业微信各发一遍形成三倍噪音。出现这种情况先检查是不是continue被开得太随意。5.3 用promtool和amtool做配置体检比重启验证靠谱改配置最怕的是改完不生效或者在原配置基础上一层层堆旧逻辑最后连自己都看不懂。Alertmanager生态里有两个工具能把这种风险压到最低。promtool可以检查Prometheus的规则文件和配置推荐每次改完rule都跑一遍promtool check rules rules.yml promtool check config prometheus.ymlamtool则负责检查Alertmanager自己的配置amtool check-config alertmanager.yml这两个工具过了再reload基本能挡住百分之九十的低级错误。注意reload之前别忘了一个动作把原来的配置备份一份。改坏了还能秒回滚。我还会在测试环境里专门做告警验证。比如临时加一条测试用的rule表达式永远为真label写成testtrue然后跟踪它在Alertmanager里的路由匹配和通知发送结果确认每条路径都符合预期后再删掉。这套验证方法花不了多少时间但它能避免你直接在线上试验把故障时间窗口变成自己的“告警实验舱”。如果你用的amtool版本较新它还有一些可以直接查看当前路由树、静默记录的子命令具体名字以你环境里的amtool --help输出为准。花五分钟看看这些命令排查的时候能省不少事。最后说一个我自己踩过好几次的坑。一开始为了让告警少一点我在inhibit_rules里只写了source_matchers和target_matchers没有加equal结果一台机器宕了凡是目标规则里severitywarning的告警全被压住范围远超那一台机器很多其实有价值的告警在悄悄消失。排查了很久才发现是equal字段缺失加上equal: [instance]之后才恢复正常的抑制范围。降噪的本质不是把告警全部变成零而是保证送到人面前的那一条值得马上处理。把规则维护成一个小而准的集合比追着数字把告警量压到零要健康得多。