高可用性
高可用性
Alertmanager 支持通过配置创建一个高可用集群。本文档介绍了 HA 机制的工作原理、设计目标以及运维注意事项。
设计目标
Alertmanager 的高可用实现基于三个核心原则:
- 统一视图与管理 - 可以从任何集群成员查看和管理静默规则 (silences) 和告警,从而提供统一的运维体验。
- 通过“故障开放 (fail open)”应对集群脑裂 - 在网络分区期间,Alertmanager 倾向于发送重复告警,而不是漏掉关键告警。
- 至少一次 (At-least-once) 交付 - 系统保证告警至少被发送一次,这符合“故障开放”的理念。
这些目标优先考虑运维的可靠性和告警交付,而不是严格的“仅且一次”语义。
架构概述
Alertmanager 集群由多个通过 Gossip 协议进行通信的 Alertmanager 实例组成。每个实例:
- 独立地从 Prometheus 服务器接收告警
- 参与对等 (peer-to-peer) 的 Gossip 网状网络
- 将状态(静默规则和通知日志)复制到其他集群成员
- 独立处理并发送通知
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Prometheus 1 │ │ Prometheus 2 │ │ Prometheus N │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ alerts │ alerts │ alerts
│ │ │
▼ ▼ ▼
┌────────────────────────────────────────────┐
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ AM-1 │ │ AM-2 │ │ AM-3 │ │
│ │ (pos: 0) ├──┤ (pos: 1) ├──┤ (pos: 2) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ Gossip Protocol (Memberlist) │
└────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
Receivers Receivers Receivers
Gossip 协议
Alertmanager 使用 Hashicorp 的 Memberlist 库来实现基于 Gossip 的通信。Gossip 协议处理以下方面:
成员管理
- 自动节点发现 - 可以配置实例列表,实例将自动发现集群中的其他成员。
- 健康检查 - 定期探测以检测故障成员(默认:每 1 秒一次)。
- 故障检测 - 标记故障成员,且允许其尝试重新加入。
状态复制
Gossip 层复制三种状态:
- 静默规则 (Silences) - 创建、更新和删除操作会广播给所有节点。
- 通知日志 (Notification log) - 记录已发送的通知,以防止重复。
- 成员变更 - 加入、离开和故障事件。
状态是最终一致的 —— 在有足够时间和网络连通性的情况下,所有集群成员最终会收敛到相同的状态。
Gossip 稳定
当 Alertmanager 启动或重新加入集群时,它会等待 Gossip 机制“稳定 (settle)”后才处理通知。这可以防止基于不完整的状态发送通知。
稳定算法会一直等待,直到:
- 节点数量在 3 次连续检查中保持稳定(默认间隔:push-pull 间隔)
- 或者发生超时(可通过上下文配置)
在此期间,实例虽然会接收并存储告警,但会延迟通知的处理。
HA 模式下的通知流水线
通知流水线在集群环境中运行方式有所不同,以确保去重并保持至少一次交付:
┌────────────────────────────────────────────────┐
│ DISPATCHER STAGE │
├────────────────────────────────────────────────┤
│ 1. Find matching route(s) │
│ 2. Find/create aggregation group within route │
│ 3. Throttle by group wait or group interval │
└───────────────────┬────────────────────────────┘
│
▼
┌────────────────────────────────────────────────┐
│ NOTIFIER STAGE │
├────────────────────────────────────────────────┤
│ 1. Wait for HA gossip to settle │◄─── Ensures complete state
│ 2. Filter inhibited alerts │
│ 3. Filter non-time-active alerts │
│ 4. Filter time-muted alerts │
│ 5. Filter silenced alerts │◄─── Uses replicated silences
│ 6. Wait according to HA cluster peer index │◄─── Staggered notifications
│ 7. Dedupe by repeat interval/HA state │◄─── Uses notification log
│ 8. Notify & retry intermittent failures │
│ 9. Update notification log │◄─── Replicated to peers
└────────────────────────────────────────────────┘
HA 特有阶段
1. Gossip 稳定等待
在针对某一组告警发出第一个通知之前,实例会等待 Gossip 稳定。这确保了:
- 静默规则已完全复制
- 通知日志包含了其他实例最近的发送记录
- 集群成员关系稳定
实现:peer.WaitReady(ctx)
2. 基于对等位置的等待
为了防止所有集群成员同时发送通知,每个实例会根据其在排序后的对等列表中的位置进行等待:
wait_time = peer_position × peer_timeout
例如,有 3 个实例,且对等超时时间为 15 秒:
- 实例
am-1(位置 0):等待 0 秒 - 实例
am-2(位置 1):等待 15 秒 - 实例
am-3(位置 2):等待 30 秒
这种错开的计时允许:
- 第一个实例发送通知
- 后续实例能够看到通知日志条目
- 通过去重防止重复发送
实现:cmd/alertmanager/main.go:594 中的 clusterWait()
位置由所有对等节点名称按字母顺序排序决定。
func (p *Peer) Position() int {
all := p.mlist.Members()
sort.Slice(all, func(i, j int) bool {
return all[i].Name < all[j].Name
})
// Find position of self in sorted list
}
3. 通过通知日志去重
DedupStage(去重阶段)会查询通知日志,以决定是否应该发送通知。
// Check notification log for recent sends
entry := nflog.Query(receiver, groupKey)
if entry.exists && !shouldNotify(entry, alerts, repeatInterval) {
// Skip: already notified recently
return nil
}
去重检查:
- 告警状态发生改变? 若是,则发送通知。
- 已解决告警发生改变? 若是,且设置了
send_resolved: true,则发送。 - 重复间隔是否已过? 若是,则发送。
- 否则:跳过通知(去重)。
通知日志通过 Gossip 复制,因此所有集群成员共享相同的发送历史。
脑裂处理(故障开放)
在网络分区期间,集群可能会分裂成多个无法通信的组。Alertmanager 的“故障开放”设计确保告警仍能被送达。
场景:网络分区
Before partition:
┌────────┬────────┬────────┐
│ AM-1 │ AM-2 │ AM-3 │
└────────┴────────┴────────┘
Unified cluster
After partition:
┌────────┐ │ ┌────────┬────────┐
│ AM-1 │ │ │ AM-2 │ AM-3 │
└────────┘ │ └────────┴────────┘
Partition A │ Partition B
分区期间的行为
在分区 A 中 (仅 AM-1)
- AM-1 将自己视为位置 0
- 等待 0 × 超时 = 0 秒
- 发送通知(没有来自 AM-2/AM-3 的去重)
在分区 B 中 (AM-2, AM-3)
- AM-2 为位置 0,AM-3 为位置 1
- AM-2 等待 0 秒,发送通知
- AM-3 看到 AM-2 的通知日志条目,进行去重
结果:发送了重复通知(分区 A 一个,分区 B 一个)
这是有意为之 —— Alertmanager 宁可发送重复通知,也不愿漏掉告警。
分区恢复后
当网络分区恢复时:
- Gossip 协议再次检测到所有对等节点
- 通知日志被合并(通过类似 CRDT 的带时间戳合并)
- 未来的通知将在所有实例间正确去重
- 在任一分区中创建的静默规则都会复制到所有对等节点
HA 中的静默规则管理
静默规则是集群中的一等复制状态。
静默规则的创建与更新
当在任何实例上创建或更新静默规则时:
- 本地存储 - 静默规则存储在本地状态映射中。
- 广播 - 静默规则被序列化(protobuf)并通过 Gossip 广播。
- 接收时合并 - 其他实例接收并合并该静默规则。
// Merge logic: last-write-wins based on UpdatedAt timestamp if !exists || incoming.UpdatedAt > existing.UpdatedAt { accept_update() } - 索引 - 更新静默匹配器缓存,以便快速匹配告警。
静默规则过期
静默规则拥有:
StartsAt,EndsAt- 活跃时间范围ExpiresAt- 何时执行垃圾回收(EndsAt + 保留期)UpdatedAt- 用于合并时的冲突解决
每个实例独立地:
- 根据当前时间评估静默状态(待生效/活跃/过期)
- 对超过保留期的过期静默规则进行垃圾回收
- GC 仅在本地进行(无需 Gossip),因为所有实例都会收敛到相同的决定。
统一管理视图
用户可以与集群中的任何 Alertmanager 实例进行交互:
- 查看静默规则 - 所有实例具有相同的静默状态(最终一致性)
- 创建/更新静默规则 - 在任何实例上的更改都会传播到所有对等节点
- 删除静默规则 - 实现为“立即过期”+ Gossip 传播
这提供了无论访问哪个实例都一致的运维体验。
运维注意事项
配置
要配置集群,每个 Alertmanager 实例需要:
# alertmanager.yml
global:
# ... other config ...
# No cluster config in YAML - use CLI flags
命令行标志
alertmanager \
--cluster.listen-address=0.0.0.0:9094 \
--cluster.peer=am-1.example.com:9094 \
--cluster.peer=am-2.example.com:9094 \
--cluster.peer=am-3.example.com:9094 \
--cluster.advertise-address=$(hostname):9094 \
--cluster.peer-timeout=15s \
--cluster.gossip-interval=200ms \
--cluster.pushpull-interval=60s
关键标志:
--cluster.listen-address- 集群通信的绑定地址(默认:0.0.0.0:9094)--cluster.peer- 对等节点地址列表(可重复)--cluster.advertise-address- 广播给对等节点的地址(如果省略,则自动检测)--cluster.peer-timeout- 去重时每个对等位置的等待时间(默认:15s)--cluster.gossip-interval- Gossip 频率(默认:200ms)--cluster.pushpull-interval- 全量状态同步间隔(默认:60s)--cluster.probe-interval- 对等节点健康检查间隔(默认:1s)--cluster.settle-timeout- 等待 Gossip 稳定的最大时间(默认:上下文超时)
Prometheus 配置
重要:配置 Prometheus 将告警发送给所有 Alertmanager 实例,而不是通过负载均衡器发送。
# prometheus.yml
alerting:
alertmanagers:
- static_configs:
- targets:
- am-1.example.com:9093
- am-2.example.com:9093
- am-3.example.com:9093
这确保了:
- 冗余 - 如果一个 Alertmanager 宕机,其他实例仍能接收告警
- 独立处理 - 每个实例独立评估路由、分组和去重
- 无单点故障 - 负载均衡器会引入单点故障
集群规模考虑
由于 Alertmanager 使用无需仲裁或投票的 Gossip,任意 N 个实例可容忍 N-1 个故障 —— 只要有一个实例存活,通知就会被发出。
然而,集群规模涉及权衡:
更多实例的好处:
- 对同时发生的故障(硬件、网络、数据中心中断)有更强的抵御能力
- 即使在维护窗口期也能持续运行
更多实例的代价:
- 在分区情况下,重复通知将会增加
- 更多的 Gossip 流量
典型部署:
- 2-3 个实例 - 单数据中心生产部署常见选择
- 4-5 个实例 - 多数据中心或高度关键的环境
注意:与基于共识的系统(etcd、Raft)不同,奇数或偶数集群大小没有区别 —— 这里没有投票或仲裁。
监控集群健康
需要监控的关键指标:
# Cluster size
alertmanager_cluster_members
# Peer health
alertmanager_cluster_peer_info
# Peer position (affects notification timing)
alertmanager_peer_position
# Failed peers
alertmanager_cluster_failed_peers
# State replication
alertmanager_nflog_gossip_messages_propagated_total
alertmanager_silences_gossip_messages_propagated_total
安全性
默认情况下,集群通信是未加密的。对于生产环境,尤其是跨 WAN 的部署,请使用相互 TLS (mTLS)。
alertmanager \
--cluster.tls-config=/etc/alertmanager/cluster-tls.yml
详情请参阅 安全集群流量。
持久化
每个 Alertmanager 实例持久化:
- 静默规则 - 存储在快照文件中(默认:
data/silences) - 通知日志 - 存储在快照文件中(默认:
data/nflog)
重启时:
- 实例从磁盘加载静默规则和通知日志
- 加入集群并与对等节点进行 Gossip 通信
- 合并从对等节点接收到的状态(时间戳更新的胜出)
- 在 Gossip 稳定后开始处理通知
注意:告警本身不持久化 —— Prometheus 会定期重新发送告警。
常见陷阱
-
Prometheus → Alertmanager 的负载均衡
- ❌ 不要使用负载均衡器
- ✅ 在 Prometheus 中配置所有实例
-
未等待 Gossip 稳定
- 可能导致启动时静默规则丢失或重复通知
--cluster.settle-timeout标志可控制此行为
-
网络 ACL 阻挡集群端口
- 确保所有实例间均开放了 9094 端口(或您的
--cluster.listen-address端口) - 默认同时使用 TCP 和 UDP(如果使用 TLS 传输则仅使用 TCP)
- 确保所有实例间均开放了 9094 端口(或您的
-
无法路由的广播地址
- 如果未设置
--cluster.advertise-address,Alertmanager 会尝试自动检测 - 在云/NAT 环境中,请明确设置可路由的地址
- 如果未设置
-
集群配置不匹配
- 所有实例应具有相同的
--cluster.peer-timeout和 Gossip 设置 - 不匹配可能导致不必要的重复或漏发通知
- 所有实例应具有相同的
工作原理:端到端示例
场景:3 实例集群,出现新告警组
- 告警到达:Prometheus 将告警发送给所有 3 个实例
- 分发器 (Dispatcher) 创建聚合组,等待
group_wait(例如 30s) - group_wait 结束后::
- 每个实例准备通知
- 通知阶段:
- 所有实例等待 Gossip 稳定(如果是刚启动)
- AM-1 (位置 0):等待 0s,检查通知日志(为空),发送通知,记录到 nflog
- AM-2 (位置 1):等待 15s,检查通知日志(看到 AM-1 的条目),跳过通知
- AM-3 (位置 2):等待 30s,检查通知日志(看到 AM-1 的条目),跳过通知
- 结果:精确发送了一个通知(由 AM-1 发送)
场景:AM-1 故障
- 告警到达:仅到达 AM-2 和 AM-3
- 分发器 创建组,等待
group_wait - 通知阶段:
- AM-1 不在集群中(故障探测失败)
- AM-2 现在为位置 0:等待 0s,发送通知
- AM-3 现在为位置 1:等待 15s,看到 AM-2 的条目,跳过
- 结果:仍发送了通知(故障开放)
场景:通知期间发生网络分区
- 告警到达:到达所有实例
- 网络分区:AM-1 与 AM-2/AM-3 分隔
- 在分区 A 中 (AM-1)
- 位置 0,等待 0s,发送通知
- 在分区 B 中 (AM-2, AM-3)
- AM-2 为位置 0,等待 0s,发送通知
- AM-3 为位置 1,等待 15s,进行去重
- 结果:发送了两个通知(每个分区一个)- 故障开放行为
故障排除
检查集群状态
# View cluster members via API
curl http://am-1:9093/api/v2/status
# Check metrics
curl http://am-1:9093/metrics | grep cluster
诊断脑裂
如果您怀疑脑裂:
- 在每个实例上检查
alertmanager_cluster_members- 应与总集群大小匹配
- 检查
alertmanager_cluster_peer_info{state="alive"}- 应显示所有对等节点均处于存活状态
- 检查实例间的网络连通性
调试重复通知
重复通知可能由于以下原因:
- 网络分区(预期行为,故障开放)
- Gossip 未稳定 - 检查
--cluster.settle-timeout - 时钟偏差 - 确保所有实例都配置了 NTP
- 通知日志未同步 - 检查 Gossip 指标
启用调试日志
alertmanager --log.level=debug
查找:
"Waiting for gossip to settle...""gossip settled; proceeding"- 通知流水线中的去重决策