Prometheus邮件报警实战:Alertmanager配置与避坑指南
1. 监控告警链路里为什么邮件报警依然是绕不开的一环做运维监控这些年我见过太多团队把精力全砸在指标采集和看板上Grafana面板做得花里胡哨Prometheus的采集规则写了上百条结果真出故障的时候值班的人压根不知道——因为没人配告警。等用户投诉电话打进来才手忙脚乱去翻监控这种场景我经历过不止一次。Prometheus负责采集和计算但它本身不具备发送通知的能力。真正把指标异常翻译成人能收到的消息的是Alertmanager这个组件。它接收Prometheus推过来的告警做分组、去重、静默、抑制最后通过Receiver把消息投递出去。投递渠道有很多种企业微信、钉钉、Slack、Webhook都行但邮件报警是其中最基础、最通用、也最容易被低估的一种。为什么说它容易被低估因为很多人觉得邮件太慢没人看。但实际生产环境里邮件有几个别的渠道替代不了的优势第一它是正式的通知记录出了问题可以追溯谁在什么时候收到过什么告警邮件里白纸黑字第二它不依赖第三方平台的账号体系和API配额只要有一台能发信的SMTP服务器就能跑第三对于非即时性的告警比如磁盘使用率缓慢增长、证书还有30天到期这类邮件比电话和即时消息更合适不会造成打扰。这篇内容适合谁看如果你已经搭好了Prometheus指标能正常采集但告警还停留在自己上网页看的阶段那这篇就是给你写的。如果你用的是容器化部署或者传统的二进制部署两种场景我都会覆盖到。哪怕你之前完全没碰过Alertmanager跟着走一遍也能把邮件报警跑通。我先把整条链路的逻辑理清楚再一步步拆配置。很多人配邮件报警失败不是某一步写错了而是没搞明白数据在组件之间是怎么流动的所以一出问题就无从下手。理解了链路排查就是顺藤摸瓜的事。2. 邮件报警的整体链路与设计思路拆解2.1 从指标异常到邮件送达中间到底经过了什么先上一张我脑子里一直有的链路图用文字描述清楚。整个流程分四段第一段Prometheus按照scrape_interval周期性抓取目标指标同时按照evaluation_interval周期性执行告警规则rule。规则里写的是PromQL表达式比如up 0或者node_filesystem_avail_bytes / node_filesystem_size_bytes 0.1。当表达式在for指定的持续时间内一直为真Prometheus就把这条告警从inactive推到pending再推到firing。第二段Prometheus把firing状态的告警通过HTTP POST推送到Alertmanager的/api/v2/alerts接口。注意这里推的是告警的原始信息包括labels和annotationsAlertmanager不负责判断告警是否该触发它只负责后续处理。第三段Alertmanager收到告警后按照route树做匹配决定这条告警走哪条分支用哪个receiver。同时做分组group、去重、抑制inhibit、静默silence。分组的意义在于同一台机器上十个磁盘同时告警不应该发十封邮件而是合并成一封。第四段匹配到的receiver如果是email类型Alertmanager就通过配置的SMTP服务器把渲染好的邮件发出去。邮件内容用的是Go template语法可以自定义得相当灵活。提示Prometheus和Alertmanager是两个独立进程各自有独立的配置文件。改完Alertmanager配置要重启或热加载Alertmanager改完Prometheus的rule要重启或热加载Prometheus别改错文件。2.2 为什么选邮件而不是别的渠道以及它的取舍有人会问现在即时通讯这么发达为什么还要折腾邮件我的看法是渠道没有优劣只有场景匹配。邮件报警的核心价值在于异步、留痕、低打扰。异步意味着它不要求接收者立刻响应适合那些需要知道但不紧急的告警。留痕意味着邮件天然是一份审计记录事后复盘时能查到告警的完整内容。低打扰意味着它不会像电话那样半夜把人吵醒适合分级告警里的低级别通知。但邮件也有明显的短板必须提前想清楚。第一是延迟SMTP投递本身可能就有几十秒到几分钟的延迟加上邮件客户端拉取的时间实际到达可能比告警触发晚好几分钟。第二是容易被淹没如果告警量大邮箱很快就被刷屏反而没人看。第三是可能进垃圾箱尤其是自建SMTP发信很容易被收件方判定为垃圾邮件。所以我的设计思路是邮件报警只承载中低优先级的告警高优先级告警必须叠加即时通讯或电话。同时通过分组和抑制把邮件数量压下来保证每封邮件都值得看。这个取舍想清楚了后面的配置才有方向。2.3 配置文件的组织方式与选型考量Alertmanager的配置是一个YAML文件顶层有几个关键字段global、route、receivers、inhibit_rules、templates。我习惯把它们按职责分开理解。global放全局默认值比如SMTP服务器地址、发件人、默认的告警解决超时时间。route是一棵路由树根节点是默认路由子节点按label匹配分流。receivers定义具体的接收方式邮件、Webhook都在这里。inhibit_rules定义抑制规则比如某台机器宕机了它上面所有服务的告警都应该被抑制只发一条机器宕机的告警。templates引入自定义的邮件模板文件。关于SMTP的选型我建议用独立的发信账号不要用个人邮箱。原因很简单个人邮箱的密码一旦写进配置文件泄露风险大而且个人邮箱通常有发信频率限制。用企业邮箱或者专门的发信服务配置独立密码权限可控。如果条件允许SMTP连接一定要走TLS端口用465或587别用25端口的明文。注意Alertmanager配置文件里的密码是明文存储的。如果对安全有要求可以用--config.file配合文件权限控制或者用环境变量注入的方式但Alertmanager原生不支持环境变量替换需要借助外部工具渲染配置。这一点后面会细说。3. 核心配置细节逐项拆解与实操要点3.1 global段SMTP连接的基础参数怎么填global段是整份配置的地基SMTP相关的参数基本都在这里。我拿一个实际能跑通的配置来拆global: smtp_smarthost: smtp.example.com:465 smtp_from: alertmanagerexample.com smtp_auth_username: alertmanagerexample.com smtp_auth_password: your_password_here smtp_require_tls: true resolve_timeout: 5m逐项说。smtp_smarthost是SMTP服务器地址加端口格式是host:port。465端口是SMTPS也就是隐式TLS连接建立时就加密587端口是SMTP加STARTTLS先明文握手再升级加密。这两个端口选哪个取决于你的邮件服务商支持哪种。smtp_require_tls设为true时Alertmanager会强制使用TLS如果服务器不支持就会报错这其实是好事能帮你发现配置问题。smtp_from是发件人地址注意有些SMTP服务器要求发件人地址必须和认证用户名一致否则会被拒绝。smtp_auth_username和smtp_auth_password是认证凭据。resolve_timeout是告警恢复的超时时间如果一条告警在resolve_timeout时间内没有再被推送Alertmanager就认为它已经解决会发送恢复通知。默认是5分钟我一般保持默认。这里有个坑我踩过smtp_from如果写成Alertmanager alertmanagerexample.com这种带显示名的格式某些SMTP服务器会解析失败。稳妥起见先只写纯邮箱地址跑通之后再考虑加显示名。3.2 route段路由树怎么设计才不混乱route是Alertmanager配置里最容易写乱的部分。它是一棵树根节点必须存在子节点通过match或match_re匹配label。我见过有人把所有告警都塞进一个receiver结果邮件里什么都有根本没法看。合理的做法是按严重程度或按业务线分流。route: receiver: default-email group_by: [alertname, cluster, instance] group_wait: 30s group_interval: 5m repeat_interval: 4h routes: - match: severity: critical receiver: critical-email repeat_interval: 1h - match: severity: warning receiver: warning-email repeat_interval: 6hgroup_by决定按哪些label分组。我一般用alertname、cluster、instance三个。这样同一台机器上同一个告警名只会合并成一封邮件。group_wait是首次分组等待时间意思是收到第一条告警后等30秒看还有没有同组的告警进来一起发。group_interval是同一组告警再次发送的间隔。repeat_interval是告警一直没恢复时重复发送的间隔。repeat_interval这个参数特别重要。设太短邮件轰炸设太长值班的人可能忘了这条告警还没处理。我的经验是critical级别设1小时warning级别设6小时info级别设24小时。这样既不会漏也不会烦。提示group_wait不要设太长。有人为了合并更多告警设成5分钟结果紧急告警延迟5分钟才发出来得不偿失。30秒到1分钟是比较合理的区间。3.3 receivers段邮件接收器的完整写法receivers定义具体的接收方式。邮件类型的receiver写法如下receivers: - name: default-email email_configs: - to: ops-teamexample.com send_resolved: true headers: Subject: [{{ .Status | toUpper }}] {{ .CommonLabels.alertname }} - name: critical-email email_configs: - to: oncallexample.com send_resolved: true html: {{ template email.custom.html . }}to是收件人多个收件人用逗号分隔。send_resolved决定告警恢复时是否发送恢复邮件我强烈建议设为true否则你不知道问题什么时候好的。headers可以自定义邮件头比如主题。html指定用自定义的HTML模板渲染邮件正文。这里有个细节to字段如果写多个地址Alertmanager会一次性发给所有人没有抄送和密送的概念。如果需要区分主送和抄送得在SMTP层面或者用模板技巧处理比较麻烦。我的做法是简单粗暴该发给谁就写谁。3.4 邮件模板让告警邮件一眼能看懂默认的邮件模板信息量够但排版一般。我习惯自定义一个HTML模板把关键信息突出显示。模板文件放在Alertmanager能访问到的路径然后在配置里用templates字段引入templates: - /etc/alertmanager/templates/*.tmpl模板用的是Go template语法核心是遍历.Alerts数组把每条告警的labels和annotations渲染出来。一个简化的模板长这样{{ define email.custom.html }} table border1 trth告警名称/thth实例/thth描述/th/tr {{ range .Alerts }} tr td{{ .Labels.alertname }}/td td{{ .Labels.instance }}/td td{{ .Annotations.description }}/td /tr {{ end }} /table {{ end }}.Status是整组告警的状态.CommonLabels是所有告警共有的label.Alerts是告警列表。写模板的时候要注意如果某条告警没有某个label直接引用会渲染成空字符串不会报错但显示出来就是空白所以模板里最好做判空处理。注意模板文件修改后Alertmanager需要重新加载配置才能生效。如果用的是--web.enable-lifecycle可以POST到/-/reload热加载否则要重启进程。4. 完整实操流程从零把邮件报警跑起来4.1 环境准备与前置检查动手之前先确认几件事。第一Prometheus和Alertmanager的版本要匹配一般来说Alertmanager 0.2x版本配Prometheus 2.x都没问题但跨大版本可能有API差异。第二确认服务器能访问SMTP服务器的端口用telnet smtp.example.com 465测一下能连上再往下走。第三准备好发信账号和密码最好先在命令行用swaks或者curl测一下能不能发信排除账号本身的问题。我习惯先单独把Alertmanager跑起来确认它能启动、能访问Web界面默认9093端口再去配Prometheus。这样出问题的时候能快速定位是Alertmanager的问题还是Prometheus推送的问题。4.2 Alertmanager配置文件落地把前面拆解的各个段落拼起来形成一份完整的配置文件。我把它放在/etc/alertmanager/alertmanager.ymlglobal: smtp_smarthost: smtp.example.com:465 smtp_from: alertmanagerexample.com smtp_auth_username: alertmanagerexample.com smtp_auth_password: your_password_here smtp_require_tls: true resolve_timeout: 5m templates: - /etc/alertmanager/templates/*.tmpl route: receiver: default-email group_by: [alertname, cluster, instance] group_wait: 30s group_interval: 5m repeat_interval: 4h routes: - match: severity: critical receiver: critical-email repeat_interval: 1h - match: severity: warning receiver: warning-email repeat_interval: 6h receivers: - name: default-email email_configs: - to: ops-teamexample.com send_resolved: true - name: critical-email email_configs: - to: oncallexample.com send_resolved: true - name: warning-email email_configs: - to: ops-teamexample.com send_resolved: true inhibit_rules: - source_match: severity: critical target_match: severity: warning equal: [alertname, instance]写完用amtool check-config /etc/alertmanager/alertmanager.yml校验语法这个工具是Alertmanager自带的能提前发现大部分格式错误。校验通过再启动。4.3 Prometheus告警规则配置Prometheus这边要做两件事一是配置告警规则文件二是配置Alertmanager地址。规则文件rules/node.yml示例groups: - name: node-alerts rules: - alert: NodeDown expr: up{jobnode} 0 for: 2m labels: severity: critical annotations: summary: 节点 {{ $labels.instance }} 失联 description: 节点 {{ $labels.instance }} 已经超过2分钟无法抓取请立即检查。 - alert: DiskSpaceLow expr: node_filesystem_avail_bytes / node_filesystem_size_bytes 0.15 for: 5m labels: severity: warning annotations: summary: 节点 {{ $labels.instance }} 磁盘空间不足 description: 挂载点 {{ $labels.mountpoint }} 剩余空间低于15%。for字段是告警持续时长表达式为真且持续超过for才真正触发。这个参数能有效过滤抖动别省。labels里的severity就是路由匹配的依据。annotations里的summary和description会出现在邮件里写清楚点值班的人半夜看到也能快速判断。然后在prometheus.yml里加Alertmanager地址alerting: alertmanagers: - static_configs: - targets: [localhost:9093] rule_files: - rules/*.yml改完Prometheus配置用promtool check config prometheus.yml校验再热加载或重启。4.4 端到端验证与效果确认配置都落地后怎么确认整条链路通了我的做法是人为制造一个告警。最简单的办法是停掉一个被监控的node_exporter等for时间过去看邮件有没有到。或者临时把某条规则的阈值改得很苛刻让它立刻触发。验证的时候分三步看。第一步在Prometheus的Web界面/alerts页面确认告警状态从inactive变成pending再变成firing。第二步在Alertmanager的Web界面/#/alerts页面确认告警已经推过来了。第三步查收邮件。如果前两步都正常但邮件没到问题就在SMTP这一段去看Alertmanager的日志通常会有明确的报错。我实测下来最容易出问题的就是SMTP认证和TLS。日志里如果出现authentication failed就是账号密码问题如果出现connection refused或timeout就是网络或端口问题如果出现x509相关的错误就是证书问题。对症下药很快能解决。5. 常见问题排查与避坑经验实录5.1 邮件发不出去的高频原因速查我把这些年遇到的邮件发送失败问题整理成一张表方便对照排查现象可能原因排查方法解决方式日志报认证失败账号密码错误或未开启SMTP服务用swaks命令行测试核对密码确认邮箱已开启SMTP连接超时端口不通或被防火墙拦截telnet测试端口放行端口确认服务器地址TLS握手失败端口与加密方式不匹配查看日志具体报错465配隐式TLS587配STARTTLS邮件进垃圾箱发件域名无SPF/DKIM记录查看收件方垃圾箱配置域名解析记录收件人收不到收件地址拼写错误检查to字段修正地址邮件内容为空模板渲染出错查看Alertmanager日志修正模板语法这张表覆盖了九成以上的问题。我特别想强调TLS端口匹配这一条很多人把587端口配成require_tls: true却忘了STARTTLS的握手方式结果一直失败。记住一个口诀465是一上来就加密587是先聊两句再加密。5.2 告警重复发送与漏发的处理告警重复发送通常是repeat_interval设太短或者group_by没配好导致同一类告警被拆成多组。我的排查思路是先看Alertmanager界面上这条告警被分成了几组如果分组过多就调整group_by把能唯一标识问题的label加进去。如果分组正常但发送频繁就调大repeat_interval。告警漏发一般是group_wait和group_interval配合出了问题或者告警在for时间内就恢复了根本没到firing状态。还有一种情况是抑制规则写得太宽把该发的告警也抑制掉了。排查抑制问题可以在Alertmanager界面上看告警的inhibited by字段能直接看到被哪条规则抑制了。提示调试阶段可以临时把group_wait设成0repeat_interval设成1分钟快速验证链路。验证完再改回合理值别把调试配置带到生产。5.3 密码明文存储的安全处理前面提过Alertmanager配置里的密码是明文。如果服务器有多人共用或者配置文件会被纳入版本管理这就是个隐患。我的处理方式有几种。第一种用文件权限控制把配置文件权限设成600属主是运行Alertmanager的用户。第二种如果用了配置管理工具把密码放在加密的vault里渲染配置时再注入。第三种如果Alertmanager跑在容器里用Secret挂载配置文件避免密码出现在镜像或命令行里。还有一种取巧的办法用amtool或者外部脚本在启动前渲染配置把环境变量替换进去。但这不是Alertmanager原生能力需要自己写启动脚本。我一般推荐前两种简单可靠。5.4 大规模环境下的分组与抑制策略当监控目标上百台机器时邮件报警的策略要重新设计否则邮箱会被淹没。我的经验是三条。第一按业务线或机房分组不同组的告警发给不同的接收人避免所有人都收到所有告警。第二善用抑制规则比如机房级网络故障时抑制该机房下所有主机的告警只发一条网络故障的告警。第三对低优先级告警做聚合比如把磁盘使用率超过80%这类warning聚合成一封日报而不是实时发送。抑制规则的equal字段很关键它指定哪些label必须相同才触发抑制。比如equal: [alertname, instance]表示同一个实例上的同一个告警名才抑制。如果写成equal: [instance]那同一实例上所有告警都会被抑制范围就太大了。这个字段一定要想清楚再写。6. 几个让邮件报警更好用的小技巧配通只是第一步用好才是目的。分享几个我实际用下来觉得有价值的技巧。第一个是给邮件主题加前缀。在headers里把Subject设成[{{ .Status | toUpper }}][{{ .CommonLabels.severity }}] ...这样在邮箱列表里一眼就能看出是告警还是恢复是critical还是warning不用点开。配合邮箱的过滤规则可以把critical的邮件自动标星或转发到手机。第二个是善用send_resolved。很多人只关心告警不关心恢复结果问题解决了也不知道。开启恢复通知后你能清楚地看到每个问题的生命周期对复盘很有帮助。恢复邮件的主题里Status是resolved和告警邮件区分得开。第三个是定期审查告警规则。我每个季度会过一遍所有告警规则看哪些从来没触发过可能是阈值太宽松哪些触发太频繁可能是阈值太敏感。邮件报警的价值在于每条都值得看如果某条规则天天发邮件但没人处理那它就是在制造噪音该调整或删掉。第四个是给邮件模板加上告警的链接。在annotations里放一个指向Grafana面板或Prometheus查询的URL值班的人点一下就能看到相关指标比只看文字描述高效得多。这个URL可以用模板变量动态拼接比如{{ $labels.instance }}。最后说一个我踩过的坑。有一次我把Alertmanager的配置改完直接重启结果发现之前正在pending的告警全丢了因为重启会清空内存里的告警状态。后来我养成了习惯改配置尽量用热加载实在要重启就选在告警低峰期。这个细节不起眼但真出事的时候会让人很被动。