高可用性
Alertmanager 支援透過配置來建立高可用(HA)叢集。本文件介紹了高可用機制的工作原理、設計目標以及運維考量。
設計目標
Alertmanager 的高可用實現圍繞著三個核心原則進行設計:
- 單一檢視與管理 - 可以從任何叢集成員檢視和管理靜默(Silences)和告警,從而提供統一的運維體驗
- 透過“故障開啟(fail open)”在叢集腦裂中生存 - 在網路分割槽期間,Alertmanager 寧願傳送重複的通知,也不願遺漏關鍵告警
- 至少一次交付 - 系統保證通知至少送達一次,這與“故障開啟”理念一致
這些目標將運維可靠性和告警送達率置於嚴格的“僅一次(exactly-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) - 建立、更新和刪除操作會廣播到所有節點
- 通知日誌 - 已傳送通知的記錄,用以防止重複傳送
- 成員變更 - 加入、離開和故障事件
狀態是最終一致的——在給予足夠的時間和網路連線的情況下,所有叢集成員都將收斂到相同的狀態。
Gossip 穩定
當 Alertmanager 啟動或重新加入叢集時,它會等待 Gossip “穩定”後再處理通知。這可以防止基於不完整的狀態傳送通知。
穩定演算法會一直等待,直到:
- 節點數量在連續 3 次檢查中保持穩定(預設間隔:push-pull 間隔)
- 或者發生超時(可透過 context 配置)
在此期間,該例項已經接收並存儲告警,但會推遲通知處理。
高可用模式下的通知管道
通知管道在叢集環境中的執行方式不同,以確保在保持“至少一次交付”的同時進行去重
┌────────────────────────────────────────────────┐
│ 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
└────────────────────────────────────────────────┘
高可用特定階段
1. 等待 Gossip 穩定
在傳送來自某個組的第一個通知之前,例項會等待 Gossip 穩定。這確保了:
- 靜默已完全複製
- 通知日誌包含來自其他例項的近期傳送記錄
- 叢集成員關係穩定
實現:peer.WaitReady(ctx)
2. 基於節點位置的等待
為了防止所有叢集成員同時傳送通知,每個例項會根據其在排序後的節點列表中的位置進行等待
wait_time = peer_position × peer_timeout
例如,如果有 3 個例項且節點超時(peer timeout)為 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 合併)
- 未來的通知將在所有例項之間被正確去重
- 在任一分割槽中建立的靜默都會被複制到所有節點
高可用模式下的靜默管理
靜默是叢集中的一等複製狀態。
靜默建立與更新
當在任何例項上建立或更新靜默時:
- 本地儲存 - 靜默儲存在本地狀態對映中
- 廣播 - 靜默被序列化(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=192.0.2.1: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- 廣播給節點的 IP 地址(不能是主機名),格式為<ip>:<port>(如果省略,則自動檢測)--cluster.peer-timeout- 去重時每個節點位置的等待時間(預設:15s)--cluster.gossip-interval- Gossip 通訊的頻率(預設:200ms)--cluster.pushpull-interval- 完整狀態同步間隔(預設:60s)--cluster.probe-interval- 節點健康檢查間隔(預設:1s)--cluster.settle-timeout- 等待 Gossip 穩定的最大時間(預設:context 超時)
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 使用無需法定人數(quorum)或投票的 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
安全性
預設情況下,叢集 communication 是未加密的。對於生產環境部署,尤其是跨廣域網(WAN)的部署,請使用雙向 TLS(mutual TLS)
alertmanager \
--cluster.tls-config=/etc/alertmanager/cluster-tls.yml
有關雙向 TLS 配置,請參閱 Gossip 流量部分。
持久化
每個 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 埠(或您的
-
無法路由的廣播(advertise)地址
- 如果未設定
--cluster.advertise-address,Alertmanager 會嘗試自動檢測 - 該值必須是
<ip>:<port>格式的 IP 地址——不支援主機名。使用主機名會記錄一條警告,並且啟動過程將繼續(但廣播地址會處於未設定或不正確狀態),從而導致叢集通訊失敗 - 對於雲/NAT 環境,請顯式設定可路由的 IP 地址(例如
--cluster.advertise-address=192.0.2.1:9094)
- 如果未設定
-
叢集配置不匹配
- 所有例項都應具有相同的
--cluster.peer-timeout和 Gossip 設定 - 不匹配可能會導致不必要的重複或遺漏通知
- 所有例項都應具有相同的
工作原理:端到端示例
場景:包含 3 個例項的叢集,新的告警組
- 告警從 Prometheus 送達至所有 3 個例項
- 排程器(Dispatcher) 建立聚合組,等待
group_wait(例如,30 秒) - 在 group_wait 之後:
- 每個例項準備傳送通知
- 通知階段:
- 所有例項等待 Gossip 穩定(如果剛剛啟動)
- AM-1(位置 0):等待 0 秒,檢查通知日誌(為空),傳送通知,記錄到 nflog
- AM-2(位置 1):等待 15 秒,檢查通知日誌(看到 AM-1 的條目),跳過通知
- AM-3(位置 2):等待 30 秒,檢查通知日誌(看到 AM-1 的條目),跳過通知
- 結果:剛好傳送了一條通知(由 AM-1 傳送)
場景:AM-1 發生故障
- 告警僅送達 AM-2 和 AM-3
- 排程器 建立組,等待
group_wait - 通知階段:
- AM-1 不在叢集中(探測失敗)
- AM-2 現在處於位置 0:等待 0 秒,傳送通知
- AM-3 現在處於位置 1:等待 15 秒,看到 AM-2 的條目,跳過通知
- 結果:通知仍能傳送(故障開啟)
場景:通知期間發生網路分割槽
- 告警送達所有例項
- 網路分割槽將 AM-1 與 AM-2/AM-3 分開
- 在分割槽 A 中(AM-1)
- 處於位置 0,等待 0 秒,傳送通知
- 在分割槽 B 中(AM-2,AM-3)
- AM-2 處於位置 0,等待 0 秒,傳送通知
- AM-3 處於位置 1,等待 15 秒,進行去重
- 結果:傳送了兩條通知(每個分割槽各一條)——展現故障開啟行為
問題排查
檢查叢集狀態
# 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"}- 應顯示所有節點均為活躍狀態(alive)
- 檢查例項之間的網路連線狀況
除錯重複通知問題
發生重複通知的原因可能是:
- 網路分割槽(預期內,由於故障開啟)
- Gossip 未穩定 - 檢查
--cluster.settle-timeout - 時鐘偏移 - 確保所有例項上都配置了 NTP
- 通知日誌未複製 - 檢查 Gossip 相關指標
啟用除錯日誌記錄
alertmanager --log.level=debug
尋找以下資訊:
"Waiting for gossip to settle...""gossip settled; proceeding"- 通知管道中的去重決策