antony@notes:~/kubernetes$ cat "實作-K8s-實體機節點硬碟備份與還原.md"
實作 K8s 實體機節點硬碟備份與還原
實作 K8s 實體機節點硬碟備份與還原
0. Preface
本篇文章將實作透過 Clonezilla 再生龍來備份硬碟到其他裝置,並還原到新的硬碟上。 (實作環境透過 Proxmox 進行模擬)
實作兩個情境:
- 備份 Control-plane node (同時扮演 Etcd member) 的硬碟,並還原在新的一顆乾淨的硬碟上。
實作環境並沒用切割系統碟和資料碟,通通儲存在同一顆硬碟。
- 備份 Control-plane node (同時扮演 Etcd leader) 的硬碟,在備份完後,破壞一些 K8s 核心服務的檔案及目錄,再透過備份好的硬碟還原到新的硬碟上,最後檢查 K8s 是否能正常運作。
1. 下載 Clonezilla live CD
- 到以下 Clonezilla 官方網站下載 Clonezilla live CD: https://clonezilla.nchc.org.tw/clonezilla-live/download/
- 選擇 “穩定-發行版”
:::spoiler 點我展開圖片
::: - CPU 架構選擇:
amd64, 檔案類別選擇:iso,檔案儲存庫:auto,確認好後點選下載按鈕 :::spoiler 點我展開圖片
::: - 可透過 Rufus 將 ISO 做成 USB 可開機裝置; 如過是虛擬環境則將 ISO 匯入虛擬化平台
2. 將第一顆硬碟備份,存為印象檔(disk image),放置在第二顆硬碟上
Step1: 由再生龍 live CD 開機
- 新增一顆硬碟 (稍後會作為儲放備份 disk image 的硬碟)
:::spoiler 點我展開圖片
::: - 設定 Size 為
50Gi (須視環境做調整),確認好後點選 Add 按鈕 :::spoiler 點我展開圖片
::: - 將 /dev/sdb 格式化為 XFS 檔案系統
sudo mkfs.xfs /dev/sdb - 將 VM 關機
- 點選 Hardware -> 點選 Add -> 點選 CD/DVD Driver
:::spoiler 點我展開圖片
::: - 選擇 Storage 和 ISO image:
clonezilla-live-3.2.2-15-amd64.iso,確認好後點選 Add 按鈕 :::spoiler 點我展開圖片
::: - 新增一顆硬碟 (稍後會作為儲放備份 disk image 的硬碟)
:::spoiler 點我展開圖片
::: - 設定可開機裝置的順序,點選 options -> 點選 Boot Order -> 點選 Edit
:::spoiler 點我展開圖片
::: - 將 再生龍 live CD 設為第一個可開機裝置,確認好後點選 OK 按鈕
:::spoiler 點我展開圖片
::: - 將 VM 開機
:::spoiler 點我展開圖片
:::
Step2: Clonezilla live 開機選單
- 選擇 “Clonezilla live (Default settings, VGA 800x600)": 第1個選項是 Clonezilla Live 的預設模式:使用 framebuffer 並設定螢幕解析度為 800x600.
:::spoiler 點我展開圖片
:::
Step3: 選擇語言
- 選擇
zh_TW.UFT-8 Chinese (Traditional) | 正體中文 - 臺灣:::spoiler 點我展開圖片
:::
Step4: 選擇鍵盤
- 使用預設的鍵盤,直接按 Enter 鍵
:::spoiler 點我展開圖片
:::
Step5: 選擇 “使用再生龍”
- 使用預設的選項,直接按 Enter 鍵
:::spoiler 點我展開圖片
:::
Step6: 選擇 “device-image”
- 選擇預設的選項:“device-image”
:::spoiler 點我展開圖片
:::
Step7: 選擇 “local_dev” 來指定 sdb 作為 Disk image 家目錄
- 選擇 “local_dev”, 就可以將印象檔存到本機其他硬碟上或 USB 裝置. 如果使用 USB 儲存裝置,請等待一下後在將 USB 儲存裝置插上
:::spoiler 點我展開圖片
::: - 將 USB 接上電腦,過 5 秒後按 Enter 鍵(前面步驟已先額外新增一顆硬碟,故此步驟可直接按 Enter 鍵)
:::spoiler 點我展開圖片
::: - 每過幾秒就會顯示裝置掃描的結果,如下面的提示
:::spoiler 點我展開圖片
::: - 確認裝置都認到後,按
Ctrl + C
Step8: 選擇 sdb 作為 Disk image 的目錄
- 選擇 sdb 硬碟裡的目錄作為印象檔目錄.
:::spoiler 點我展開圖片
::: - 選擇 “fsck” 檢查來源硬碟的檔案系統
:::spoiler 點我展開圖片
::: - 選擇要哪個目錄給再生龍存 disk image,確定後按 Tab 鍵,選擇
<Done>:::spoiler 點我展開圖片
::: - 顯示硬碟的使用狀況
:::spoiler 點我展開圖片
::: - 選擇 “Beginner” (初學模式)
:::spoiler 點我展開圖片
::: - 選擇 “savedisk”
:::spoiler 點我展開圖片
:::
Step9: 輸入 Disk image 檔名和選擇來源硬碟
- 輸入備份之後 disk image 的檔名,確認後按 Enter 鍵
:::spoiler 點我展開圖片
::: - 選擇要備份哪一顆硬碟,本篇範例為 sda
:::spoiler 點我展開圖片
::: - 選擇印象檔壓縮演算法
-z9p(zstd): :::spoiler 點我展開圖片
::: - 選擇 “fsck” 檢查來源硬碟的檔案系統
:::spoiler 點我展開圖片
::: - 選擇檢查備份後的 Disk image 是否完整
:::spoiler 點我展開圖片
::: - 選擇不要對 Disk image 加密
:::spoiler 點我展開圖片
::: - 選擇將 log 檔也儲存在 Disk image 中
:::spoiler 點我展開圖片
::: - 選擇
-p choose,等程式跑完後再選擇關機或重開或進入命令模式 :::spoiler 點我展開圖片
::: - Clonezilla 出現此次工作的完整命令提示. 此命令可在製作客制化Clonezilla live時使用,按 Enter 鍵繼續
:::spoiler 點我展開圖片
::: - 開始備份之前,再次詢問是否進行,輸入
y,按 Enter 鍵確定開始備份 :::spoiler 點我展開圖片
:::
Step 10: 完成將硬碟 (sda) 備份到第二顆硬碟 (sdb) 中
- 完成時,Clonezilla 提示若要再次使用再生龍選單的方式,按 Enter 鍵繼續
:::spoiler 點我展開圖片
::: - 選擇關機
:::spoiler 點我展開圖片
::: - 按 Enter 繼續關機
:::spoiler 點我展開圖片
:::
3. 將 Disk 還原到第一顆硬碟上
Step1: 由再生龍 live CD 開機
- 新增一顆硬碟 (稍後會作為儲放備份 disk image 的硬碟)
:::spoiler 點我展開圖片
::: - 設定 Size 為
100Gi (須視環境做調整),確認好後點選 Add 按鈕 :::spoiler 點我展開圖片
::: - 點選 Hardware -> 點選 Add -> 點選 CD/DVD Driver
:::spoiler 點我展開圖片
::: - 選擇 Storage 和 ISO image:
clonezilla-live-3.2.2-15-amd64.iso,確認好後點選 Add 按鈕 :::spoiler 點我展開圖片
::: - 設定可開機裝置的順序,點選 options -> 點選 Boot Order -> 點選 Edit
:::spoiler 點我展開圖片
::: - 將 再生龍 live CD 設為第一個可開機裝置,確認好後點選 OK 按鈕
:::spoiler 點我展開圖片
::: - 將 VM 開機
:::spoiler 點我展開圖片
:::
Step2: Clonezilla live 開機選單
- 選擇 “Clonezilla live (Default settings, VGA 800x600)": 第1個選項是 Clonezilla Live 的預設模式:使用 framebuffer 並設定螢幕解析度為 800x600.
:::spoiler 點我展開圖片
:::
Step3: 選擇語言
- 選擇
zh_TW.UFT-8 Chinese (Traditional) | 正體中文 - 臺灣:::spoiler 點我展開圖片
:::
Step4: 選擇鍵盤
- 使用預設的鍵盤,直接按 Enter 鍵
:::spoiler 點我展開圖片
:::
Step5: 選擇 “使用再生龍”
- 使用預設的選項,直接按 Enter 鍵
:::spoiler 點我展開圖片
:::
Step6: 選擇 “device-image”
- 選擇預設的選項:“device-image”
:::spoiler 點我展開圖片
:::
Step7: 選擇 “local_dev” 來指定 sdb 作為 Disk image 家目錄
- 選擇 “local_dev”, 就可以將印象檔存到本機其他硬碟上或 USB 裝置. 如果使用 USB 儲存裝置,請等待一下後在將 USB 儲存裝置插上
:::spoiler 點我展開圖片
::: - 將 USB 接上電腦,過 5 秒後按 Enter 鍵(前面步驟已先額外新增一顆硬碟,故此步驟可直接按 Enter 鍵)
:::spoiler 點我展開圖片
::: - 每過幾秒就會顯示裝置掃描的結果,如下面的提示
:::spoiler 點我展開圖片
::: - 確認裝置都認到後,按
Ctrl + C
Step8: 選擇 sdb 作為 Disk image 的目錄
- 選擇 sdb 硬碟裡的目錄作為印象檔目錄.
:::spoiler 點我展開圖片
::: - 選擇 “fsck” 檢查來源硬碟的檔案系統
:::spoiler 點我展開圖片
::: - 選擇要哪個目錄給再生龍存 disk image,確定後按 Tab 鍵,選擇
<Done>:::spoiler 點我展開圖片
::: - 顯示硬碟的使用狀況
:::spoiler 點我展開圖片
::: - 選擇 “Beginner” (初學模式)
:::spoiler 點我展開圖片
::: - 選擇 “restoredisk”
:::spoiler 點我展開圖片
:::
Step9: 選擇印象檔與目標碟
- 選擇要透過哪個 Disk image 還原,確認後按 Enter 鍵
:::spoiler 點我展開圖片
::: - 選擇要還原到哪一顆硬碟上,本篇範例為 sdc
:::spoiler 點我展開圖片
::: - 選擇印象檔中的硬碟分割表
:::spoiler 點我展開圖片
::: - 選擇 “是”,在還原前檢查來源硬硬碟的檔案完整性
:::spoiler 點我展開圖片
::: - 選擇將 log 檔也儲存在 Disk image 中
:::spoiler 點我展開圖片
::: - 選擇
-p choose,等程式跑完後再選擇關機或重開或進入命令模式 :::spoiler 點我展開圖片
::: - Clonezilla 出現此次工作的完整命令提示. 此命令可在製作客制化Clonezilla live時使用,按 Enter 鍵繼續
:::spoiler 點我展開圖片
::: - 因為剛剛有選擇要檢查印象檔所以在還原之前, 就會進行印象檔的檢查
:::spoiler 點我展開圖片
::: - 開始備份之前,詢問是否進行,按 Enter 鍵確定開始備份
:::spoiler 點我展開圖片
::: - 開始備份之前,再次詢問是否進行,按
y,再按 Enter 鍵確定開始備份 :::spoiler 點我展開圖片
::: - 開始備份之前,第三次詢問是否進行,按
y,再按 Enter 鍵確定開始備份 :::spoiler 點我展開圖片
:::
Step 10: 完成將硬碟 (sda) 備份到第二顆硬碟 (sdb) 中
- 完成時,Clonezilla 提示若要再次使用再生龍選單的方式,按 Enter 鍵繼續
:::spoiler 點我展開圖片
::: - 選擇關機
:::spoiler 點我展開圖片
::: - 按 Enter 繼續關機
:::spoiler 點我展開圖片
:::
Step 11: 驗證是否備份成功
設定可開機裝置的順序,點選 options -> 點選 Boot Order -> 點選 Edit :::spoiler 點我展開圖片
:::將第三顆硬碟 sdc 設為第一個可開機裝置,確認好後點選 OK 按鈕 :::spoiler 點我展開圖片
:::將 VM 開機 :::spoiler 點我展開圖片
:::檢視 K8s 節點是否健康
$ kubectl get nodes NAME STATUS ROLES AGE VERSION m1 Ready control-plane,master 7d5h v1.21.14 m2 Ready control-plane,master 7d4h v1.21.14 m3 Ready control-plane,master 7d3h v1.21.14 w1 Ready <none> 7d3h v1.21.14 w2 Ready <none> 7d3h v1.21.14確認 K8s 核心服務是否正常
$ kubectl -n kube-system get pods NAME READY STATUS RESTARTS AGE calico-kube-controllers-6477b97f9f-zk24r 1/1 Running 6 5d3h calico-node-558x6 1/1 Running 0 7d3h calico-node-cnzrx 1/1 Running 2 5d3h calico-node-gpcbl 1/1 Running 6 7d3h calico-node-mh4k6 1/1 Running 7 7d3h calico-node-rsvmr 1/1 Running 1 7d3h coredns-558bd4d5db-nvbxc 1/1 Running 0 24h coredns-558bd4d5db-sg5nb 1/1 Running 0 24h etcd-m1 1/1 Running 3 26h etcd-m2 1/1 Running 0 7d4h etcd-m3 1/1 Running 2 7d3h kube-apiserver-m1 1/1 Running 0 26h kube-apiserver-m2 1/1 Running 0 7d4h kube-apiserver-m3 1/1 Running 2 7d3h kube-controller-manager-m1 1/1 Running 1 26h kube-controller-manager-m2 1/1 Running 2 7d3h kube-controller-manager-m3 1/1 Running 2 7d3h kube-proxy-6w529 1/1 Running 0 7d3h kube-proxy-75c4k 1/1 Running 0 7d3h kube-proxy-dpr4d 1/1 Running 2 5d4h kube-proxy-lzxml 1/1 Running 0 5d4h kube-proxy-vlfwv 1/1 Running 0 7d3h kube-scheduler-m1 1/1 Running 1 26h kube-scheduler-m2 1/1 Running 2 7d3h kube-scheduler-m3 1/1 Running 2 7d3h kube-vip-m1 1/1 Running 2 7d3h kube-vip-m2 1/1 Running 2 7d4h kube-vip-m3 1/1 Running 2 7d3h檢查該節點上的 Pod 是否正常
$ kubectl get pods --field-selector spec.nodeName=m3 -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system calico-node-cnzrx 1/1 Running 2 5d3h kube-system etcd-m3 1/1 Running 2 7d3h kube-system kube-apiserver-m3 1/1 Running 2 7d3h kube-system kube-controller-manager-m3 1/1 Running 2 7d3h kube-system kube-proxy-dpr4d 1/1 Running 2 5d4h kube-system kube-scheduler-m3 1/1 Running 2 7d3h kube-system kube-vip-m3 1/1 Running 2 7d3h確認 etcd Member 是不是都還健在
$ alias etcdctl="ETCDCTL_API=3 sudo /usr/local/bin/etcdctl \ --endpoints=127.0.0.1:2379,172.20.7.81:2379,172.20.7.82:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/apiserver-etcd-client.crt \ --key=/etc/kubernetes/pki/apiserver-etcd-client.key" $ etcdctl member list 768bf4f42edfb9b3, started, m1, https://172.20.7.80:2380, https://172.20.7.80:2379, false e044dfaaca4a3cf4, started, m2, https://172.20.7.81:2380, https://172.20.7.81:2379, false ff78f71ad246a1cd, started, m3, https://172.20.7.82:2380, https://172.20.7.82:2379, false確認 etcd 狀態
$ etcdctl endpoint status -w table +------------------+------------------+---------+-----------------+---------+--------+-----------------------+-------+-----------+------------+-----------+------------+--------------------+--------+--------------------------+-------------------+ | ENDPOINT | ID | VERSION | STORAGE VERSION | DB SIZE | IN USE | PERCENTAGE NOT IN USE | QUOTA | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS | DOWNGRADE TARGET VERSION | DOWNGRADE ENABLED | +------------------+------------------+---------+-----------------+---------+--------+-----------------------+-------+-----------+------------+-----------+------------+--------------------+--------+--------------------------+-------------------+ | 127.0.0.1:2379 | 768bf4f42edfb9b3 | 3.4.13 | | 9.0 MB | 4.3 MB | 52% | 0 B | false | false | 243 | 1963834 | 1963834 | | | false | | 172.20.7.81:2379 | e044dfaaca4a3cf4 | 3.4.13 | | 8.9 MB | 4.3 MB | 52% | 0 B | true | false | 243 | 1963834 | 1963834 | | | false | | 172.20.7.82:2379 | ff78f71ad246a1cd | 3.4.13 | | 8.9 MB | 4.3 MB | 52% | 0 B | false | false | 243 | 1963834 | 1963834 | | | false | +------------------+------------------+---------+-----------------+---------+--------+-----------------------+-------+-----------+------------+-----------+------------+--------------------+--------+--------------------------+-------------------+透過 IS LEADER 欄位確認 m2 主機是 etcd leader
4. 挑戰破壞 etcd leader vm 並透過再生龍備份的硬碟 restore 回去
開始備份與還原
- 確認當前哪一台節點是 etcd leader
$ etcdctl endpoint status --cluster -w table +--------------------------+------------------+---------+-----------------+---------+--------+-----------------------+-------+-----------+------------+-----------+------------+--------------------+--------+--------------------------+-------------------+ | ENDPOINT | ID | VERSION | STORAGE VERSION | DB SIZE | IN USE | PERCENTAGE NOT IN USE | QUOTA | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS | DOWNGRADE TARGET VERSION | DOWNGRADE ENABLED | +--------------------------+------------------+---------+-----------------+---------+--------+-----------------------+-------+-----------+------------+-----------+------------+--------------------+--------+--------------------------+-------------------+ | https://172.20.7.81:2379 | 31954c2b28bfea6a | 3.4.13 | | 9.0 MB | 3.9 MB | 57% | 0 B | false | false | 244 | 2226833 | 2226833 | | | false | | https://172.20.7.80:2379 | 768bf4f42edfb9b3 | 3.4.13 | | 9.0 MB | 3.9 MB | 57% | 0 B | true | false | 244 | 2226833 | 2226833 | | | false | | https://172.20.7.82:2379 | ff78f71ad246a1cd | 3.4.13 | | 8.9 MB | 3.9 MB | 57% | 0 B | false | false | 244 | 2226833 | 2226833 | | | false | +--------------------------+------------------+---------+-----------------+---------+--------+-----------------------+-------+-----------+------------+-----------+------------+--------------------+--------+--------------------------+-------------------+由以上執行結果得知 leader 是
m1節點 - 先透過再生龍備份
m1主機的硬碟 sda (備份動作請參考前面的步驟,這裡不再贅述) - 透過原來的硬碟 sda 將 VM 開機,並破壞 K8s
$ ssh m1 $ sudo systemctl disable --now containerd.service $ sudo systemctl disable --now kubelet $ sudo rm -r /etc/containerd/ $ sudo rm -r /etc/kubernetes/ $ sudo poweroff - 透過再生龍將備份好的 disk image 還原到新的硬碟 sdc 上 (還原動作請參考前面的步驟,這裡不再贅述)
- 透過還原好的硬碟 sdc 將 VM 開機
- 檢查在
m1節點的 pod 是否有異常$ kubectl get pods --field-selector spec.nodeName=m1 -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system calico-node-558x6 1/1 Running 1 7d21h kube-system etcd-m1 0/1 CrashLoopBackOff 7 44h kube-system kube-apiserver-m1 0/1 Error 3 44h kube-system kube-controller-manager-m1 1/1 Running 2 44h kube-system kube-proxy-6w529 1/1 Running 1 7d21h kube-system kube-scheduler-m1 1/1 Running 2 44h kube-system kube-vip-m1 1/1 Running 3 7d21h發現 etcd 跟 API Server 掛了
- 查找 etcd 死亡原因執行結果如下:
$ kubectl -n kube-system logs etcd-m1... 以上省略 2025-07-09 03:45:22.108428 I | rafthttp: started streaming with peer ff78f71ad246a1cd (stream MsgApp v2 reader) raft2025/07/09 03:45:22 INFO: 768bf4f42edfb9b3 [term: 244] received a MsgHeartbeat message with higher term from 31954c2b28bfea6a [term: 245] raft2025/07/09 03:45:22 INFO: 768bf4f42edfb9b3 became follower at term 245 raft2025/07/09 03:45:22 tocommit(2238877) is out of range [lastIndex(2235883)]. Was the raft log corrupted, truncated, or lost? panic: tocommit(2238877) is out of range [lastIndex(2235883)]. Was the raft log corrupted, truncated, or lost? goroutine 75 [running]: log.(*Logger).Panicf(0xc0001e6140, 0x10db3a0, 0x5d, 0xc000fa6380, 0x2, 0x2) /usr/local/go/src/log/log.go:219 +0xc1 go.etcd.io/etcd/raft.(*DefaultLogger).Panicf(0x1ac1310, 0x10db3a0, 0x5d, 0xc000fa6380, 0x2, 0x2) /tmp/etcd-release-3.4.13/etcd/release/etcd/raft/logger.go:127 +0x60 go.etcd.io/etcd/raft.(*raftLog).commitTo(0xc00015e150, 0x22299d) /tmp/etcd-release-3.4.13/etcd/release/etcd/raft/log.go:203 +0x131 go.etcd.io/etcd/raft.(*raft).handleHeartbeat(0xc0000f9a40, 0x8, 0x768bf4f42edfb9b3, 0x31954c2b28bfea6a, 0xf5, 0x0, 0x0, 0x0, 0x0, 0x0, ...) /tmp/etcd-release-3.4.13/etcd/release/etcd/raft/raft.go:1396 +0x54 go.etcd.io/etcd/raft.stepFollower(0xc0000f9a40, 0x8, 0x768bf4f42edfb9b3, 0x31954c2b28bfea6a, 0xf5, 0x0, 0x0, 0x0, 0x0, 0x0, ...) /tmp/etcd-release-3.4.13/etcd/release/etcd/raft/raft.go:1341 +0x480 go.etcd.io/etcd/raft.(*raft).Step(0xc0000f9a40, 0x8, 0x768bf4f42edfb9b3, 0x31954c2b28bfea6a, 0xf5, 0x0, 0x0, 0x0, 0x0, 0x0, ...) /tmp/etcd-release-3.4.13/etcd/release/etcd/raft/raft.go:984 +0x1113 go.etcd.io/etcd/raft.(*node).run(0xc0003bd020) /tmp/etcd-release-3.4.13/etcd/release/etcd/raft/node.go:352 +0xab6 created by go.etcd.io/etcd/raft.RestartNode /tmp/etcd-release-3.4.13/etcd/release/etcd/raft/node.go:240 +0x33c
🔍 分析 etcd 毀損原因
- 收到 heartbeat,變成 follower表示本節點(768bf4f42edfb9b3)接受到領導者心跳,發現自己在舊 term(244),於是以新 term(245)降階為 follower。這是正常 Raft 流程。
INFO: 768bf4f42edfb9b3 [term: 244] received a MsgHeartbeat message with higher term from 31954c2b28bfea6a [term: 245] INFO: 768bf4f42edfb9b3 became follower at term 245 - 提交索引
tocommit超出日誌範圍這裡 leader 傳來了一筆要 commit 的索引tocommit(2238877) is out of range [lastIndex(2235883)]2238877,但本地的最後日誌索引只有2235883。缺少 2994 筆日誌紀錄,即 index gap。Raft 本意要求 follower 在 commit 前先擁有所有日誌條目;若 commit index 超出本地 lastIndex,代表日誌是不連續、不完整的。 - 導致 panic 崩潰 Raft 為了保障一致性,若遇到不合法的 commit 請求(tocommit 超界),會直接 panic 停止,以避免資料錯誤或分裂。這是設計上的安全機制。
💥 Root Cause
Raft 在 etcd 中負責維持複本間的一致性(consensus),當一個成員嘗試啓動並提交過舊的資料時,領導者會嘗試將該成員更新到目前的提交進度。如果領導者無法更新該成員,就會觸發 raft 錯誤,以避免集群內資料損壞,並導致該成員因 raft panic 而啓動失敗。
這個問題可能發生於某個 etcd 成員長時間與其他成員隔離,導致 etcd 領導者無法保留所有的提交歷史來正確更新該成員。
✅ 解決辦法
- 檢查當前 etcd member
$ etcdctl member list 31954c2b28bfea6a, started, m2, https://172.20.7.81:2380, https://172.20.7.81:2379, false 768bf4f42edfb9b3, started, m1, https://172.20.7.80:2380, https://172.20.7.80:2379, false ff78f71ad246a1cd, started, m3, https://172.20.7.82:2380, https://172.20.7.82:2379, false - 檢查每個 etcd 的狀態
$ etcdctl endpoint status --cluster -w table {"level":"warn","ts":"2025-07-09T13:10:30.247283+0800","logger":"etcd-client","caller":"v3@v3.6.1/retry_interceptor.go:65","msg":"retrying of unary invoker failed","target":"etcd-endpoints://0xc000480000/172.20.7.80:2379","method":"/etcdserverpb.Maintenance/Status","attempt":0,"error":"rpc error: code = DeadlineExceeded desc = latest balancer error: connection error: desc = \"transport: Error while dialing: dial tcp 172.20.7.80:2379: connect: connection refused\""} Failed to get the status of endpoint https://172.20.7.80:2379 (context deadline exceeded) +--------------------------+------------------+---------+-----------------+---------+--------+-----------------------+-------+-----------+------------+-----------+------------+--------------------+--------+--------------------------+-------------------+ | ENDPOINT | ID | VERSION | STORAGE VERSION | DB SIZE | IN USE | PERCENTAGE NOT IN USE | QUOTA | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS | DOWNGRADE TARGET VERSION | DOWNGRADE ENABLED | +--------------------------+------------------+---------+-----------------+---------+--------+-----------------------+-------+-----------+------------+-----------+------------+--------------------+--------+--------------------------+-------------------+ | https://172.20.7.81:2379 | 31954c2b28bfea6a | 3.4.13 | | 9.0 MB | 4.0 MB | 56% | 0 B | true | false | 245 | 2264113 | 2264113 | | | false | | https://172.20.7.82:2379 | ff78f71ad246a1cd | 3.4.13 | | 8.9 MB | 4.0 MB | 56% | 0 B | false | false | 245 | 2264130 | 2264130 | | | false | +--------------------------+------------------+---------+-----------------+---------+--------+-----------------------+-------+-----------+------------+-----------+------------+--------------------+--------+--------------------------+-------------------+確認在 m1 node 的 etcd 已故障
- 在
m1主機停止 etcd containersudo mv /etc/kubernetes/manifests/etcd.yaml . - 將
m1移出 etcd cluster,注意此命令須在健康的 etcd 節點執行執行結果:etcdctl member remove 768bf4f42edfb9b3Member 768bf4f42edfb9b3 removed from cluster 4bfe2fbd656adb1a - 清理在
m1主機上 etcd 的資料ssh m1 sudo mv /var/lib/etcd/member /var/lib/etcd/member-old sudo mkdir -p /var/lib/etcd/member sudo chmod 700 /var/lib/etcd/member - 修改 etcd 參數修改內容如下:
sudo nano etcd.yamlspec: containers: - command: - etcd - --advertise-client-urls=https://172.20.7.80:2379 - --cert-file=/etc/kubernetes/pki/etcd/server.crt - --client-cert-auth=true - --data-dir=/var/lib/etcd - --initial-advertise-peer-urls=https://172.20.7.80:2380 #- --initial-cluster=m1=https://172.20.7.80:2380 ## 將上一行註解,並添加以下這行,如果原本有以下兩個參數,就修改成跟以下一樣 - --initial-cluster=m1=https://172.20.7.80:2380,m2=https://172.20.7.81:2380,m3=https://172.20.7.82:2380 - --initial-cluster-state=existing - 將
m1etcd 以learner的身份加回 etcd cluster,注意此命令須在健康的 etcd 節點執行執行結果:etcdctl member add m1 --peer-urls="https://172.20.7.80:2380" --learnerMember 3bf15e520c24448c added as learner to cluster 4bfe2fbd656adb1a ETCD_NAME="m1" ETCD_INITIAL_CLUSTER="m2=https://172.20.7.81:2380,m1=https://172.20.7.80:2380,m3=https://172.20.7.82:2380" ETCD_INITIAL_ADVERTISE_PEER_URLS="https://172.20.7.80:2380" ETCD_INITIAL_CLUSTER_STATE="existing" - 在
m1主機啟動 etcd containersudo mv etcd.yaml /etc/kubernetes/manifests/ - 確認
m1主機的 etcd 是否成功加入叢集,並啟動執行結果:etcdctl member list -w table+------------------+---------+------+--------------------------+--------------------------+------------+ | ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS | IS LEARNER | +------------------+---------+------+--------------------------+--------------------------+------------+ | 31954c2b28bfea6a | started | m2 | https://172.20.7.81:2380 | https://172.20.7.81:2379 | false | | fbc0ce2210efe023 | started | m1 | https://172.20.7.80:2380 | https://172.20.7.80:2379 | true | | ff78f71ad246a1cd | started | m3 | https://172.20.7.82:2380 | https://172.20.7.82:2379 | false | +------------------+---------+------+--------------------------+--------------------------+------------+ - 確認
m1主機 etcd 同步狀態執行結果:etcdctl endpoint status --cluster -w table+--------------------------+------------------+---------+-----------------+---------+--------+-----------------------+-------+-----------+------------+-----------+------------+--------------------+--------+--------------------------+-------------------+ | ENDPOINT | ID | VERSION | STORAGE VERSION | DB SIZE | IN USE | PERCENTAGE NOT IN USE | QUOTA | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS | DOWNGRADE TARGET VERSION | DOWNGRADE ENABLED | +--------------------------+------------------+---------+-----------------+---------+--------+-----------------------+-------+-----------+------------+-----------+------------+--------------------+--------+--------------------------+-------------------+ | https://172.20.7.81:2379 | 31954c2b28bfea6a | 3.4.13 | | 9.0 MB | 4.3 MB | 53% | 0 B | true | false | 379 | 2314290 | 2314290 | | | false | | https://172.20.7.80:2379 | fbc0ce2210efe023 | 3.4.13 | | 9.0 MB | 4.3 MB | 53% | 0 B | false | true | 379 | 2314290 | 2314290 | | | false | | https://172.20.7.82:2379 | ff78f71ad246a1cd | 3.4.13 | | 8.9 MB | 4.3 MB | 53% | 0 B | false | false | 379 | 2314290 | 2314290 | | | false | +--------------------------+------------------+---------+-----------------+---------+--------+-----------------------+-------+-----------+------------+-----------+------------+--------------------+--------+--------------------------+-------------------+觀察
RAFT_INDEX與RAFT_APPLIED_INDEX,若兩者相同或非常接近,代表該 learner 已完全跟上 leader 的日誌,catch‑up 已完成 - 確認 etcd learner 已同步資料後,將它升級為 member執行結果:
etcdctl member promote fbc0ce2210efe023Member fbc0ce2210efe023 promoted in cluster 4bfe2fbd656adb1a - 確認 API Server Contaienr 運作正常執行結果:
$ sudo crictl ps --name kube-apiserverCONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD NAMESPACE 8a13e70428b12 e58b890e4ab44 About a minute ago Running kube-apiserver 7 010418cc69c62 kube-apiserver-m1 kube-system - 確認
m1主機的 Pods 運作正常執行結果:kubectl get pods --field-selector spec.nodeName=m1 -ANAMESPACE NAME READY STATUS RESTARTS AGE kube-system calico-node-558x6 1/1 Running 1 8d kube-system etcd-m1 1/1 Running 0 25m kube-system kube-apiserver-m1 1/1 Running 7 36m kube-system kube-controller-manager-m1 1/1 Running 2 2d1h kube-system kube-proxy-6w529 1/1 Running 1 8d kube-system kube-scheduler-m1 1/1 Running 2 2d1h kube-system kube-vip-m1 1/1 Running 3 8d