ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Alertmanager邮件报警配置实战:从原理到落地,解决告警最后一公里

Alertmanager邮件报警配置实战:从原理到落地,解决告警最后一公里

1. 为什么告警这件事值得单独拎出来聊

监控系统搭起来只是第一步,真正让一套监控产生价值的,是告警能不能在故障发生的第一时间,准确、清晰地送到该看到它的人手里。我见过太多团队,Prometheus 的指标采集做得漂漂亮亮,Grafana 面板也画得赏心悦目,结果一出问题全靠用户反馈,监控形同虚设。问题往往就卡在最后这一公里——告警发不出去,或者发出去了没人看,或者看的人被淹没在一堆噪音里。

Alertmanager 就是 Prometheus 生态里专门负责这最后一公里的组件。它接收 Prometheus 推过来的告警,做去重、分组、抑制、静默,最后通过邮件、即时通讯工具等渠道发出去。邮件报警是最经典、最通用的一种方式,几乎每家公司都有邮件系统,不需要额外申请第三方服务,配置一次就能长期用。但恰恰因为它看起来简单,很多人在配置时踩了坑:邮件发不出去、告警重复轰炸、关键告警被淹没、TLS 握手失败等等。

这篇内容我会把 Alertmanager 邮件报警从原理到落地完整拆一遍,包括配置文件的每一段为什么这么写、参数怎么算、常见故障怎么排查。适合已经搭好 Prometheus 但告警还没跑通的运维和开发同学,也适合想把现有告警质量再提一档的老手。整套方案基于常见的开源组件组合,不涉及任何特定厂商,你可以直接抄作业。

2. 告警链路整体设计与核心思路拆解

2.1 从指标到邮件,中间到底经过了什么

很多人配邮件报警时是懵的,因为不清楚数据流。我先把这条链路捋清楚,后面配置的时候你才知道每一段在干什么。

整条链路是这样的:Prometheus 按照rule_files里定义的告警规则,周期性地对采集到的指标做评估。当某个表达式持续满足条件超过for指定的时长,Prometheus 就生成一条告警,状态从inactive变成pending,再变成firing。处于firing状态的告警会被推送到 Alertmanager。Alertmanager 收到后,先做一轮处理:相同内容的告警去重合并,相关的告警按group_by分组,被inhibit_rules命中的告警抑制掉,处于静默期的告警丢弃。剩下的告警经过路由树route匹配,找到对应的receiver,最后通过 receiver 里配置的邮件通道发出去。

理解这条链路的意义在于:邮件发不出来,问题可能出在链路的任何一环。可能是 Prometheus 根本没产生告警,可能是 Alertmanager 没收到,可能是路由没匹配上,也可能是邮件通道本身的问题。排查的时候要顺着链路一段段验证,而不是盯着邮件配置死磕。

2.2 为什么选邮件,以及它的边界在哪

邮件报警的优势很明确:基础设施现成、成本几乎为零、有天然的存档和检索能力、适合发送需要留痕的告警。对于中小团队或者作为告警的兜底渠道,邮件是非常务实的选择。

但邮件的短板也要心里有数。第一是时效性,邮件从发出到被人看到,中间可能隔着几分钟甚至更久,不适合对响应速度要求极高的场景。第二是容易被忽略,如果告警量大,收件箱很快就会被淹没,人会产生告警疲劳。第三是移动端体验一般,半夜收到邮件爬起来处理,体验远不如即时通讯工具。

所以我的建议是:邮件作为基础渠道和存档渠道保留,同时根据团队实际情况叠加即时通讯工具作为快速响应渠道。两者不是替代关系,而是互补。这篇内容聚焦邮件,但你在设计告警体系时要把这个分层思路考虑进去。

2.3 分组、抑制、静默:三个让告警不扰民的关键机制

Alertmanager 最核心的价值不是"发告警",而是"聪明地发告警"。如果只是把 Prometheus 的告警原样转发,那要 Alertmanager 干嘛?真正让它有价值的是三个机制。

