】39-etcd腦裂故障復(fù)盤)
案例:etcd 腦裂故障復(fù)盤一句話定位:3 節(jié)點 etcd 集群因機房網(wǎng)絡(luò)分區(qū)導(dǎo)致 2 節(jié)點失聯(lián),集群瞬間不可寫,核心業(yè)務(wù)全掛 23 分鐘——一次典型 Raft 多數(shù)派失效故障的完整應(yīng)急復(fù)盤。寫在前面etcd 是 K8s 的心臟,所有集群狀態(tài)都存在它里面。平時我們聊 etcd 高可用,大多停留在3 節(jié)點能掛 1 個的理論層面,真遇到網(wǎng)絡(luò)分區(qū)導(dǎo)致多數(shù)派失效,很多團隊的第一反應(yīng)是慌。2024 年 6 月,我們生產(chǎn)集群經(jīng)歷了一次 etcd 腦裂:3 節(jié)點中 2 節(jié)點因機房網(wǎng)絡(luò)設(shè)備故障失聯(lián),Raft 協(xié)議要求多數(shù)派(2/3)在線才能寫入,瞬間整個集群 apiserver 全部 5xx,核心業(yè)務(wù)全掛。這篇文章復(fù)盤這次故障的全過程:從告警觸發(fā)、影響評估、緊急恢復(fù),到數(shù)據(jù)一致性校驗和長期改進。etcd 腦裂不是重啟就好的故障,恢復(fù)過程中如果操作不當(dāng),可能導(dǎo)致數(shù)據(jù)丟失或腦裂后雙主。我把我們踩過的坑、查過的資料、最終的操作步驟都記錄下來,希望對大家有幫助。etcd 腦裂的本質(zhì)是 Raft 協(xié)議的多數(shù)派失效,這是設(shè)計上的保護機制,不是 bug。理解這一點,是正確處置這類故障的前提。案例概覽維度內(nèi)容集群規(guī)模生產(chǎn) K8s 1.30,1200 節(jié)點,3 控制面etcd 版本v3.5.13,3 節(jié)點,部署在控制面節(jié)點故障時間2024/06/21 14:32:08 - 14:55:17(共 23 分鐘)故障現(xiàn)象etcd 無主,apiserver 全部 5xx,業(yè)務(wù) Pod 無法調(diào)度/重啟影響范圍全集群,新建/調(diào)度/擴容全部失敗,運行中 Pod 不受影響根因機房核心交換機故障,etcd-1/etcd-2 之間網(wǎng)絡(luò)分區(qū)恢復(fù)方式隔離 etcd-2,member remove 后重新加入,數(shù)據(jù)校驗業(yè)務(wù)影響約 8 萬筆交易延遲,無數(shù)據(jù)丟失故障時間線:14:32:08 告警:etcd cluster has no leader 14:32:15 告警:apiserver 5xx 率 100% 14:32:20 值班 SRE 確認,拉應(yīng)急群 14:35:00 初判:etcd 腦裂,2 節(jié)點失聯(lián) 14:38:00 決策:隔離 etcd-2,單節(jié)點恢復(fù)(錯誤決策,后修正) 14:42:00 嘗試單節(jié)點啟動失敗(數(shù)據(jù)不一致) 14:45:00 改策略:用 etcd-0(健康節(jié)點)數(shù)據(jù)恢復(fù) 14:48:30 etcd-0 單節(jié)點成集群,apiserver 恢復(fù) 14:52:00 etcd-1 重新加入 14:55:17 etcd-2 重新加入,集群 3 節(jié)點正常,業(yè)務(wù)全恢復(fù) 15:30:00 數(shù)據(jù)一致性校驗完成,無丟失一、背景與挑戰(zhàn)1.1 集群架構(gòu)┌──────────────────────────────────────────────────────┐ │ K8s 控制面 │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ cp-node-0│ │ cp-node-1│ │ cp-node-2│ │ │ │ apiserver│ │ apiserver│ │ apiserver│ │ │ │ etcd-0 │ │ etcd-1 │ │ etcd-2 │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ └──────交換機A─┴──交換機B─┘ │ └──────────────────────────────────────────────────────┘ 機房 A 機房 B3 個控制面節(jié)點分布在機房 A 的兩個交換機域:cp-node-0/etcd-0 在交換機 A,cp-node-1/etcd-1 cp-node-2/etcd-2 在交換機 B。這個拓撲是后來出事的伏筆——兩個節(jié)點在同一個交換機域,交換機 B 故障直接帶走 2 個 etcd 成員。1.2 故障現(xiàn)象6/21 14:32,值班手機被告警刷屏:[FIRING:1] EtcdClusterHasNoLeader (critical) etcd_cluster_has_leader{jobetcd} 0 etcd-0, etcd-1, etcd-2 全部無 leader [FIRING:1] ApiserverDown (critical) apiserver_request_total{code~5..} rate 1.0 所有 apiserver 5xx 100% [FIRING:1] PodScheduleFail (critical) kube_pod_status_unschedulable 持續(xù)增長業(yè)務(wù)側(cè)反饋:新訂單創(chuàng)建失敗、Pod 擴容失敗、kubectl 全部超時,但已運行的 Pod 業(yè)務(wù)正常(因為 kubelet 不依賴 etcd)。1.3 應(yīng)急挑戰(zhàn)etcd 不可用,所有 kubectl 命令失敗:常規(guī) K8s 運維手段全部失效,必須直接操作 etcd。腦裂狀態(tài)下數(shù)據(jù)可能不一致:恢復(fù)時選哪個節(jié)點的數(shù)據(jù)?選錯會丟數(shù)據(jù)。操作風(fēng)險高:etcdctl member remove 操作不當(dāng),可能把健康成員也踢出去,雪上加霜。業(yè)務(wù)壓力:每分鐘影響約 4000 筆交易,老板每 5 分鐘問一次好了沒。二、方案設(shè)計(應(yīng)急流程)2.1 etcd 腦裂應(yīng)急決策樹1 個2 個是否告警:etcd 無 leader確認網(wǎng)絡(luò)分區(qū)范圍幾個節(jié)點失聯(lián)多數(shù)派還在,集群自愈多數(shù)派丟失,集群不可寫定位健康節(jié)點健康節(jié)點數(shù)據(jù)是否最新用健康節(jié)點重建集群選數(shù)據(jù)最新的節(jié)點member remove 失聯(lián)節(jié)點健康節(jié)點單點啟動apiserver 恢復(fù)失聯(lián)節(jié)點修復(fù)后逐個加入數(shù)據(jù)一致性校驗2.2 關(guān)鍵決策點決策選項風(fēng)險選擇用哪個節(jié)點恢復(fù)etcd-0(健康)數(shù)據(jù)可能不是最新選(網(wǎng)絡(luò)分區(qū)前是 leader)是否直接刪除失聯(lián)節(jié)點member remove刪錯會永久丟成員謹(jǐn)慎,先 backup單節(jié)點能否撐業(yè)務(wù)單點 etcd再掛就全完臨時撐,立即擴回 3 節(jié)點三、實施過程3.1 第一步:確認故障范圍(14:32 - 14:35)# 1. 直接連 etcd(繞過 apiserver)exportETCDCTL_API3ETCD_ENDPOINTShttps://10.0.1.10:2379,https://10.0.1.11:2379,https://10.0.1.12:2379etcdctl--endpoints$ETCD_ENDPOINTS\--cacert/etc/etcd/ca.pem\--cert/etc/etcd/etcd.pem\--key/etc/etcd/etcd-key.pem\endpoint status --write-outtable# 輸出:# -----------------------------------------------------------------------# | ENDPOINT | ID | VERSION | DB SIZE | IS LEADER |# -----------------------------------------------------------------------# | https://10.0.1.10:2379 | 8e9e05c52164694d | 3.5.13 | 4.3 GB | false |# | https://10.0.1.11:2379 | 91bc3c398fb3c146 | 3.5.13 | 4.3 GB | false | ← 超時# | https://10.0.1.12:2379 | fd422379fda50e85 | 3.5.13 | 4.3 GB | false | ← 超時# -----------------------------------------------------------------------# 三個節(jié)點都 false,無 leader,確認腦裂# 2. 查網(wǎng)絡(luò)連通性ping-c310.0.1.11#不通ping-c310.0.1.12#不通ping-c310.0.1.10#通(本機)# 3. 確認 etcd-0 本地服務(wù)狀態(tài)systemctl status etcd# active (running),但日志瘋狂報:# failed to connect to peer fd422379fda50e85結(jié)論:etcd-0 健康,etcd-1/etcd-2 因網(wǎng)絡(luò)分區(qū)失聯(lián),腦裂確認。3.2 第二步:關(guān)鍵決策與錯誤嘗試(14:36 - 14:45)3.2.1 錯誤決策:直接 member remove我們第一反應(yīng)是把失聯(lián)的兩個節(jié)點踢了,讓 etcd-0 單節(jié)點成集群:# ?? 這個操作后來證明是錯的etcdctl--endpointshttps://10.0.1.10:2379\--cacert...--cert...--key...\member remove 91bc3c398fb3c146# 報錯:# Error: etcdserver: request timed out# 原因:Raft 協(xié)議要求多數(shù)派同意才能執(zhí)行 member remove# 當(dāng)前只有 1/3,無法達成多數(shù)派,命令失敗教訓(xùn):腦裂狀態(tài)下,member remove 也需要多數(shù)派,直接 etcdctl 刪不掉。必須先讓健康節(jié)點單節(jié)點啟動(脫離原集群配置),再操作。3.2.2 正確做法:單節(jié)點強制啟動# 1. 先備份 etcd-0 數(shù)據(jù)(救命稻草)etcdctl--endpointshttps://10.0.1.10:2379\--cacert...--cert...--key...\snapshot save /backup/etcd-snapshot-$(date%s).db# 2. 停止 etcd-0systemctl stop etcd# 3. 修改 etcd 配置:從集群模式改為單節(jié)點模式# 關(guān)鍵:--force-new-cluster,用現(xiàn)有數(shù)據(jù)啟動新集群cat/etc/etcd/etcd.conf.ymlEOF name: etcd-0># 4. 啟動 etcd-0(單節(jié)點)systemctl start etcdsleep5etcdctl--endpointshttps://10.0.1.10:2379\--cacert...--cert...--key...\endpoint status --write-outtable# 輸出:IS LEADER true,集群恢復(fù)(單節(jié)點)關(guān)鍵點:--force-new-cluster用本地數(shù)據(jù)創(chuàng)建新集群,member list 里只有自己。這一步必須確認數(shù)據(jù)是正確的,因為后續(xù)都基于這份數(shù)據(jù)。3.3 第三步:恢復(fù) apiserver(14:48)etcd-0 單節(jié)點起來后,apiserver 自動恢復(fù):# 驗證 apiserverkubectl get nodes# 全部 Ready,apiserver 恢復(fù)kubectl get pods-nprod|head# 業(yè)務(wù) Pod 正常列出# 業(yè)務(wù)側(cè)確認:新訂單創(chuàng)建恢復(fù)14:48:30,apiserver 恢復(fù),業(yè)務(wù)新建/調(diào)度恢復(fù),但 etcd 還是單點,風(fēng)險極高,必須立即擴回 3 節(jié)點。3.4 第四步:修復(fù)失聯(lián)節(jié)點并重新加入(14:50 - 14:55)3.4.1 修復(fù) etcd-1網(wǎng)絡(luò)分區(qū)原因是交換機 B 故障,網(wǎng)絡(luò)團隊 14:45 修復(fù)了交換機,etcd-1/etcd-2 網(wǎng)絡(luò)恢復(fù)。但它們的數(shù)據(jù)可能和 etcd-0 不一致(分區(qū)期間各自有寫入嘗試),不能直接加入,必須清空數(shù)據(jù)重新同步。# 在 etcd-1 上操作systemctl stop etcd# 清空舊數(shù)據(jù)(?? 確認 etcd-0 數(shù)據(jù)正確后才做)rm-rf/var/lib/etcd/member# 修改配置:作為成員加入 etcd-0cat/etc/etcd/etcd.conf.ymlEOF name: etcd-1># 在 etcd-0 上把 etcd-1 加為成員(先加 member 再啟動)etcdctl--endpointshttps://10.0.1.10:2379\--cacert...--cert...--key...\memberaddetcd-1\--peer-urlshttps://10.0.1.11:2380# 啟動 etcd-1systemctl start etcd# 驗證:etcd-1 自動從 etcd-0 同步數(shù)據(jù)etcdctl--endpointshttps://10.0.1.10:2379\--cacert...--cert...--key...\endpoint status --write-outtable# etcd-0 leader,etcd-1 follower,數(shù)據(jù)同步中3.4.2 修復(fù) etcd-2(同樣流程)# etcd-2 上systemctl stop etcdrm-rf/var/lib/etcd/member# etcd-0 上加成員etcdctl--endpointshttps://10.0.1.10:2379\--cacert...--cert...--key...\memberaddetcd-2\--peer-urlshttps://10.0.1.12:2380# etcd-2 上改配置并啟動(同 etcd-1 流程)systemctl start etcd14:55:17,3 節(jié)點全部 online,leader 選舉完成,集群恢復(fù)。3.5 第五步:數(shù)據(jù)一致性校驗(15:00 - 15:30)腦裂期間,etcd-1/etcd-2 可能接受過少量寫請求(雖然無法 commit,但 WAL 日志可能有臟數(shù)據(jù))。必須校驗數(shù)據(jù)一致性。3.5.1 集群哈希對比# etcd 提供了 hash 命令,對比各節(jié)點數(shù)據(jù)哈希etcdctl--endpointshttps://10.0.1.10:2379\--cacert...--cert...--key...\endpoint hashkv--cluster# 輸出:# ------------------------------------------------------# | ENDPOINT | ID | HASHKV |# ------------------------------------------------------# | https://10.0.1.10:2379 | 8e9e05c52164694d | 2834567891 |# | https://10.0.1.11:2379 | 91bc3c398fb3c146 | 2834567891 | ← 一致# | https://10.0.1.12:2379 | fd422379fda50e85 | 2834567891 | ← 一致# ------------------------------------------------------# 三個節(jié)點哈希一致,數(shù)據(jù)一致3.5.2 關(guān)鍵資源完整性校驗# 對比關(guān)鍵資源數(shù)量etcdctl--endpointshttps://10.0.1.10:2379\--cacert...--cert...--key...\get /registry/pods--prefix--keys-only|wc-l# 28456 條(與故障前監(jiān)控記錄一致)etcdctl--endpointshttps://10.0.1.10:2379\--cacert...--cert...--key...\get /registry/services--prefix--keys-only|wc-l# 1842 條# 業(yè)務(wù)側(cè)校驗:抽樣確認訂單、支付關(guān)鍵數(shù)據(jù)# DB 層面數(shù)據(jù)未受影響(業(yè)務(wù)數(shù)據(jù)不在 etcd)校驗結(jié)論:數(shù)據(jù)零丟失,一致性正常。四、踩坑與應(yīng)急4.1 踩坑1:member remove 在腦裂下失敗(見 3.2.1)教訓(xùn):腦裂狀態(tài)下任何需要多數(shù)派的操作都會失敗,必須先--force-new-cluster單節(jié)點啟動。4.2 踩坑2:etcd-1 重新加入報 “database schema incompatible”現(xiàn)象:etcd-1 啟動后報錯,無法同步。定位:etcd-1 舊數(shù)據(jù)沒清干凈,/var/lib/etcd/member下還有 wal 文件。修復(fù):# 徹底清空,包括 walsystemctl stop etcdrm-rf/var/lib/etcd/* systemctl start etcd4.3 踩坑3:apiserver 緩存導(dǎo)致業(yè)務(wù)偶發(fā)異?,F(xiàn)象:etcd 恢復(fù)后,部分 apiserver 請求返回舊數(shù)據(jù)。定位:apiserver 本地有緩存,etcd 恢復(fù)后緩存沒刷新。修復(fù):# 重啟所有 apiserver,強制刷新緩存kubectl-nkube-system rollout restart deploy kube-apiserver# 或直接 ssh 到控制面節(jié)點systemctl restart kube-apiserver4.4 踩坑4:監(jiān)控告警風(fēng)暴影響判斷現(xiàn)象:故障期間收到 200 條告警,真正的根因告警被淹沒。改進:后續(xù)做了告警收斂,etcd/apiserver 故障時只推一條聚合告警,其他依賴告警靜默。五、復(fù)盤與改進5.1 故障影響總結(jié)指標(biāo)數(shù)據(jù)故障時長23 分鐘業(yè)務(wù)影響約 8 萬筆交易延遲,無數(shù)據(jù)丟失RTO23 分鐘(目標(biāo) 15 分鐘,未達標(biāo))RPO0(無數(shù)據(jù)丟失)根因機房交換機故障 etcd 拓撲不合理5.2 經(jīng)驗教訓(xùn)etcd 拓撲必須跨故障域:3 節(jié)點不能有 2 個在同一交換機,這次是拓撲設(shè)計失誤。腦裂下 member remove 無效:必須--force-new-cluster單節(jié)點啟動,這是核心知識點。數(shù)據(jù)備份是底線:恢復(fù)前必須 snapshot,操作失敗還能回滾。清數(shù)據(jù)要徹底:/var/lib/etcd/member和 wal 都要清,殘留會導(dǎo)致加入失敗。apiserver 緩存要刷新:etcd 恢復(fù)后必須重啟 apiserver。告警必須收斂:故障時告警風(fēng)暴嚴(yán)重影響判斷,聚合告警是剛需。應(yīng)急流程要演練:我們這次操作有猶豫,因為沒演練過,后續(xù)每月演練一次。5.3 長期改進5.3.1 etcd 拓撲優(yōu)化把 etcd 節(jié)點分散到不同交換機域,甚至跨機房:改造后拓撲: etcd-0 → 機房A-交換機1 etcd-1 → 機房A-交換機2 etcd-2 → 機房B-交換機1(異地容災(zāi))5.3.2 etcd 監(jiān)控告警增強# Prometheus 告警規(guī)則groups:-name:etcdrules:-alert:EtcdClusterHasNoLeaderexpr:etcd_server_has_leader 0for:1mlabels:severity:criticalannotations:summary:etcd 集群無 leader,可能腦裂runbook:https://wiki.example.com/etcd-split-brain-alert:EtcdMembersUnhealthyexpr:count(etcd_server_health_failures) by (cluster)0for:2mlabels:severity:criticalannotations:summary:etcd 有成員不健康-alert:EtcdClusterNoQuorumexpr:|count(up{jobetcd} 1) by (cluster) 2for:1mlabels:severity:criticalannotations:summary:etcd 多數(shù)派丟失,集群不可寫-alert:EtcdFsyncDurationHighexpr:|histogram_quantile(0.99, rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m])) 0.1for:3mlabels:severity:warningannotations:summary:etcd WAL fsync 延遲高,磁盤可能瓶頸5.3.3 etcd 健康巡檢腳本#!/bin/bash# etcd_health_check.sh - 每日巡檢ENDPOINTShttps://10.0.1.10:2379,https://10.0.1.11:2379,https://10.0.1.12:2379CERTS--cacert/etc/etcd/ca.pem --cert/etc/etcd/etcd.pem --key/etc/etcd/etcd-key.pemecho etcd 集群健康巡檢$(date)# 1. 集群狀態(tài)echo--- 節(jié)點狀態(tài) ---etcdctl--endpoints$ENDPOINTS$CERTSendpoint status --write-outtable# 2. 成員列表echo--- 成員列表 ---etcdctl--endpoints$ENDPOINTS$CERTSmember list --write-outtable# 3. 數(shù)據(jù)哈希一致性echo--- 數(shù)據(jù)哈希 ---etcdctl--endpoints$ENDPOINTS$CERTSendpoint hashkv--cluster# 4. 告警檢查echo--- 無 leader 檢查 ---LEADER$(etcdctl--endpoints$ENDPOINTS $CERTS endpoint status-wjson|jq-r.[0].Status.header.member_id)if[-z$LEADER];thenechoWARN: 無 leader!exit1fi# 5. 磁盤使用echo--- 數(shù)據(jù)目錄大小 ---forhostin10.0.1.1010.0.1.1110.0.1.12;doecho$host:$(ssh$hostdu-sh/var/lib/etcd|awk{print $1})done# 6. 備份驗證echo--- 最近備份 ---ls-lht/backup/etcd-snapshot-*.db|head-3echo 巡檢完成 5.3.4 定期容災(zāi)演練每月一次 etcd 故障注入演練:模擬單節(jié)點宕機(驗證集群自愈)模擬雙節(jié)點宕機(驗證應(yīng)急恢復(fù)流程)模擬網(wǎng)絡(luò)分區(qū)(驗證腦裂處置)模擬數(shù)據(jù)損壞(驗證備份恢復(fù))六、可復(fù)用產(chǎn)出6.1 etcd 故障應(yīng)急預(yù)案(分級響應(yīng))級別現(xiàn)象響應(yīng)時間處置P0集群無 leader/腦裂1 分鐘內(nèi)響應(yīng)啟動應(yīng)急流程,force-new-clusterP0多數(shù)派丟失1 分鐘內(nèi)響應(yīng)同上P1單節(jié)點宕機5 分鐘內(nèi)響應(yīng)集群自愈,觀察是否擴縮P1磁盤使用 80%15 分鐘內(nèi)響應(yīng)compact defrag 擴容P2fsync 延遲高30 分鐘內(nèi)響應(yīng)排查磁盤 IOP2leader 切換頻繁30 分鐘內(nèi)響應(yīng)排查網(wǎng)絡(luò)抖動6.2 腦裂檢測告警規(guī)則(見 5.3.2)6.3 etcd 健康巡檢腳本(見 5.3.3)6.4 復(fù)盤報告模板# etcd 故障復(fù)盤報告 ## 1. 故障概述 - 故障時間: - 影響時長: - 業(yè)務(wù)影響: - 根因: ## 2. 時間線(分鐘級) | 時間 | 事件 | 操作人 | ## 3. 根因分析 - 直接原因: - 深層原因: - 架構(gòu)問題: ## 4. 處置過程 - 應(yīng)急動作: - 踩坑記錄: ## 5. 數(shù)據(jù)一致性校驗 - 哈希對比: - 資源數(shù)量對比: - 業(yè)務(wù)側(cè)確認: ## 6. 改進項 | 改進項 | 負責(zé)人 | 截止時間 | 狀態(tài) | ## 7. 經(jīng)驗沉淀 - 可復(fù)用 SOP: - 監(jiān)控告警優(yōu)化: - 演練計劃:思考題如果 3 節(jié)點 etcd 中有 1 個數(shù)據(jù)損壞(非網(wǎng)絡(luò)問題),你會如何處置?和腦裂處置有什么區(qū)別?--force-new-cluster操作的風(fēng)險點在哪?如何保證選用的節(jié)點數(shù)據(jù)是最新的?etcd 集群規(guī)模是 3 節(jié)點還是 5 節(jié)點更合理?在成本和可用性之間如何權(quán)衡?延伸閱讀etcd 官方災(zāi)難恢復(fù)文檔:https://etcd.io/docs/v3.5/op-guide/recovery/Raft 論文:In Search of an Understandable Consensus Algorithmetcd 腦裂與多數(shù)派機制解析K8s 控制面高可用最佳實踐《分布式系統(tǒng):概念與設(shè)計》第 5 章