高可用与运维监控
前面几篇把 Alertmanager 的配置玩法讲完了:路由怎么分流、接收器怎么对接、抑制和静默怎么降噪。但这些都建立在「Alertmanager 自己活着」的前提上。这篇解决最后一块短板:让 Alertmanager 自己也高可用,并且把它当成一个普通服务一样监控、备份、升级。
文中示例基于 Alertmanager 0.27+、Prometheus 2.5x / 3.x。部署形态以二进制 + systemd 为例,Docker 和 Kubernetes 环境对照转换即可。
为什么要 HA
先想清楚一个问题:Alertmanager 挂了会发生什么?
Prometheus 侧的设计是「发出去就不管」——告警评估照常进行,往 Alertmanager 推送失败了就报错重试,但告警通知这条链路整体是断的。你不会有任何感觉:群里安安静静,值班手机一声不响,看起来是「系统很稳定」,实际上是通知通道哑了。
更讽刺的是,监控系统自身往往没有「Alertmanager 挂了」的告警——谁来告「告警系统的警」?这是一个经典的自监控盲区。很多团队第一次意识到这个问题,都是在某次事故复盘时发现「告警其实触发了,但那天 Alertmanager 正好挂了」。
所以 Alertmanager 的高可用不是锦上添花,而是告警链路的最后一公里。好在它的 HA 方案很成熟,也不复杂:多实例 + gossip 同步,没有外部依赖,不需要共享存储。
gossip 集群原理
Alertmanager 集群用的是 gossip 协议(基于 HashiCorp 的 memberlist 库),和 Consul、Redis Cluster 的节点间通信是同一类思路:
- 每个实例启动时通过
--cluster.peer参数认识几个同伴,之后靠 gossip 协议周期性互发信息,逐步感知到整个集群的全部成员。 - 实例之间同步两样东西:通知日志(nflog)和静默状态(silences)。
- nflog 记录「哪条告警、哪个接收器、已经发到哪一步了」。它的作用是回答「这条通知发过了没有」——这正是集群下「发过的通知不重复发」的依据。
- silences 是抑制、静默与免打扰一篇创建的静默规则,创建后 gossip 到所有实例,任何一个实例收到告警都会遵守。
- 集群是无主的:没有 leader 选举,没有主从之分,每个实例地位平等,都能独立接收告警、独立发通知。
这个设计有两个直接推论:
- 不需要共享存储。nflog 和 silences 每个实例本地都有一份,靠 gossip 对齐,重启后从同伴那里拉回来。集群里没有「数据面单点」。
- 去重是「尽力而为」的。Prometheus 把同一条告警推给所有 Alertmanager 实例,各实例先把自己要发的通知在集群里广播一遍「我要发了」,收到广播的实例就知道不用重复发。这个协调基于 gossip,存在秒级的传播窗口——所以集群保证的是「绝大多数情况只发一份」,极端时序下(恰好同时触发、传播还没到位)可能收到重复通知。这是用最终一致性换来的简单可靠,要心里有数。
gossip 只同步运行时状态(nflog、silences),不同步配置。route、receivers、inhibit_rules 这些配置文件必须保证所有实例内容一致——配置不一致的集群会各发各的,路由行为不可预测。配置下发交给配置管理工具(Ansible、ConfigMap)统一做。
3 实例集群部署
假设三台机器:10.0.0.11、10.0.0.12、10.0.0.13。Alertmanager 的 Web/API 端口默认 9093,集群 gossip 通信端口默认 9094。
第一台(10.0.0.11)的启动命令:
alertmanager \
--config.file=/etc/alertmanager/alertmanager.yml \
--storage.path=/var/lib/alertmanager \
--web.external-url=https://alertmanager.example.com \
--cluster.listen-address=0.0.0.0:9094 \
--cluster.advertise-address=10.0.0.11:9094 \
--cluster.peer=10.0.0.12:9094 \
--cluster.peer=10.0.0.13:9094
第二台(10.0.0.12)改两处——advertise 地址换成自己,peer 指向另外两台:
alertmanager \
--config.file=/etc/alertmanager/alertmanager.yml \
--storage.path=/var/lib/alertmanager \
--web.external-url=https://alertmanager.example.com \
--cluster.listen-address=0.0.0.0:9094 \
--cluster.advertise-address=10.0.0.12:9094 \
--cluster.peer=10.0.0.11:9094 \
--cluster.peer=10.0.0.13:9094
第三台(10.0.0.13)依此类推。几个参数的含义:
--cluster.listen-address:gossip 监听地址,空字符串表示单机模式(不组集群)。--cluster.advertise-address:告诉其他实例「连我这个地址」,不写默认取 listen 地址;NAT 或容器网络里必须显式指定成对端可达的地址。--cluster.peer:同伴地址,格式 host:port(不写端口默认 9094)。不需要把所有实例都列全,写一两个能连上的就行,gossip 会自动发现剩下的;但实践中建议配全,首台启动的同伴挂了也能加入。
gossip 走的是 TCP + UDP 双协议(memberlist 的特性),安全组/防火墙要把 9094 的 TCP 和 UDP 都放行,只放 TCP 会出现「偶尔能同步、偶尔不同步」的灵异现象。
Docker Compose 版
同样三实例的 Compose 写法(核心参数和上面一致):
services:
alertmanager-1:
image: prom/alertmanager:v0.27.0
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
- '--storage.path=/alertmanager'
- '--cluster.listen-address=0.0.0.0:9094'
- '--cluster.advertise-address=alertmanager-1:9094'
- '--cluster.peer=alertmanager-2:9094'
- '--cluster.peer=alertmanager-3:9094'
ports:
- '9093:9093'
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
- am1-data:/alertmanager
alertmanager-2:
image: prom/alertmanager:v0.27.0
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
- '--storage.path=/alertmanager'
- '--cluster.listen-address=0.0.0.0:9094'
- '--cluster.advertise-address=alertmanager-2:9094'
- '--cluster.peer=alertmanager-1:9094'
- '--cluster.peer=alertmanager-3:9094'
ports:
- '9094:9093'
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
- am2-data:/alertmanager
alertmanager-3:
image: prom/alertmanager:v0.27.0
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
- '--storage.path=/alertmanager'
- '--cluster.listen-address=0.0.0.0:9094'
- '--cluster.advertise-address=alertmanager-3:9094'
- '--cluster.peer=alertmanager-1:9094'
- '--cluster.peer=alertmanager-2:9094'
ports:
- '9095:9093'
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
- am3-data:/alertmanager
volumes:
am1-data:
am2-data:
am3-data:
Compose 版适合本地验证和测试环境,生产上还是建议分散到不同机器/不同可用区——三个容器跑在同一台宿主机上,宿主机一挂全完,HA 了个寂寞。
Prometheus 侧配置
Prometheus 把所有 Alertmanager 实例都配上,它会给每个实例都推送一遍告警(去重是 Alertmanager 集群的事):
alerting:
alertmanagers:
- static_configs:
- targets:
- 10.0.0.11:9093
- 10.0.0.12:9093
- 10.0.0.13:9093
单个实例挂了,Prometheus 对它的推送失败,但其余实例照常接收,通知链路不断。Alertmanager 侧的配置发现和超时参数详见告警规则与 Alertmanager一篇。
验证集群状态
部署完先确认集群真的组起来了,别凭「启动没报错」就下结论。
看 API 里的 cluster 段
任意一个实例的 /api/v2/status 会返回集群视图:
curl -s http://10.0.0.11:9093/api/v2/status | jq '.cluster'
期望输出三个 peer 都在、状态正常:
{
"status": "ready",
"peers": [
{ "name": "01JXX...", "address": "10.0.0.11:9094" },
{ "name": "01JXY...", "address": "10.0.0.12:9094" },
{ "name": "01JXZ...", "address": "10.0.0.13:9094" }
]
}
Web UI 的 Status 页面也能看到同样的信息。每个实例都查一遍,确认三台看到的都是三个 peer——某台只看到一两个,说明它和其余实例之间网络不通(多半是 9094 没放行)。
杀一个实例,验证不丢不重
集群不是「起了就行」,要实际演练一次故障:
- 制造一条持续触发的测试告警(比如临时把某条规则的阈值改低)。
- 确认收到了通知,记下通知时间。
- 杀掉一个 Alertmanager 实例:
systemctl stop alertmanager或docker stop alertmanager-1。 - 观察:通知照常收到(剩余实例接管),且没有因为实例数变化而收到重复通知。
- 把实例拉回来,观察它重新加入集群,nflog 和 silences 从同伴同步回来——可以在它下线期间建一条静默,上线后确认这条静默出现在它的 Status 页面里。
这个演练每季度做一次,和「杀 Prometheus 副本」一样列入常规混沌工程清单。
关键指标
Alertmanager 在 9093 端口暴露 /metrics,Prometheus 抓起来就是自监控的数据源。
接入抓取
scrape_configs:
- job_name: alertmanager
static_configs:
- targets:
- 10.0.0.11:9093
- 10.0.0.12:9093
- 10.0.0.13:9093
值得盯的指标
| 指标 | 含义 | 关注点 |
|---|---|---|
alertmanager_notifications_total | 各接收器累计发送成功的通知数 | 按 integration 标签分组看发送量趋势 |
alertmanager_notifications_failed_total | 各接收器累计发送失败数 | 最重要,失败即通知没送达 |
alertmanager_cluster_members | 当前实例认为的集群成员数 | 应该恒等于实例总数,掉一个就是集群有问题 |
alertmanager_config_last_reload_successful | 最近一次配置重载是否成功(1/0) | 为 0 说明有人推了份坏配置 |
alertmanager_silences | 当前生效的静默数量 | 静默数暴涨可能是有人批量捂嘴 |
必备告警规则
回答「谁来告告警的警」,至少配这三条:
groups:
- name: alertmanager-self-monitoring
rules:
# Alertmanager 实例挂了
- alert: AlertmanagerDown
expr: up{job="alertmanager"} == 0
for: 3m
labels:
severity: critical
annotations:
summary: Alertmanager 实例 {{ $labels.instance }} 已下线
description: 告警通知链路有断点风险,立即检查该实例。
# 集群成员数不符合预期(假集群 / 脑裂信号)
- alert: AlertmanagerClusterIncomplete
expr: alertmanager_cluster_members < 3
for: 5m
labels:
severity: critical
annotations:
summary: Alertmanager 集群成员不完整
description: 实例 {{ $labels.instance }} 只看到 {{ $value }} 个成员,预期 3 个,检查 9094 端口连通性。
# 通知失败率突增(按接收器/集成类型分组)
- alert: AlertmanagerNotificationFailing
expr: |
rate(alertmanager_notifications_failed_total[5m])
/
rate(alertmanager_notifications_total[5m]) > 0.1
for: 10m
labels:
severity: warning
annotations:
summary: 通知失败率超过 10%
description: 接收器 {{ $labels.integration }} 的通知失败率异常,检查对应渠道(SMTP / webhook / 网关)是否可用。
AlertmanagerDown 这条告警本身也要经过 Alertmanager 才能发出来——如果三个实例全挂了,它照样哑。兜底做法是配一个独立的死信开关(dead man's switch):一条永远触发的告警,经由 Alertmanager 发给外部健康检查服务(如 Healthchecks.io),外部服务收不到心跳就电话通知。这样整条链路全断也有人知道。
备份与升级
数据目录里有什么
--storage.path 指向的数据目录内容很简单:
/var/lib/alertmanager/
├── nflog # 通知日志:哪条告警发给了谁、发到哪一步
└── silences # 静默规则的快照
两个文件都是 gob 编码的二进制快照,进程运行时周期性落盘。备份策略:
- 平时不用特意备份。这两个文件都是运行时状态,丢了的影响是「刚发过的通知可能再发一遍」和「静默没了」——难受但不致命。集群本身就有冗余:一个实例数据目录清空重启,会从同伴 gossip 同步回来。
- 真要备份(比如单机部署、或合规要求),直接打包整个数据目录即可:
tar czf alertmanager-backup-$(date +%F).tar.gz -C /var/lib/alertmanager .
恢复就是停服务、解压回去、启动。注意 silences 有过期时间,恢复几个月前的备份没意义。
真正需要版本管理的是配置文件:alertmanager.yml、模板文件,用 Git 管起来,比备份数据目录重要得多。
升级注意事项
Alertmanager 升级本身不复杂(换二进制、滚动重启),但有两件事必须先看:
- gossip 协议兼容性。滚动升级期间集群里会同时存在新旧两个版本。官方不保证跨大版本的 gossip 协议兼容,升级前先看 changelog 里有没有 cluster 相关的 breaking change。稳妥做法:逐台滚动,升一台验证一台(看
/api/v2/status的 peers 是否齐全),全部升完前不要再做第二轮变更。 - 配置和行为的变更。Alertmanager 偶尔会在小版本里调整配置字段(比如 0.27 起老版
match/match_re被matchers取代并标记废弃,老语法还能用但建议迁移)和默认行为。changelog 里搜config、route、notify关键词,重点看 breaking changes 小节。
升级顺序建议:先在测试环境跑一次完整通知链路验证,生产环境按「一台 → 观察半天 → 其余」的节奏滚。
常见坑
集群脑裂导致重复通知
网络分区时(比如跨可用区专线抖动),集群被切成两半。每一半都认为「另一半挂了」,各自独立发通知——用户会收到重复通知,每个分区发一份。这不是 bug,是 gossip 最终一致性的固有代价。应对:
- Alertmanager 实例尽量部署在网络质量好的同机房/同区域,跨可用区部署时接受偶发重复。
- 用上面的 AlertmanagerClusterIncomplete 告警及时发现分区,分区期间人工知会值班「可能收到重复通知,别慌」。
实例时间不同步
nflog 和 silences 都带时间戳:静默有起止时间,通知日志靠时间判断「这条通知是不是刚发过」。实例间时钟差几分钟,就会出现「静默在一台生效、另一台还没生效」「该重发的没重发」这类怪问题。所有实例必须配 NTP 校时,这是 gossip 类集群的通用前提。顺手把 node_exporter 的 node_timex_offset_seconds 也纳入监控。
peer 地址写错导致「假集群」
最经典的部署错误:三台实例的 --cluster.peer 各写各的,互相连不上,但每台都启动正常、日志看起来也没大问题,各自以为自己是「只有一个成员的集群」。表现是:通知重复发三份、静默只在创建它的那台生效。
识别方法很简单——每个实例查一次集群成员数:
curl -s http://10.0.0.11:9093/api/v2/status | jq '.cluster.peers | length'
不是 3 就是假的。这也是为什么 AlertmanagerClusterIncomplete 告警是必备项。排查方向按序:peer 地址拼写 → 9094 端口防火墙(TCP 和 UDP)→ advertise 地址是否对端可达。
小结
本篇把 Alertmanager 从「单点玩具」补成了「生产可用」:HA 用 gossip 集群,无中心、无共享存储,nflog 和 silences 在实例间同步,「发过的通知不重复发」靠它;部署上三台起步、Prometheus 配全所有实例、9094 端口 TCP/UDP 双放行;运维上把 alertmanager_notifications_failed_total、alertmanager_cluster_members、配置重载状态这几个指标抓起来,配上「实例下线 + 成员不完整 + 失败率突增」三条自监控告警,再加一个外部死信开关兜底;升级前看 changelog 的 gossip 协议和配置变更,逐台滚动。
回头看整个系列:核心概念与架构讲了告警怎么流转,路由与接收器讲了告警发给谁,抑制、静默与免打扰讲了怎么降噪,本篇讲了怎么让它自己不成为单点。剩下最后一块——通知发出去的内容长什么样、怎么写得好——交给下一篇。