分组(grouping)解决的是告警风暴问题。想象一下数据库挂了,依赖它的十几个服务同时报错,如果每条告警都单独发一封邮件,你的收件箱瞬间爆炸。分组机制把同一类告警合并成一封邮件,group_by里指定的标签相同的告警会被归到一组,group_wait控制等待多久凑够一批再发,group_interval控制同一组后续有新告警时多久发一次,repeat_interval控制同一组告警多久重复提醒一次。

抑制(inhibition)解决的是因果告警问题。当一台主机宕机时,它上面所有服务的告警其实都是同一个根因导致的,没必要全部发出来。抑制规则可以配置成"当某条严重告警触发时,抑制掉由它引起的其他告警"。

静默(silence)解决的是计划内维护问题。你要做一次发布或者迁移,期间必然产生告警,提前配置静默就能避免误报。静默可以通过 Alertmanager 的 Web 界面临时创建,也可以走 API。

这三个机制配合好了,告警的噪音能降一个数量级。很多人抱怨告警太多,其实不是告警本身多,而是没用上这些机制。

3. 核心配置细节逐段拆解与实操要点

3.1 Alertmanager 主配置文件的骨架

Alertmanager 的配置文件默认是 YAML 格式,启动时通过--config.file指定。整个文件由几个顶层字段组成:global、route、inhibit_rules、receivers、templates。我先把骨架列出来,然后逐段讲。

global: resolve_timeout: 5m smtp_smarthost: 'smtp.example.com:587' smtp_from: 'alertmanager@example.com' smtp_auth_username: 'alertmanager@example.com' smtp_auth_password: 'your_password_or_token' smtp_require_tls: true route: receiver: 'default-email' group_by: ['alertname', 'cluster', 'service'] group_wait: 30s group_interval: 5m repeat_interval: 4h routes: - match: severity: critical receiver: 'critical-email' repeat_interval: 1h receivers: - name: 'default-email' email_configs: - to: 'ops-team@example.com' send_resolved: true - name: 'critical-email' email_configs: - to: 'oncall@example.com' send_resolved: true inhibit_rules: - source_match: severity: 'critical' target_match: severity: 'warning' equal: ['alertname', 'instance']

这个骨架已经能跑起来了,但每一段都有讲究,下面拆开说。

3.2 global 段:SMTP 参数怎么填才不出错

global段里的 SMTP 配置是邮件能否发出去的关键。这里几个参数我逐个说。

smtp_smarthost是邮件服务器的地址和端口。这里有个坑:端口选 465 还是 587?465 是 SMTPS,从连接建立就是加密的;587 是 SMTP 加 STARTTLS,先明文连接再升级为加密。Alertmanager 的smtp_require_tls默认是 true,配合 587 端口使用 STARTTLS 是最常见的组合。如果你用 465,需要确认 Alertmanager 版本是否支持隐式 TLS,老版本可能有问题。我一般推荐 587。

smtp_from是发件人地址,很多邮件服务器会校验这个地址必须和认证账号一致,否则拒收。所以最好和smtp_auth_username保持一致。

smtp_auth_password这里要特别注意:如果你用的是主流邮件服务商,很多已经不支持直接用登录密码做 SMTP 认证了,需要单独生成一个授权码或应用专用密码。这个密码通常是一串随机字符,和你的登录密码不同。我第一次配的时候就是卡在这里,一直报认证失败,后来才发现要用授权码。

smtp_require_tls建议保持 true。如果内网邮件服务器不支持 TLS,才设为 false,但生产环境强烈建议用 TLS。

还有一个容易被忽略的参数是smtp_hello,它指定 EHLO 时发送的主机名。某些严格的邮件服务器会校验这个值,如果 Alertmanager 所在主机的主机名不规范,可能被拒。遇到奇怪的拒收问题时可以显式设置成你的域名。

3.3 route 段:路由树是告警分发的指挥中心

route段决定了告警往哪发。它是一棵树,根节点是默认路由,子节点通过match或match_re匹配特定告警。

