刚开始接触Prometheus告警的时候,我犯过一个特别典型的错误:以为只要把Alertmanager部署起来、配上邮箱,告警就能"自动"变得好用。结果呢?Prometheus每15秒拉一次指标,Alertmanager每10秒重发一次通知,磁盘告警刚恢复又触发,一个晚上邮箱里躺着几百封邮件。从那次之后我才真正意识到,告警系统不是"搭起来"就完事的,它需要从规则、路由、分组、抑制到接收器逐层设计,才可能真正做到"该响的响、不该响的安静"。这篇文章就是围绕这条链路来的:从Alertmanager怎么部署、配置文件每段什么意思,到PromQL告警规则怎么写才不误报,再到怎样把告警准确路由到对应的处理人,以及怎么用Webhook接自己的系统。内容全部来自我实际部署和维护Prometheus监控体系的经验,适合正在搭告警系统、或者已经搭起来但一直被告警噪音折磨的运维和开发同学参考。
1. 告警链路全景:先搞清楚Prometheus和Alertmanager各自管什么
很多人第一次接触Prometheus告警的时候,会把"Prometheus告警"理解成一件事,但实际这套体系里Prometheus和Alertmanager是两个职责完全不同的组件。如果没把这个边界搞清楚,后面配规则、调路由都会很别扭。
1.1 组件职责边界:哪些事不该让Alertmanager做
Prometheus负责的是"检测":它按设定周期(默认15秒)从 exporter 拉取指标,然后持续评估你配置的告警规则。只要规则里的 PromQL 表达式计算结果满足了触发条件,Prometheus 就会把这条告警标记为 pending 或 firing,然后通过 alerting 配置里指定的地址推送给 Alertmanager。
Alertmanager 负责的是"处理":它接收来自 Prometheus 的告警,去重、分组、走路由树匹配接收人,再经过抑制、静默等逻辑过滤之后,才真正调用邮件、Webhook、企业微信这些接收器把消息发出去。用一句话概括就是:Prometheus 管"什么时候出问题",Alertmanager 管"怎么通知到人、通知得是否克制"。
我在实际排查中见过不少反例,比如有人在 Prometheus 规则里直接把接收人写死在告警标签里,然后在 Alertmanager 里完全不配路由;也有人恨不得在 Alertmanager 里做数据聚合,试图用 alertmanager 去查询指标。这些都是职责混淆。Alertmanager 不查指标,它只针对 Prometheus 推过来的 alert 数据做处理。
1.2 一条告警从产生到触发的完整生命周期
一条告警在 Prometheus 体系里的状态流转,新手最容易看懵。其实就三个状态:inactive(正常)、pending(已满足条件但未到持续时间)、firing(持续达到设定的 for 时间,正式触发)。
举个例子,我配了一条磁盘使用率超过 85% 且持续 5 分钟触发告警的规则。当磁盘使用率瞬间超过 85% 时,告警进入 pending;如果 5 分钟过去仍然超过 85%,状态变为 firing,此时 Prometheus 才把这条告警推给 Alertmanager。只要低于阈值 85%,无论 pending 还是 firing,都会回到 inactive。
这里有一个很多初学者忽略的关键点:Prometheus 在 firing 状态下会持续推送告警,而不是只在状态变化的那一刻推一次。默认情况下,每次重新评估规则(通常是每 15 秒到 1 分钟)时,处于 firing 状态的告警都会被再次发送给 Alertmanager。这正是"告警需要分组和抑制"的根本原因——如果不做处理,一条持续的故障会刷出大量重复通知。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Alertmanager部署:从二进制安装到Prometheus侧地址对接
Alertmanager 的部署方式很多,可以用二进制包、容器、Kubernetes Operator 等。我自己的监控环境既有虚拟机场景也有容器场景,这里以最常见的二进制方式为例,讲清楚每个环节的细节。
2.1 二进制部署与systemd管理
从 Prometheus 官网下载 Alertmanager 的 Linux 二进制包后,解压到 /opt/alertmanager 目录。二进制会把默认配置文件指向同目录下的 alertmanager.yml。我通常会把配置文件和二进制分开,用 --config.file 参数显式指定路径,方便后续维护。
解压完成后,推荐用 systemd 管理进程,而不是 nohup 放任不管。systemd 服务配置里需要重点关注几个参数:Restart=always 保证进程挂掉自动拉起;User=alertmanager 避免用 root 运行;ExecStart 里显式写清 --config.file 和 --storage.path。storage.path 是 Alertmanager 存储静默和通知状态的数据目录,不指定的话默认在本地目录,如果二进制所在目录被清理,静默记录就会丢。
启动后先做两件事:一是 curl http://localhost:9093/-/healthy 检查健康状态,返回 OK 才算起步成功;二是浏览器打开 9093 端口,能正常看到 Alertmanager 的 Web UI。到这里 Alertmanager 本身已经跑起来了,但它还收不到任何告警,因为 Prometheus 那边还没告诉它"我这边有告警要发给你"。
2.2 Prometheus侧的alerting配置:不配这个,Alertmanager就是个空壳
Prometheus 的 prometheus.yml 里需要配置 alerting 字段,把自己和 Alertmanager 关联起来。核心配置如下:
yaml复制alerting:
alertmanagers:
- static_configs:
- targets:
- 192.168.1.10:9093
timeout: 10s
这里 timeout: 10s 是 Prometheus 推送告警给 Alertmanager 的超时时间。我建议不要用默认值,稍微调大一点到 10 秒,免得 Alertmanager 在处理大量告警时响应稍慢,Prometheus 就报推送失败。
Prometheus 推送告警走的是 Alertmanager 的 API v2 接口,这是默认行为,不需要额外配置。但如果你用的是老版本 Alertmanager,可能要走 API v1,需要在 alertmanager 配置里显式启用 api_v1 支持,这个还是得注意一下版本兼容。
验证这一步是否打通也很简单:在 Prometheus 里手动执行一条会触发的规则,或者用 amtool 工具的 check-config 先验证配置文件,再用 curl 往 Alertmanager 的 API 里手动塞一条测试告警。
2.3 配置文件核心结构:global、route、receivers、inhibit_rules
Alertmanager 的 alertmanager.yml 就四个核心段落:global、route、receivers、inhibit_rules。很多人在初期容易一上来就照着网上抄一个大而全的配置,抄完却不知道每段是干嘛的,出了问题无从下手。
global:全局配置,比如 SMTP 服务器信息、企业微信相关参数。这些可以被 receivers 里的具体配置覆盖。route:路由树,决定告警进入哪个接收器、如何分组、要不要重复发送。receivers:接收器定义,具体怎么发出去(邮件、Webhook 等)。inhibit_rules:抑制规则,当某个告警已经在触发时,抑制另一组相关的告警。典型的例子是服务器宕机的告警触发后,这个节点上所有服务不可用的告警就没必要再发一遍了。
这四个段落里,route 和 receivers 是日常调整最多的。下面第4节和第5节我会单独展开讲。
3. 告警规则设计:PromQL阈值不是随便写的
Alertmanager 只是"通知出口",真正决定告警准不准的,是 Prometheus 里的告警规则。规则文件写在 Prometheus 的 rules 目录下,通过 rule_files 字段加载。不要把所有规则塞进一个文件,按场景拆分(节点、容器、中间件等)会好维护得多。
3.1 规则文件格式与for字段的作用
一个典型的节点存活告警规则长这样:
yaml复制groups:
- name: node_alerts
rules:
- alert: InstanceDown
expr: up == 0
for: 2m
labels:
severity: critical
annotations:
summary: "实例 {{ $labels.instance }} 已下线"
这里 expr 是 PromQL 表达式,up == 0 表示 exporter 返回的 up 指标为 0,即抓取失败。for: 2m 表示这个条件持续 2 分钟才触发告警,目的是过滤掉网络抖动、exporter 重启瞬间造成的瞬时误报。
for 的取值需要根据监控对象性质来定。基础设施层我一般设置 2~3 分钟,可以在 Prometheus 的 UI 右上角菜单进到 Rules 页面查看每个规则当前的评估状态。对于业务接口这种波动大的,可能要设置更长的时间窗口;而像磁盘满这种持续性问题,for 可以设短一些,比如 1 分钟,因为在多实例场景下,有时候 1 分钟流量就是巨大的损失。
3.2 磁盘使用率规则:为什么很多人的规则一直在误报
磁盘使用率是 Prometheus 监控里最常见的告警规则,但也是误报率最高的规则。原因在于很多人直接抄网上的表达式,没理解 node-exporter 暴露的指标含义。
我见过最常被抄也最坑的一个表达式是:
promql复制(1 - (node_filesystem_avail_bytes / node_filesystem_size_bytes)) * 100 > 85
不加任何过滤的话,这个表达式会把 node-exporter 自己挂载的临时文件系统、伪文件系统统统算进去。比如 tmpfs、overlayfs、shm 这些目录的可用率本来就一直在动态变化,很容易触发误报。
我实际在用的节点磁盘规则长这样:
promql复制(1 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|squashfs|ramfs"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs|ramfs"})) * 100 > 85
这里用 fstype 过滤掉临时和虚拟文件系统,只关注真实的磁盘文件系统。另一个需要注意的点是,node_filesystem_avail_bytes 对普通用户而言不等于可用空间,因为 root 通常有保留块(默认为总空间的 5%)。比如磁盘使用率达到 94% 时,普通用户看到的是满了,但 root 还能写。所以阈值设在 85% 是一个比较稳妥的实践值。
如果你监控的是容器环境,别直接沿用 node-exporter 的规则。容器里的磁盘指标往往来自 cgroup 或 overlayfs,需要结合容器运行时暴露的指标单独设计,不要一条规则打天下。
3.3 标签与注解的正确用法:别把summary和description当成装饰
很多人在写规则时会忽略 labels 和 annotations 的设计。labels 直接影响 Alertmanager 的路由匹配,annotations 则影响接收到的通知内容。实际上这两者的设计才是一条告警规则质量的分水岭。
labels 里我建议至少包含 severity(告警级别),根据业务需要再加 team(负责团队)或 env(环境)。这样 Alertmanager 就能按这些标签做路由分派。annotations 里 summary 写一句话概述,description 补充详细上下文,比如当前值、历史趋势链接等。
description 里最实用的就是动态把当前指标值带进去:
yaml复制annotations:
summary: "磁盘使用率过高"
description: "节点 {{ $labels.instance }} 的挂载点 {{ $labels.mountpoint }} 使用率已达 {{ $value | humanizePercentage }}"
humanizePercentage 是 Prometheus 模板函数,可以把 0.8532 转成 85.32%。这样收到的告警消息直接念得通,不用再查监控面板。
4. 路由与分组:把告警送对人是核心
Alertmanager 的路由树是整个配置里最考验"设计能力"的部分。它的匹配逻辑跟 nginx 的 location 有点类似:从根节点往下走,第一个匹配到的节点生效,子节点还有继续匹配的机会。如果根节点直接匹配了,子节点就不看了。
4.1 路由树匹配逻辑:从根到叶子逐层决定去向
一条告警进入 Alertmanager 后,会从 route 的根节点开始逐层匹配。根节点通常配置最通用的分组参数和默认接收器,子节点按告警标签匹配更具体的接收人。
我实际在用的一个简化版路由树如下:
yaml复制route:
receiver: "default"
group_by: ["alertname", "instance"]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- matchers:
- severity="critical"
receiver: "critical_webhook"
continue: false
- matchers:
- team="database"
receiver: "db_webhook"
continue: false
路由子节点里的 matchers 支持多个匹配条件,可以按 severity="critical"、team="database"、instance=~"db-.*" 等方式组合。匹配语义就是多个条件同时满足才进入该子节点。
这里有个很容易出意外的关键参数:continue。如果设为 false(默认),匹配到该子节点后路由就终止,不会再看后面的兄弟节点。如果设为 true,则匹配完当前节点后,还会继续尝试后续的兄弟节点,实现"一条告警同时发给多个接收器"的效果。在多个团队需要同时收到同一条告警的场景下,把它设为 true 就是决定性的设计。
4.2 分组参数调优:group_wait、group_interval、repeat_interval
这三个参数决定了告警通知在时间维度上的聚合方式,也是控住告警风暴的最主要手段。我见过最典型的配置失误是把 group_wait 设成 0,导致同一个分组里的几条同时触发的告警被拆成多条消息发出去。正确的做法是把 group_wait 设短一点(比如 30 秒),让短时间内触发的同组告警合并为一条消息,但又不至于让接收人等太久。
group_wait:分组后第一条通知的等待时间。比如 30 秒,告警进入分组后,会等 30 秒再发送,让同一时间窗口内的其他同组告警搭上同一班车。group_interval:同组内新告警加入后,下一次发送通知的时间间隔。比如 5 分钟,在分组里已有告警还在 firing 时,如果来了新告警,最快 5 分钟后会把整个组重新发一次。repeat_interval:同一个告警组在保持 firing 状态时,重复发送通知的间隔。比如 4 小时,作用是提醒接收人"这问题还挂着",但又不至于每 5 分钟轰炸一次。
这三个参数要根据告警频率和应急响应的节奏来调。业务比较核心的,repeat_interval 可以短一点;非核心的比如集群里某台机器磁盘快满了但没有业务流量,repeat_interval 可以拉得很长。另外提一句,只有 group_wait、group_interval 是每次触发新分组或新告警加入时生效,repeat_interval 只针对组内"已存在的告警还在 fire"这个场景重复通知。
4.3 抑制和静默:怎么让Alertmanager闭嘴
抑制规则(inhibit_rules)解决的是"父故障已经触发时,子告警不用再发"的问题。比如一台物理机宕机,如果该机器上还跑了几十个容器或虚机,这些实例的存活告警会在同一时间全部触发。如果没有抑制规则,接收人会瞬间收到几十条告警,谁看到都会崩溃。
抑制规则核心配置如下:
yaml复制inhibit_rules:
- source_matchers:
- alertname="InstanceDown"
- instance="server01"
target_matchers:
- instance="server01"
equal:
- instance
语义是:当源告警 alertname="InstanceDown" 且 instance="server01" 存在时,抑制所有目标规则里 instance="server01" 的其他告警。equal 字段表示源和目标都必须有相同的标签值才执行抑制,这是防止误伤的关键。
静默(silence)则是手动让某条告警在指定时间内闭嘴的机制。比如大版本发布期间不想被无关告警打扰,可以在 Alertmanager UI 的 Silence 页面新建静默,或者在命令行用 amtool silence add 创建。需要注意的是,静默是基于标签匹配的,如果告警内容里没有你设置的标签,静默就不会生效。另外静默一旦创建,到截止时间前都不会自动解除,最好设置一个有效期提醒自己回来删掉。
5. 接收器配置实践:邮件、Webhook与企业微信
Alertmanager 支持很多接收器类型,官方文档列得很清楚。但在真实环境里,邮件和自定义 Webhook 是两种最主流的接入方式:邮件适合传统团队,Webhook 适合把告警对接进内部系统或 IM 工具。
5.1 邮箱SMTP配置的几个常见坑
邮箱配置看似简单,但也是踩坑重灾区。最常见的坑有三个:
- 使用 25 端口发信被云厂商封禁。现在很多云服务器默认封禁 25 端口,表现就是配置好 SMTP 后测试一直超时或连接被拒。解决方法是改用 465(SSL)或 587(STARTTLS)。
- 邮箱授权码问题。某些邮箱服务需要专门的 SMTP 授权码,不是登录密码。
- 没有配置
auth_identity导致登录失败。
一个可用的邮箱接收器配置大概长这样:
yaml复制receivers:
- name: "email"
email_configs:
- to: "ops@example.com"
from: "alert@example.com"
smarthost: "smtp.example.com:465"
auth_username: "alert@example.com"
auth_password: "your_smtp_password"
auth_identity: "alert@example.com"
require_tls: true
关于 require_tls,如果 SMTP 服务器要求加密,那必须设为 true,否则邮件发不出去。但有些内网自建的邮件服务器不支持 TLS,就得显式设为 false。还有一个小技巧:Alertmanager 的邮件通知默认主题比较丑,可以通过 headers 字段自定义主题模板,把告警级别和概要带进去。
5.2 自定义Webhook:把告警对接到内部系统
Webhook 是 Alertmanager 接收器里最灵活的一种,它会把标准格式的告警 JSON Post 到你指定的 HTTP 接口。我自己的内部告警平台就是通过 Webhook 接入 Alertmanager 的,这样告警可以直接落入工单系统,还能自动关联到对应的服务负责人。
配置非常简单:
yaml复制receivers:
- name: "custom_webhook"
webhook_configs:
- url: "http://192.168.1.100:8080/alert/hook"
send_resolved: true
send_resolved 极其重要。默认情况下 Alertmanager 不会发送"恢复"通知,如果接收方系统需要根据恢复通知关闭告警工单,必须把它设为 true。
收到的 Webhook POST 请求体是一个 JSON,外层是 status(firing / resolved),alerts 数组里是具体告警项。每次 Prometheus 重新推送时,Alertmanager 可能会重复发送同一条告警,接收系统一定要做幂等处理,否则会产生重复工单。我的做法是用 alertname + instance + startsAt 组合生成去重键,这样即使后续重复通知也能对应到同一张工单。
5.3 企业微信/钉钉机器人的常见对接方式
企业微信机器人和钉钉机器人其实是基于 Webhook 的变种:Alertmanager 的 webhook 接收器把告警 POST 给我们写的一个转发服务,由它再调用企业微信或钉钉的机器人接口。不过现在也有人直接用 Alertmanager 的 wechat_configs,官方支持企业微信。
如果用了自定义转发服务,需要注意企业微信机器人有消息频率限制(每个机器人每分钟最多 20 条),所以高并发告警场景下必须做聚合转发。转发服务里我一般会做三件事:按 severity 级别选择是否艾特对应负责人,按 group 合并多条告警成一条消息,把 summary 和 description 渲染成易读文本。
6. 从触发到通知:一次内存告警的完整排查链路
理论讲再多,不如亲自走一遍排查链路。我拿一次真实的内存告警事件来说明:某天凌晨收到一条告警,内容是"服务器可用内存低于20%触发器"。这个告警从触发到最终送达邮箱,经历了下面这些环节,其中任何一环出问题,都会导致没收到或者收到一堆垃圾。
6.1 场景复现:内存告警为什么没收到?
先说背景:Prometheus 持续监控 node-exporter 的 node_memory_MemAvailable_bytes,规则设定当可用内存占总量比例低于 20% 且持续 3 分钟时触发告警。那天凌晨触发后值班同事反馈压根没收到邮件。
排查时我按顺序做了四步检查:
第一步,先到 Prometheus 的 Rules 页面看规则状态。确认这条规则处于 firing 状态。如果状态是 pending,说明 for 持续时间还没到,或者中间恢复过又再次触发。这里能看到告警从何时开始、当前最新评估值是多少。
第二步,去 Alertmanager 的 Web UI 看收到的告警列表。如果这里能看到告警,说明 Prometheus 推送环节正常,问题出在 Alertmanager 的通知环节。如果这里什么都看不到,那问题大概率出在 Prometheus 到 Alertmanager 的推送链路上,需要检查 Prometheus 的 alerting 配置或网络连通性。
第三步,看 Alertmanager 日志。日志会记录每一次通知发送的动作、目标 receiver 名称。如果日志里报 SMTP 超时或者 401 登录失败,那就是邮件配置问题;如果日志显示通知已发送成功,那问题在邮件服务器或垃圾箱。
我这次遇到的坑是第三种:SMTP 服务器把 Alertmanager 当成了恶意发件人,邮件直接进了垃圾箱。另外还有个隐藏问题:该告警虽然触发,但它匹配进了默认路由组,而默认路由组绑定的接收器是 email 接收器,绑定的收件人之一把邮箱设置成了"来信分类到垃圾箱",所以同事没看到。
6.2 常见问题的定位方法与工具手段
amtool check-config alertmanager.yml:验证配置文件格式,检查路由树匹配是否正确。配置有错会直接报出来,对排查很有帮助。amtool alert query:查询当前 Alertmanager 里活跃的告警。curl http://localhost:9093/api/v2/alerts:用 API 直接查看告警原始数据,判断接收到的告警是否带齐了路由匹配需要的标签。
如果发现 Alertmanager 收到了告警但接收人没收到消息,优先看 Alertmanager 日志,它会明确告诉你是发送成功还是发错地方。如果日志里显示发送成功,则把排查重点放在目标接收方(邮箱、IM 消息等)。
另一个常见问题是 Webhook 接口收到请求但没处理成功。我调试的时候会临时在自己写的接口里加一个打印原始 body 的日志,然后把 Alertmanager 的 webhook_configs.url 临时指向这个打印接口,确认数据到了、结构对不对。排查完再切回正式接口。
7. 高可用部署与告警治理经验
Alertmanager 在生产环境里通常至少部署两个实例,组成集群。很多人以为集群是为了"负载均衡",其实 Alertmanager 集群模式的核心价值是高可用和去重。
7.1 双实例集群的配置要点
配置集群模式的前提是多个 Alertmanager 实例之间能互相访问 gossip 协议端口(默认为 9094)。启动参数上,每个实例通过 --cluster.listen-address 指定自己的监听地址,通过 --cluster.peer 指定其他实例的地址。
Prometheus 侧配置 Alertmanager 时,直接在 targets 里写多个地址即可:
yaml复制alerting:
alertmanagers:
- static_configs:
- targets:
- 192.168.1.10:9093
- 192.168.1.11:9093
Prometheus 会把告警推送给每个 Alertmanager。集群模式的意义就在于此:当一条告警同时进入多个实例时,gossip 协议会自动协调出一个实例负责发送通知,其余实例保持静默,从而避免重复通知。如果负责发送的那个实例挂掉了,其他实例会立刻接管。
这里要提醒一个容易忽略的点:因为 Prometheus 会推给所有实例,如果 Alertmanager 集群配置失败(比如 peer 地址不通),两套实例会各自为政,同一告警会重复发两遍通知。所以在部署完集群后,务必到 Alertmanager 的 Web UI 的 Cluster 页面确认所有节点状态正常。
7.2 告警风暴治理:从规则和接收端两头下手
告警风暴这个词做运维的都懂。表面看是 Prometheus 一次触发了太多告警,但根因往往是规则没做好分组抑制,或者路由树设计不合理。治理告警风暴我从两个方向下手。
第一是源头减少噪音。检查告警规则里有没有 for 设得过短、有没有用 fstype 和 mountpoint 做过滤、阈值是不是定太低。这里最关键的是一条规则在触发时,问一句"收到这条告警的人现在能立刻采取行动吗?"如果答案是不能,就把它改成信息级而不是告警级,或者提高阈值减少触发频率。
第二是接收端聚合。Alertmanager 的分组、抑制、repeat_interval 调优都能显著降低通知量。比如当机房网络出现抖动时,可能同时有上百条节点失联告警,但通过 group_by: ["alertname"] 和抑制规则,接收人最终只收到一条"机房失联,涉及 N 个实例"的汇总通知。
7.3 我这边长期稳定运行的一套配置风格
经过几轮告警风暴打磨后,我这边长期稳定运行的 Alertmanager 配置风格是:根节点只负责兜底,具体告警全部走子路由项,接收器按团队或系统拆分。根节点 receiver 用邮件兜底,子节点里 critical 级别走 Webhook 并艾特值班人,数据库相关走数据库团队接收器,业务相关走业务团队接收器。
另外我会在每个子路由的 continue: false 前面谨慎考虑:如果这条告警只发一个接收器就够了,就设 false;如果希望同时发给多个团队,才设 true。大多数团队协作场景,不需要同时全发。
对于静默,我建议定期清理。如果一条告警长期被静默,通常说明这条规则已经失去告警的意义了,应该调整规则或者删除静默,而不是一直用静默遮蔽问题。
从部署 Alertmanager 那一刻起,到最后告警能准确分派到人,整个过程其实是在不断做减法:减少误报、减少重复、减少噪音,最后留下来的才是真正有价值的告警。我个人的体会是,告警系统的核心不是"把每条故障都发出去",而是"在正确的时间,把正确的信息,交给能处理它的人"。沿着这个思路去调路由、写规则、配接收器,基本上不会跑偏。
