做监控这块的朋友应该都体会过这种场景:Prometheus 抓取指标、配置告警规则都弄好了,Alertmanager也部署上去了,结果线上真的出故障时,告警发没发出去、发到哪、有没有人看,反而成了最大的不确定性。我自己早期就吃过亏,明明 rule 文件里for: 5m写得清清楚楚,Prometheus 的/alerts页面也显示Firing,可邮箱里就是收不到邮件,手机上也毫无动静。后来排查了一圈才发现,问题全出在 Alertmanager 的路由配置和接收器上——规则触发只是上半场,下半场怎么把告警准确、及时、不轰炸地送到人手里,才是真正见功夫的地方。
这篇文章就围绕 Alertmanager 的邮件告警和微信告警两条线展开,把配置方法、模板写法、路由分组、常见坑一次讲透。整体偏实操,适合已经把 Prometheus 搭起来、正在折腾告警通知的同学;如果只是刚接触 Prometheus,也可以先照着配一遍,遇到概念我再顺带解释,不会让你卡在半路。
1. 为什么用 Alertmanager 做邮件和微信告警
1.1 告警链路里 Alertmanager 到底扮演什么角色
先理清一个很多人没彻底搞清楚的问题:Prometheus 本身带告警规则,比如up == 0这种表达式,一旦命中,Prometheus 会把这个告警标记为pending或firing。但它只是一个状态机,真正负责“把告警送出去”的,是 Alertmanager。
换句话说,Prometheus 负责“发现问题”,Alertmanager 负责“通知人”。链路大概是这样的:
- Prometheus 根据告警规则计算表达式,命中后生成一条告警。
- Prometheus 通过配置里的
alertmanagers地址,把告警推送给 Alertmanager。 - Alertmanager 收到告警后,根据
route路由树决定这个告警该走哪个receiver。 - receiver 再调用具体渠道(邮件、企业微信、钉钉、webhook 等)把内容发出去。
所以,如果你在 Prometheus 里配了 rule,但没配alertmanagers这个字段,那 Prometheus 压根不会把告警发出去。这一步往往会漏掉,很多人排查半天,最后发现 Prometheus 配置文件里alerting那一段压根是空的。
在 Alertmanager 这一侧,它最核心的三个能力是:分组(grouping)、抑制(inhibition)、静默(silence)。这仨功能解决了实际运维里最头疼的问题——告警风暴和重复告警。比如一台数据库挂了,往往会连带触发几十条相关告警,如果没有分组和抑制,你的手机会在十分钟内被几百条消息打成震动模式。Alertmanager 会把同一类告警合并成一条通知,然后再用抑制规则把噪音压下去。这一点后面展开讲。
1.2 邮件加微信的组合,解决什么问题
告警渠道的选择,本质是在“可靠性”和“触达率”之间做平衡。邮件的好处是链路简单、留痕清晰、适合做事后追溯,但缺点是没人保证你盯着收件箱;微信(准确说是企业微信应用消息)的好处是手机端能直接弹出来,看一眼就能判断要不要起来处理。所以生产环境里,我推荐的做法是两个通道同时开,邮件做归档,微信做即时触达。
微信这块,很多人以为能直接把告警推到自己个人微信上。这个我刚才也踩过坑——目前官方可靠的路子不是个人微信,而是企业微信应用消息。你可以理解成:企业微信提供一个“应用”的入口,Alertmanager 通过 webhook 把告警内容交给一个转发脚本,脚本再调用企业微信 API 把消息推给应用里指定的人。接收的人手机上不需要装额外的特殊工具,只要装了企业微信并加入企业就行,推送效果和微信消息几乎一致。这也是为什么网上搜“微信告警”时,最后基本都会落到企业微信的方案上。
至于 Linux 服务器上想跑企业微信客户端收告警这类操作(麒麟系统也常见有人问),我建议直接打消这个念头。告警推送的正确姿势是 API 调用,不是靠客户端挂在服务器上接收——客户端方案既不稳定,也没法做到告警内容的自动化编排。API 推送不管你的服务器是什么发行版,只要出网能访问企业微信接口就行,这才符合运维自动化的思路。
2. 部署与最小可用配置:先把告警跑起来
2.1 安装部署:单二进制还是容器
Alertmanager 的部署非常轻量,官方给的是一个静态二进制,压缩包解压出来就一个可执行文件加一个默认配置文件,不像 Prometheus 还要考虑存储和大量 TSDB 参数。单机部署的话,直接下载对应平台的包,解压后把二进制放到/usr/local/bin/alertmanager,再配上 systemd 服务就能跑。
我用的是容器方式,一个 docker-compose 就能搞定:
version: '3' services: alertmanager: image: prom/alertmanager:v0.27.0 container_name: alertmanager restart: always ports: - "9093:9093" volumes: - ./alertmanager.yml:/etc/alertmanager/alertmanager.yml - ./template:/etc/alertmanager/template command: - '--config.file=/etc/alertmanager/alertmanager.yml' - '--web.external-url=http://your-server:9093'这里有几个参数值得说下。--web.external-url建议显式配置,特别是后面要用 Alertmanager 的 Web UI 做静默操作时,回调地址对不对会直接影响体验。如果生产环境有多个副本,还可以加--cluster.listen-address和--cluster.peer组成集群,实现告警去重和高可用。不过说实话,告警通知这种事,单节点挂了影响的就是通知,不像业务系统那样分秒必争,所以单机起步完全够用,等真需要了再上集群不迟。
启动后,浏览器访问http://your-server:9093,能看到 Alertmanager 的 Web UI,左侧有 Alerts、Silences、Status 几个标签,现在基本是空的,因为还没有任何告警进来。
2.2 alertmanager.yml 核心结构拆解
Alertmanager 的配置集中在alertmanager.yml,内容不算多,但每个字段背后都有讲究。一个最精简的配置长这样:
global: resolve_timeout: 5m smtp_smarthost: 'smtp.qq.com:465' smtp_from: 'sender@qq.com' smtp_auth_username: 'sender@qq.com' smtp_auth_password: '你的SMTP授权码' smtp_require_tls: false route: group_by: ['alertname'] group_wait: 10s group_interval: 2m repeat_interval: 4h receiver: 'email' receivers: - name: 'email' email_configs: - to: 'ops@example.com'逐段解释一下:
global是全局配置。邮件相关的 SMTP 参数都放这,smtp_smarthost是邮件服务器的地址和端口,smtp_from是发件人地址,smtp_auth_username和smtp_auth_password是认证信息。注意这里填的不是邮箱登录密码,而是 SMTP 授权码。QQ 邮箱、163 邮箱都在网页端设置里开 SMTP 服务后生成授权码,直接用登录密码会导致 535 认证失败的报错。
smtp_require_tls: false是很多人容易忽略的一点。使用 465 端口时,Alertmanager 是直接走 SSL 加密连接的,不需要在应用层再协商 STARTTLS,所以这里要设成false。如果用 587 端口走 STARTTLS,那就保持不变,默认就是true。
route是路由树,Alertmanager 收到告警后会从根路由开始向下匹配,决定这个告警交给哪个 receiver、按什么规则分组。最基本的根路由只需配receiver一个字段,就能把默认目标指向邮件接收器。
receivers是接收器列表,每个接收器定义一个发送渠道,名字与路由里的receiver对应。上面这个配置,所有告警都会以邮件形式发到ops@example.com。
2.3 路由与接收人设计思路
很多教程到上面就结束了,但真实场景里,一个 receiver 是不现实的。你的团队里,数据库告警应该发给 DBA,业务接口告警发给后端组,基础设施宕机告警发给所有人。这种"按角色分发"的需求,就要靠路由树的多分支来实现了。
我常用的设计思路是:根路由做默认兜底,子路由按标签分流。比如:
route: group_by: ['alertname', 'instance'] group_wait: 30s group_interval: 5m repeat_interval: 4h receiver: 'default-email' routes: - match: team: db receiver: 'dba-email' continue: false - match_re: severity: critical|emergency receiver: 'all-page' continue: true - match: team: web receiver: 'web-wechat'逻辑很直白:如果告警带上了team=db标签,就交给 DBA 的邮件接收器;如果级别是critical,同时发给 all-page(这个接收器可以同时配邮件和企业微信);如果team=web,就走微信通道。continue字段控制的是匹配到当前子路由后,是继续往下匹配还是直接结束。比如 critical 告警我想让所有人知道,就设continue: true,这样它还能继续命中后面的team=web规则。
这种设计思路的要点是:尽量用 Prometheus 告警规则里已有的标签作为分流依据,比如team、severity、env。标签体系一开始就要规划好,不然后面接线全靠告警规则里写死,改起来坡度很大。
3. 邮件告警配置详解:从 SMTP 到 HTML 模板
3.1 SMTP 配置与授权码
邮件通道是 Alertmanager 最基础、最稳定的接收方式,也最适合做告警的“底账”。SMTP 这块具体配置我在上一节已经给了一个例子,这里再补几个不同邮件服务商的差异。
如果你用的是 QQ 邮箱:
smtp_smarthost: 'smtp.qq.com:465' smtp_from: 'your-name@qq.com' smtp_auth_username: 'your-name@qq.com' smtp_auth_password: 'xxxxxxxxxxxxxxxx' # 授权码,16位 smtp_require_tls: false如果是 163 邮箱,把smtp_smarthost改成smtp.163.com:465,授权码也是单独生成的。企业邮箱的话,如阿里云企业邮箱,一般是smtp.mxhichina.com:465,认证方式一样,只是端口和地址不同。
有个小提示:如果公司内部有邮件中继,比如 Postfix 或 Exchange,且允许内网匿名发信,那 SMTP 认证段可以留空,smtp_smarthost填内网地址就行,速度比走公网邮箱快很多,也不受授权码过期影响。
配完之后,可以用amtool快速验证配置是否合法:
amtool check-config alertmanager.yml如果输出SUCCESS,说明基础结构没问题。但这一步验证不了邮件能否真实送达,最好直接触发一条测试告警跑一遍全流程。
3.2 用模板做出可读性强的邮件正文
Alertmanager 默认的邮件内容比较简陋:标题是[FIRING:1] (alertname),正文是一系列Labels和Annotations的键值对。给内部自己人看凑合能用,但如果要发给业务方或者老板,最好自定义一个 HTML 模板。
配置模板需在alertmanager.yml里声明模板目录:
templates: - '/etc/alertmanager/template/*.tmpl'然后在 template 目录下建一个email.tmpl,我用的模板供参考:
{{ define "email.html" }} <html> <body> <h3>Prometheus 告警通知</h3> {{ range .Alerts }} <table border="1" cellpadding="5" style="border-collapse:collapse;font-family:Arial;"> <tr><td>告警状态</td><td>{{ .Status }}</td></tr> <tr><td>告警名称</td><td>{{ .Labels.alertname }}</td></tr> <tr><td>严重级别</td><td>{{ .Labels.severity }}</td></tr> <tr><td>实例</td><td>{{ .Labels.instance }}</td></tr> <tr><td>触发时间</td><td>{{ .StartsAt.Format "2006-01-02 15:04:05" }}</td></tr> <tr><td>描述</td><td>{{ .Annotations.summary }}</td></tr> </table> {{ end }} </body> </html> {{ end }}这里有个非常容易踩坑的地方:Go 模板的时间格式化参考时间是固定的2006-01-02 15:04:05,这不是随便写的字符串,而是 Go 语言里格式化时间的一种约定。如果你写"YYYY-MM-DD HH:mm:ss",渲染出来会是满满的YYYY原始字符串,不会自动转成真实时间。
邮件接收器配置里引用这个模板:
receivers: - name: 'email' email_configs: - to: 'ops@example.com' headers: subject: '{{ template "email.subject" . }}' html: '{{ template "email.html" . }}'如果模板渲染出错,Alertmanager 会在日志里打印详细的错误信息。特别是字段名大小写写错,比如把startsAt写成StartAt,模板引擎会直接渲染失败,然后在告警日志里报template: ... map has no entry for key。这块调试起来不算麻烦,先把手工构造的 JSON 灌进amtool template render就能单独验证模板,后面 6.2 节我会专门说 amtool 的用法。
3.3 邮件告警的几个坑
邮件渠道最典型的"收不到"问题,优先级排序大概是:SMTP 认证失败、模板渲染失败、被邮箱服务商扔进垃圾箱、foxmail 等客户端本地归档。
第一类报错在 Alertmanager 日志里能看到,比如535 Error: authentication failed,这是授权码不对或者没开 SMTP。第二类通常日志里有template execution failed,不会影响发送,但邮件可能是空的。
第三类很隐蔽,特别是用企业邮箱测试时,邮件可能根本没进收件箱,而是被当成营销邮件丢进了垃圾箱。排查方法就是让对方在网页版邮箱里搜索发件人地址,如果能在垃圾箱里找到,说明你需要在邮件服务商后台把自己的发件地址加白名单。
第四类涉及到 foxmail 这类客户端。foxmail 新版本把邮件存储路径从原来的Foxmail 7.2/Storage改成了带账号目录的 Accounts 结构,不少人以为邮件丢了,其实只是没更新客户端,或者本地索引出问题导致旧邮件显示不出来。真实排查时,先登录网页版邮箱确认邮件是否真的送达,如果网页版有而本地 foxmail 没有,那就是客户端同步的问题,不要再回头折腾 Alertmanager 了。
4. 微信告警配置实战:企业微信应用消息的完整接入
4.1 为什么不是个人微信而是企业微信
这个问题几乎每次分享都会有人问。原因很简单,个人微信的消息推送接口不开放给普通开发者,你没法拿个人微信号作为告警推送的接收端去调用 API。企业微信则不同,它提供了完整的应用消息推送接口,而且触达体验和微信基本一致:手机上的企业微信 App 会弹出通知,即便没打开 App 也能收到推送。
所以整体方案是:
- Alertmanager 配置一个
webhook类型 receiver,收到告警后把内容 POST 到一个本地转发服务。 - 转发服务(可以是一个几十行代码的小脚本,也可以是现成的开源组件)拿到告警内容后,请求企业微信接口获取
access_token,然后把消息推给指定的企业成员。
你可能看到网上有些人直接用现成的开源项目,比如prometheus-webhook-wechat。这类项目确实省事,但优点是现成的缺点是黑盒,出了告警内容格式不满意,你还得去改人家的 Go 代码。我更推荐自己写一个几十行的转发脚本,逻辑完全可控,出了问题也能第一时间定位。下面都是基于自写转发脚本来讲的。
4.2 创建企业微信应用与获取凭证
首先,你得有一个企业微信账号。注册很简单,用手机号就能搞定,不一定要有真实企业资质。登录企业微信管理后台之后,按下面的步骤创建应用:
- 在左侧菜单找到“应用管理”,点击“自建”区的“创建应用”。
- 填写应用名称(比如“监控告警”)、上传 Logo、选择可见范围。可见范围这一步很关键——只有在该范围内的成员,手机端企业微信才能收到这个应用的消息推送。
- 创建成功后在应用详情页,能看到两个关键参数:AgentId和Secret。
- 还有一个全局参数:企业ID。在“我的企业 → 企业信息”页面底部,能看到一串由字母和数字组成的企业 ID(CorpID)。
使用这些参数时,用下面这个接口换取 access_token:
curl -s "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=你的企业ID&corpsecret=你的Secret"返回结果里access_token字段就是最终凭证,有效期是 7200 秒(2 小时),过期后需要重新获取。所以转发脚本里一定要做 token 缓存,否则告警高峰时每个 CD 间隔都去请求一次 token,很容易触发企业微信接口频率限制。
这里还要提醒一个很现实的问题:企业微信新版 API 在某些情况下要求配置可信 IP。如果调用接口时返回类似60020的报错,多半就是这个原因。处理方法是拿告警推送服务器所在的公网 IP,去企业微信管理后台“应用详情 → 企业可信 IP”里配置一下。如果是自己在家里测试,出口 IP 不固定,那这个限制可能让你折腾一阵子。
4.3 用 Webhook 接收器转发告警到企业微信
Alertmanager 配置一个 webhook 接收器,就相当于“把告警投递给本地的 HTTP 接口”:
receivers: - name: 'wechat-webhook' webhook_configs: - url: 'http://127.0.0.1:8080/wechat' send_resolved: truesend_resolved: true表示恢复通知也一并推送到微信。这个开关建议打开,不然你只收到“出问题”的消息,收不到“已恢复”的消息,值守的人会一直带着疑问等着。每一条告警对值班同事来说,都意味着“要不要现在处理”,有了恢复通知,状态闭环才完整。
下面这个 Python 脚本我放在/opt/alertmanager-wechat/webhook.py,用 Flask 起一个最轻量的 HTTP 服务:
import time import requests from flask import Flask, request, jsonify APP = Flask(__name__) CORP_ID = "你的企业ID" SECRET = "你的应用Secret" AGENT_ID = "你的应用AgentId" TO_USER = "@all" # 也可以指定成员userid,如"zhangsan|lisi" TOKEN_CACHE = {"token": "", "expire": 0} def get_token(): now = time.time() if TOKEN_CACHE["token"] and TOKEN_CACHE["expire"] > now + 60: return TOKEN_CACHE["token"] url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken" params = {"corpid": CORP_ID, "corpsecret": SECRET} resp = requests.get(url, params=params, timeout=10).json() if resp.get("errcode") != 0: raise Exception(f"get token failed: {resp}") TOKEN_CACHE["token"] = resp["access_token"] TOKEN_CACHE["expire"] = now + resp["expires_in"] return TOKEN_CACHE["token"] def send_wechat(text): token = get_token() url = f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={token}" payload = { "touser": TO_USER, "msgtype": "markdown", "agentid": AGENT_ID, "markdown": {"content": text}, "safe": 0, } resp = requests.post(url, json=payload, timeout=10).json() if resp.get("errcode") != 0: raise Exception(f"send message failed: {resp}") def format_alert(alert): labels = alert.get("labels", {}) annotations = alert.get("annotations", {}) status = alert.get("status", "firing") time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(alert.get("startsAt", 0))) title = "【告警恢复】" if status == "resolved" else "【告警触发】" content = f"{title} <font color=\"warning\">{labels.get('alertname', 'unknown')}</font>\n" content += f"> 级别: {labels.get('severity', 'unknown')}\n" content += f"> 实例: {labels.get('instance', 'unknown')}\n" content += f"> 时间: {time_str}\n" if annotations.get("summary"): content += f"> 描述: {annotations['summary']}\n" return content @APP.route("/wechat", methods=["POST"]) def webhook(): data = request.json for alert in data.get("alerts", []): text = format_alert(alert) send_wechat(text) return jsonify({"status": "ok"}) if __name__ == "__main__": APP.run(host="0.0.0.0", port=8080)企业微信的 markdown 消息支持部分格式,比如>表示引用块、<font color="warning">可以给文字上色。实际推送到企业微信后,效果比纯文本好看很多。这个脚本里TO_USER可以设为@all,发给全部可见范围成员;也可以指定多个成员的 userid,用竖线分隔。成员 userid 在“通讯录 → 成员详情”里能看到,不是微信号,别搞混。
这块我建议用nohup或者 systemd 常驻后台跑。systemd 的 unit 文件很简单:
[Unit] Description=Alertmanager Wechat Webhook After=network-online.target [Service] ExecStart=/usr/bin/python3 /opt/alertmanager-wechat/webhook.py Restart=always RestartSec=5 [Install] WantedBy=multi-user.target脚本跑起来后,先在本地验证一下接口能用:
curl -XPOST http://127.0.0.1:8080/wechat -d '{"alerts":[{"status":"firing","labels":{"alertname":"test","severity":"warning","instance":"localhost:9090"},"annotations":{"summary":"这是一条测试告警"},"startsAt":"2025-01-01T10:00:00+08:00"}]}'如果企业微信正常收到推送,说明整条链路已经通了一半。剩下的一半,是验证 Alertmanager 到 webhook 的对接,可以用 amtool 或者直接往 Alertmanager 的 API 里灌一条测试告警。
4.4 微信模板消息的进阶写法
上面的示例里,消息内容是直接在 Python 里拼接的。如果告警信息字段很多、团队协作时希望不同级别显示不同样式,建议把内容渲染的逻辑单独抽出来,做成一个模板文件,而不是天天改脚本代码。
企业微信 markdown 支持的颜色标签有info(灰色)、comment(绿色)、warning(橙红色)三种。我通常这样设计:
info级别告警用info颜色,只发到邮件,不进微信。warning级别告警用warning颜色,微信推送。critical级别告警用warning颜色,并且加一个@所有人的提示。
企业微信的 markdown 消息不支持真正的 @所有人 语法,但可以在文案里显式写“请相关同事立即处理”。如果想在消息里附上 Alertmanager 的告警链接,可以在format_alert里拼一个 url:
content += f"> [查看告警详情](http://your-alertmanager:9093/#/alerts?receiver=wechat-webhook)\n"企业微信中这个链接可以直接点击跳转,值班人员不用再单开电脑查监控,这是一个很实用的细节。
5. 告警路由、分组与抑制:别让告警轰炸你
5.1 分组参数:group_wait、group_interval、repeat_interval
告警通知最大的敌人不是发不出去,而是发得太多。网络上有一个经典梗:凌晨三点,值班同学被 200 条企业微信消息炸醒,手忙脚乱打开电脑,发现只是某个非核心服务重启闪断了片刻。避免这种惨剧,靠的就是 Alertmanager 的分组机制。
先看分组配置:
route: group_by: ['alertname', 'instance'] group_wait: 30s group_interval: 5m repeat_interval: 4hgroup_by决定了告警按哪些标签归类。比如group_by: ['alertname', 'instance'],意味着同一个实例上同一类告警会合并成一条通知;而如果只按alertname分组,那不同实例上的同一类告警也会合成一条,适合全局组件挂掉导致大规模故障的场景。选哪种看实际需求——我自己的习惯是基础设施告警按alertname分组,业务告警按alertname + instance分组,这样既能看到“哪些模块出了问题”,也能精确定位到具体实例。
group_wait指同一分组的第一条告警到达后,Waiting 多长时间再发通知。这个时间是为了收集同组内可能陆续到达的其他告警,一起打包发出去,避免一条条推送。设 30s 是一个比较平衡的值,既不会等太久,也能聚合大多数相关告警。
group_interval指同一分组后续有新告警加入时,间隔多久再次通知。5 分钟是常用配置,太短容易频繁打扰,太长可能会导致问题升级时得不到及时通知。
repeat_interval指同一组告警在未恢复时,隔多久重复发送一次通知。4 小时是常见选择,设太短(比如 1 小时)会变成另一种骚扰,设太长(比如 24 小时)会导致有些问题被遗忘。
5.2 抑制规则与静默的实战用法
抑制规则解决的是“主故障导致的一堆连带告警”。最常见的例子:数据库服务器宕机了,除了node_down这种根因告警,还有十几个数据表连接失败、API 超时的衍生告警。这时候如果全部推送,值班人员收到的 90% 是噪音。
Alertmanager 的抑制规则可以这么写:
inhibit_rules: - source_matchers: - severity = critical - alertname = NodeDown target_matchers: - severity =~ warning|info equal: - instance这段规则的含义是:如果某个instance上有NodeDown级别为critical的告警在触发,那么同一instance上所有warning和info级别的告警都会被抑制。用大白话说就是——机器都挂了,还在乎它上面的进程状态干嘛。
这条规则的威力巨大,配置后告警量能下降 70% 以上。但注意equal字段一定要把instance写进去,否则会误伤其他正常实例上的告警,导致真正的故障被掩盖。
静默(Silence)则是人主动沉默告警的操作。比如你计划今晚凌晨 2 点对某台机器做维护,期间用 docker stop 重启容器一定会触发告警,但你不想被打扰。在 Alertmanager 的 Web UI 里找到对应告警,点 Silences → New Silence,填上匹配标签、时长、原因,保存后这段时间内符合条件的告警就不会通知了。用命令行的方式更高效:
amtool silence add --alertmanager.url=http://localhost:9093 \ --duration=1h \ --comment="planned maintenance" \ alertname=NodeDown instance=10.0.0.1:9100这里也提醒一下:静默一定要带--comment写明原因。没有原因标注的静默,在多人协作时等于埋了一颗雷。
5.3 路由表匹配优先级与 continue 关键字
路由树的匹配规则,除了上一节提到的match和match_re,还有一个容易被忽略的continue。它的语义是:当前路由匹配成功后,是否继续尝试匹配兄弟路由。
举个例子,我想让所有critical级别告警不仅发给对应团队,同时抄送一份到管理层值守群。这时候可以这样写:
route: receiver: 'default' routes: - match: severity: critical receiver: 'page-oncall' continue: true - match: team: db receiver: 'dba-wechat'当一条severity=critical, team=db的告警到达时,会先匹配到第一条子路由,发给 oncall 组;因为continue: true,它不会立即终止,还会继续匹配第二条子路由,再发给 DBA 的微信。如果不加continue,则匹配到第一条后直接返回,DBA 反而收不到告警。这种“多通道并行通知”在很多运维团队里都是刚需。
路由匹配顺序是从上到下,子路由之间是兄弟平级关系,不满足匹配条件就跳过。还有一个细节:根路由不写routes,只有receiver和分组参数时,它是所有告警的兜底入口。务必保证根路由有一个合理的默认 receiver,不然未命中任何子路由的告警会石沉大海。
6. 常见问题排查与调试技巧实录
6.1 告警没发出去怎么查
我在帮别人排查 Alertmanager 问题时,发现 80% 的问题是出在上游而不是下游。所以遇到“邮件/微信没收到”,我的排查顺序是这样:
第一步,先在 Prometheus Web UI 的/alerts页面看告警状态。如果状态是pending,说明还没有达到for的阈值时间;如果一直pending不转firing,看看是不是for设得太长;如果根本没有这个告警,说明告警规则表达式本身就没触发。这一步可以确认“Prometheus 到底有没有产生告警”。
第二步,确认 Prometheus 是否把告警推送给了 Alertmanager。检查 Prometheus 配置文件里alerting段落:
alerting: alertmanagers: - static_configs: - targets: ['localhost:9093']缺了这段,Prometheus 根本不会发送告警给 Alertmanager。这一步是新手最容易漏的。
第三步,去 Alertmanager Web UI 的 Alerts 页面看有没有告警进来。如果 Prometheus 显示firing,但 Alertmanager 页面空白,基本可以断定是推送连接出了问题,看一下 Prometheus 日志里有没有alertmanager notification failed之类的信息。
第四步,检查 Alertmanager 自己的日志。告警发送失败时,日志会明确写出错误,比如 SMTP 认证失败、webhook POST 超时、模板渲染错误。先看日志,不要瞎猜。
6.2 amtool 这个命令行的正确用法
amtool 是 Alertmanager 官方自带的命令行工具,但很多人部署时根本没在意过它。它在排查问题时极其好用,尤其是调用 API 测试。
检查配置:
amtool check-config alertmanager.yml查询当前活跃告警:
amtool alert query --alertmanager.url=http://localhost:9093添加一个静默:
amtool silence add --alertmanager.url=http://localhost:9093 \ --duration=30m --comment="测试静默" alertname=TestAlert但最实用的功能可能是模板渲染。定义好.tmpl文件后,可以用一条构造好的 JSON 灌进去,直接看到模板输出,不用真的等告警触发:
echo '{"alerts":[{"status":"firing","labels":{"alertname":"Test","severity":"warning","instance":"host1"},"annotations":{"summary":"test summary"},"startsAt":"2025-01-01T10:00:00+08:00"}]}' | \ amtool template render --template.glob='/etc/alertmanager/template/*.tmpl' \ --template.text='{{ template "email.html" . }}'这样就能在本地肉眼检查模板渲染结果,再也不用发真实告警去试了,效率提升不止一个档次。
6.3 高可用部署与数据保留提示
Alertmanager 的高可用部署不算复杂,多个实例通过--cluster.listen-address和--cluster.peer组成集群,实例之间会通过 gossip 协议同步告警状态,避免重复发送。以两节点为例:
节点 A 启动参数:
alertmanager --config.file=alertmanager.yml \ --cluster.listen-address=192.168.1.10:9094 \ --cluster.peer=192.168.1.11:9094节点 B 启动参数:
alertmanager --config.file=alertmanager.yml \ --cluster.listen-address=192.168.1.11:9094 \ --cluster.peer=192.168.1.10:9094然后在 Prometheus 的alerting配置里把两个地址都写上,Prometheus 会同时推送告警给两个实例。由于集群内已有去重机制,任意一个节点通知了,另一个节点就不会重复发。这套方案虽然不难,但要注意一个坑:集群模式下,如果两个节点的--config.file内容不一致,分组成员和路由行为可能产生诡异差异,所以一定要使用配置管理工具统一分发alertmanager.yml。
顺便说一件事,Alertmanager不存储告警历史,它只是转发。如果你想事后统计“过去一个月告警触发多少次”,靠 Alertmanager 是查不到的,得用 Prometheus 的ALERTS这个指标在 Prometheus 里做查询。这也是很多人在做告警报表时踩坑的地方。告警记录建议沉到 Prometheus 的 TSDB 里统一管理。
关于数据保留,Prometheus 默认保留 15 天,如果你需要做月度告警趋势分析,记得在启动参数里调整--storage.tsdb.retention.time。不过那是另一个话题了,这里点到为止。
7. 最后再分享一个实用小技巧
用 Alertmanager 一年多,我最深的体会是:告警配置的核心不是怎么发出去,而是怎么让人愿意看。很多人一开始收到几条告警还认真看,等被垃圾告警轰炸几天后,就会把所有通知静音,真正的故障反而没人管。所以我的建议是:第一周宁可少配告警,也要把分组、抑制、路由的规则打磨清楚;每种告警推出去之前,自己先以值班人的身份问一句——“我看到这条消息,知道该怎么处理吗?”如果答案是否定的,这条告警的通知文案还得再改。
最后再分享一个小技巧:测试企业微信告警时,不要每次都用反序列化脚本手工构造数据。我习惯在 Alertmanager 上添加两个测试用的静默——一个匹配所有alertname=TestAlert的告警,时长 1 小时;另一个不用加。这样你想验证时直接往 Prometheus 加一条临时 rule,触发告警后真实走完整个链路,测完删掉 rule 和静默即可。整个流程差不多 2 分钟,比任何 mock 数据都可靠。告警通知这件事,多测几次、踩过几次坑,才能真正在半夜被真实告警叫醒时,不慌不忙地翻个身,拿出手机看上一眼,然后做个正确的判断。