group_by是最影响体验的参数。它决定了哪些告警会被合并到一封邮件里。常见的做法是按alertname、cluster、service、severity这些维度分组。分得太细,邮件就多;分得太粗,一封邮件里塞了几十条不相关的告警,读起来费劲。我的经验是:alertname必加,这样同类告警会合并;再加上业务维度比如service或job,让不同服务的告警分开。

group_wait是首次发送一组告警前的等待时间,默认 30 秒。它的作用是给同一批告警一点聚集的时间,避免刚发一封又来一封。如果告警产生很密集,可以适当调大,比如 1 分钟。

group_interval是同一组告警在已有通知发出后,又有新告警加入时,多久发一次更新。默认 5 分钟比较合理。

repeat_interval是同一组告警在没有任何变化的情况下,多久重复提醒一次。这个参数直接决定了你会不会被骚扰。默认 4 小时,对于 critical 告警可以缩短到 1 小时,对于 warning 可以拉长到 12 小时甚至 24 小时。我见过有人设成 5 分钟,结果一天收到几百封重复邮件,直接把邮件渠道废掉了。

子路由的匹配用match做精确匹配,match_re做正则匹配。注意match是精确相等,不是包含。比如severity: critical只会匹配 severity 标签值恰好是 critical 的告警。如果要匹配多个值,用match_re配合正则。

3.4 receivers 段:邮件接收方的细节配置

receivers定义了告警最终发到哪里。每个 receiver 有个名字,被 route 引用。邮件类型的 receiver 用email_configs配置。

to字段是收件人,多个收件人用逗号分隔。这里有个实用技巧:可以配合headers字段设置Subject,让邮件标题更清晰。默认的邮件标题格式是[FIRING:数量] 告警名 (标签),信息量够但不够醒目。你可以自定义成带环境标识的格式,方便在收件箱里一眼区分。

send_resolved控制告警恢复时是否发送恢复邮件。这个强烈建议设为 true。没有恢复邮件,你永远不知道告警是好了还是还在持续,只能去 Prometheus 界面查,非常低效。

html字段可以指定邮件正文用 HTML 模板,配合templates段加载自定义模板文件,能让邮件排版好看很多。默认的纯文本邮件信息密度低,关键信息不突出。花点时间做个 HTML 模板,把告警名、严重级别、实例、描述、发生时间用表格排出来,阅读效率提升明显。

require_tls是 receiver 级别的 TLS 开关,一般不用单独设,继承 global 即可。

3.5 inhibit_rules 段:抑制规则怎么写才不误伤

抑制规则的核心逻辑是:当 source 匹配的告警存在时,target 匹配的告警被抑制。equal字段指定哪些标签必须相等才触发抑制。

举个典型场景:主机宕机告警(alertname: HostDown,severity: critical)触发时,抑制这台主机上所有其他告警。配置如下:

inhibit_rules: - source_match: alertname: 'HostDown' target_match_re: alertname: '.+' equal: ['instance']

equal: ['instance']保证只有同一台主机的告警被抑制,不会误伤其他主机。

这里最容易踩的坑是equal标签选错。如果选了alertname,那只有同名告警才会被抑制,基本没意义。如果选了一个很多告警都没有的标签,抑制也不会生效,因为标签缺失时视为不相等。所以equal里的标签必须是 source 和 target 都有的,且能唯一标识因果关系的维度。

另一个坑是抑制规则写得太宽泛,把该发的告警也抑制了。比如用severity: critical作为 source,severity: warning作为 target,equal只写instance,那只要有一个 critical 告警,同实例的所有 warning 都被压掉,可能掩盖真实问题。抑制规则要保守,宁可多发几条,也别漏掉关键信息。

4. 完整实操流程与关键环节实现

4.1 环境准备与版本确认

动手之前先确认组件版本,不同版本的配置字段有差异。Alertmanager 0.22 之后配置格式基本稳定,但一些细节比如smtp_require_tls的默认值、match_re的语法在不同版本间有过调整。用alertmanager --version确认版本,对照官方文档核对字段。

Prometheus 侧要确认alerting段配置了 Alertmanager 的地址:

alerting: alertmanagers: - static_configs: - targets: - 'alertmanager:9093'

以及rule_files指向了告警规则文件。这两处没配好,Alertmanager 根本收不到告警,邮件配置再对也没用。

4.2 编写告警规则文件

告警规则文件是 Prometheus 格式的 YAML,用groups组织。每条规则包含alert(告警名)、expr(PromQL 表达式)、for(持续时间)、labels(附加标签)、annotations(描述信息)。

groups: - name: host-alerts rules: - alert: HostHighCpuLoad expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80 for: 5m labels: severity: warning service: host annotations: summary: "主机 CPU 负载过高 (实例 {{ $labels.instance }})" description: "CPU 使用率已持续 5 分钟超过 80%,当前值 {{ $value }}%。"

for字段很关键,它避免了瞬时抖动触发告警。CPU 偶尔飙一下很正常,持续 5 分钟才值得关注。这个值要根据指标特性调整,磁盘空间这种缓慢变化的可以设长一点,服务存活这种要快速响应的可以设短一点。

annotations里的summary和description会出现在邮件正文里,写清楚点,让收到邮件的人不看面板就知道发生了什么。{{ $labels.xxx }}和{{ $value }}是模板变量,会被替换成实际值。

4.3 配置 Alertmanager 并启动

把前面讲的配置写进alertmanager.yml,然后用amtool check-config alertmanager.yml做语法校验。这个命令能提前发现大部分格式错误,比启动后看日志快得多。

启动命令:

alertmanager --config.file=alertmanager.yml \ --storage.path=/var/lib/alertmanager \ --web.listen-address=:9093

--storage.path指定数据目录,Alertmanager 需要持久化静默状态和通知记录,这个目录要保证可写且不会被随意清理。

启动后访问http://<host>:9093,能看到 Web 界面。在Status页面可以查看当前配置,确认加载的是你期望的内容。

4.4 端到端验证:从触发到收邮件

配置完别急着等真实故障,主动造一个告警验证链路。最简单的方法是临时写一条必然触发的规则,比如expr: vector(1) > 0,for: 0m。这条规则会立即触发。

触发后按顺序检查:

  1. Prometheus 的Alerts页面,确认告警状态是firing。
  2. Alertmanager 的 Web 界面,确认收到了这条告警。
  3. 检查邮件是否到达收件箱,注意看垃圾邮件文件夹。

如果 Prometheus 有告警但 Alertmanager 没有,检查alerting段配置和网络连通性。如果 Alertmanager 有告警但邮件没发出,看 Alertmanager 日志里的 SMTP 报错信息。

4.5 邮件模板定制

默认邮件模板信息够用但不够好看。自定义模板需要两步:在配置里加templates段指向模板文件,在 receiver 里用html字段指定用哪个模板。

templates: - '/etc/alertmanager/templates/*.tmpl'

模板文件用 Go template 语法。一个实用的 HTML 模板会把告警按严重级别用不同颜色标注,把关键字段用表格列出。这块内容展开能写很多,核心思路是:让收件人在不点开任何链接的情况下,3 秒内判断出"这是什么级别的告警、影响什么、要不要马上处理"。

5. 常见问题与排查技巧实录

5.1 邮件发送失败排查速查表

现象可能原因排查方法
日志报authentication failed密码错误或用了登录密码而非授权码确认是否需要用应用专用密码,重新生成
日志报connection refused地址或端口错误,或防火墙拦截telnet 测试端口连通性
日志报TLS handshake failedTLS 配置不匹配尝试调整smtp_require_tls,确认端口与加密方式匹配
日志报sender address rejected发件人地址不被服务器接受确认smtp_from与认证账号一致
邮件进了垃圾箱发件域名缺少 SPF/DKIM 记录联系邮件管理员配置域名认证
告警触发但无邮件路由未匹配或 receiver 配置错误用amtool检查路由匹配结果

5.2 告警重复轰炸怎么治

重复告警基本是repeat_interval设太短,或者group_by没配好导致同一问题被拆成多条。先检查repeat_interval,critical 设 1 小时、warning 设 12 小时是比较舒服的节奏。再看group_by,确保同类告警能合并。如果同一台机器的同一个问题因为标签不同被拆成多条,检查是不是某个标签值在变化,比如instance里带了端口号导致每次都不一样。

还有一种情况是告警在 firing 和 resolved 之间反复横跳,这叫告警抖动。根源通常是for设太短,或者阈值卡在临界值附近。解决办法是加长for,或者用avg_over_time之类的函数平滑指标。

5.3 关键告警被淹没的处理思路

如果 critical 告警经常被 warning 淹没,说明路由没分层。正确做法是给 critical 单独配一个 receiver,发给专门的 oncall 邮箱,和日常 warning 分开。同时用抑制规则,让 critical 触发时压掉相关的 warning。

另一个技巧是利用邮件客户端的过滤器,把来自 Alertmanager 的 critical 邮件自动打标签或标星,这样在收件箱里一眼就能看到。这个在客户端侧配置,不涉及 Alertmanager,但效果立竿见影。

5.4 我踩过的几个坑

第一个坑是smtp_require_tls和端口不匹配。我用 465 端口配了smtp_require_tls: true,结果一直握手失败,换成 587 就好了。后来查文档才明白 465 是隐式 TLS,和 STARTTLS 是两套机制。

第二个坑是group_by里放了instance,导致每台机器的告警都单独成组,邮件数量爆炸。后来把instance从group_by里去掉,只在annotations里展示,邮件数量立刻降下来。

第三个坑是测试时用vector(1)造告警,忘了删,结果这条规则一直在触发,半夜收到一堆邮件。所以测试规则一定要记得清理,或者给它加个明显的标签方便识别。

第四个坑是邮件模板里的变量名写错,导致邮件正文显示{{ $labels.xxx }}原样输出。模板变量区分大小写,$labels和$Labels不一样,写的时候要仔细。

6. 告警质量持续优化的几个方向

配置跑通只是起点,告警质量是需要持续打磨的。我分享几个实际用下来有效的方向。

第一,定期回顾告警。每周花十分钟看看这周发了哪些告警,哪些是误报,哪些是重复,哪些是没人处理的。误报的调整阈值或加长for,重复的优化分组,没人处理的要么是告警不重要该删,要么是流程有问题该改。

第二,给告警分级。不是所有告警都值得半夜叫人。我一般分三级:critical 立即通知 oncall,warning 工作时间通知,info 只记录不通知。分级标准要和团队达成共识,不能一个人拍脑袋。

第三,告警描述要写清楚"怎么办"。很多告警只说了"什么坏了",没说"怎么处理"。在annotations里加上处理建议或者排查链接,能大幅缩短响应时间。比如磁盘满了,直接写上清理命令或者扩容流程的链接。

第四,定期测试告警链路。邮件服务器会变、密码会过期、网络策略会调整,告警链路不是配一次就一劳永逸的。我习惯每月主动触发一次测试告警,确认整条链路还通。这个习惯帮我提前发现过好几次密码过期的问题,避免了真实故障时告警发不出去的尴尬。

第五,控制告警总量。有个经验值可以参考:一个健康的系统,oncall 人员每天收到的告警邮件不应该超过 10 封。超过这个数,要么是系统真有问题,要么是告警配置太敏感。前者该修系统,后者该调告警。告警疲劳是监控体系最大的敌人,一旦人对告警麻木了,再完善的监控也白搭。

这套邮件报警方案我在几个不同规模的团队都用过,从几个人的小团队到几十人的运维组,核心配置基本一致,差别主要在路由分层和告警分级的粒度上。小团队可以简单点,一个 receiver 搞定;大团队就要把路由树设计得细一些,配合值班表做轮转。但不管规模大小,先把链路跑通、再逐步优化质量这个路径是通用的。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进