實作 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/
- 選擇 "穩定-發行版"
點我展開圖片

- CPU 架構選擇:
amd64
, 檔案類別選擇:iso
,檔案儲存庫:auto
,確認好後點選 下載
按鈕點我展開圖片

- 可透過 Rufus 將 ISO 做成 USB 可開機裝置; 如過是虛擬環境則將 ISO 匯入虛擬化平台
2. 將第一顆硬碟備份,存為印象檔(disk image),放置在第二顆硬碟上
Step1: 由再生龍 live CD 開機
- 新增一顆硬碟 (稍後會作為儲放備份 disk image 的硬碟)
點我展開圖片

- 設定 Size 為
50
Gi (須視環境做調整),確認好後點選 Add 按鈕點我展開圖片

- 將 /dev/sdb 格式化為 XFS 檔案系統
- 將 VM 關機
- 點選 Hardware -> 點選 Add -> 點選 CD/DVD Driver
點我展開圖片

- 選擇 Storage 和 ISO image:
clonezilla-live-3.2.2-15-amd64.iso
,確認好後點選 Add 按鈕點我展開圖片

- 新增一顆硬碟 (稍後會作為儲放備份 disk image 的硬碟)
點我展開圖片

- 設定可開機裝置的順序,點選 options -> 點選 Boot Order -> 點選 Edit
點我展開圖片

- 將 再生龍 live CD 設為第一個可開機裝置,確認好後點選 OK 按鈕
點我展開圖片

- 將 VM 開機
點我展開圖片

Step2: Clonezilla live 開機選單
- 選擇 "Clonezilla live (Default settings, VGA 800x600)": 第1個選項是 Clonezilla Live 的預設模式:使用 framebuffer 並設定螢幕解析度為 800x600.
點我展開圖片

Step3: 選擇語言
- 選擇
zh_TW.UFT-8 Chinese (Traditional) | 正體中文 - 臺灣
點我展開圖片

Step4: 選擇鍵盤
- 使用預設的鍵盤,直接按 Enter 鍵
點我展開圖片

Step5: 選擇 "使用再生龍"
- 使用預設的選項,直接按 Enter 鍵
點我展開圖片

Step6: 選擇 "device-image"
- 選擇預設的選項:"device-image"
點我展開圖片

Step7: 選擇 "local_dev" 來指定 sdb 作為 Disk image 家目錄
- 選擇 "local_dev", 就可以將印象檔存到本機其他硬碟上或 USB 裝置. 如果使用 USB 儲存裝置,請等待一下後在將 USB 儲存裝置插上
點我展開圖片

- 將 USB 接上電腦,過 5 秒後按 Enter 鍵(前面步驟已先額外新增一顆硬碟,故此步驟可直接按 Enter 鍵)
點我展開圖片

- 每過幾秒就會顯示裝置掃描的結果,如下面的提示
點我展開圖片

- 確認裝置都認到後,按
Ctrl + C
Step8: 選擇 sdb 作為 Disk image 的目錄
- 選擇 sdb 硬碟裡的目錄作為印象檔目錄.
點我展開圖片

- 選擇 "fsck" 檢查來源硬碟的檔案系統
點我展開圖片

- 選擇要哪個目錄給再生龍存 disk image,確定後按 Tab 鍵,選擇
<Done>
點我展開圖片

- 顯示硬碟的使用狀況
點我展開圖片

- 選擇 "Beginner" (初學模式)
點我展開圖片

- 選擇 "savedisk"
點我展開圖片

Step9: 輸入 Disk image 檔名和選擇來源硬碟
- 輸入備份之後 disk image 的檔名,確認後按 Enter 鍵
點我展開圖片

- 選擇要備份哪一顆硬碟,本篇範例為 sda
點我展開圖片

- 選擇印象檔壓縮演算法
-z9p
(zstd):點我展開圖片

- 選擇 "fsck" 檢查來源硬碟的檔案系統
點我展開圖片

- 選擇檢查備份後的 Disk image 是否完整
點我展開圖片

- 選擇不要對 Disk image 加密
點我展開圖片

- 選擇將 log 檔也儲存在 Disk image 中
點我展開圖片

- 選擇
-p choose
,等程式跑完後再選擇關機或重開或進入命令模式點我展開圖片

- Clonezilla 出現此次工作的完整命令提示. 此命令可在製作客制化Clonezilla live時使用,按 Enter 鍵繼續
點我展開圖片

- 開始備份之前,再次詢問是否進行,輸入
y
,按 Enter 鍵確定開始備份點我展開圖片

Step 10: 完成將硬碟 (sda) 備份到第二顆硬碟 (sdb) 中
- 完成時,Clonezilla 提示若要再次使用再生龍選單的方式,按 Enter 鍵繼續
點我展開圖片

- 選擇關機
點我展開圖片

- 按 Enter 繼續關機
點我展開圖片

3. 將 Disk 還原到第一顆硬碟上
Step1: 由再生龍 live CD 開機
- 新增一顆硬碟 (稍後會作為儲放備份 disk image 的硬碟)
點我展開圖片

- 設定 Size 為
100
Gi (須視環境做調整),確認好後點選 Add 按鈕點我展開圖片

- 點選 Hardware -> 點選 Add -> 點選 CD/DVD Driver
點我展開圖片

- 選擇 Storage 和 ISO image:
clonezilla-live-3.2.2-15-amd64.iso
,確認好後點選 Add 按鈕點我展開圖片

- 設定可開機裝置的順序,點選 options -> 點選 Boot Order -> 點選 Edit
點我展開圖片

- 將 再生龍 live CD 設為第一個可開機裝置,確認好後點選 OK 按鈕
點我展開圖片

- 將 VM 開機
點我展開圖片

Step2: Clonezilla live 開機選單
- 選擇 "Clonezilla live (Default settings, VGA 800x600)": 第1個選項是 Clonezilla Live 的預設模式:使用 framebuffer 並設定螢幕解析度為 800x600.
點我展開圖片

Step3: 選擇語言
- 選擇
zh_TW.UFT-8 Chinese (Traditional) | 正體中文 - 臺灣
點我展開圖片

Step4: 選擇鍵盤
- 使用預設的鍵盤,直接按 Enter 鍵
點我展開圖片

Step5: 選擇 "使用再生龍"
- 使用預設的選項,直接按 Enter 鍵
點我展開圖片

Step6: 選擇 "device-image"
- 選擇預設的選項:"device-image"
點我展開圖片

Step7: 選擇 "local_dev" 來指定 sdb 作為 Disk image 家目錄
- 選擇 "local_dev", 就可以將印象檔存到本機其他硬碟上或 USB 裝置. 如果使用 USB 儲存裝置,請等待一下後在將 USB 儲存裝置插上
點我展開圖片

- 將 USB 接上電腦,過 5 秒後按 Enter 鍵(前面步驟已先額外新增一顆硬碟,故此步驟可直接按 Enter 鍵)
點我展開圖片

- 每過幾秒就會顯示裝置掃描的結果,如下面的提示
點我展開圖片

- 確認裝置都認到後,按
Ctrl + C
Step8: 選擇 sdb 作為 Disk image 的目錄
- 選擇 sdb 硬碟裡的目錄作為印象檔目錄.
點我展開圖片

- 選擇 "fsck" 檢查來源硬碟的檔案系統
點我展開圖片

- 選擇要哪個目錄給再生龍存 disk image,確定後按 Tab 鍵,選擇
<Done>
點我展開圖片

- 顯示硬碟的使用狀況
點我展開圖片

- 選擇 "Beginner" (初學模式)
點我展開圖片

- 選擇 "restoredisk"
點我展開圖片

Step9: 選擇印象檔與目標碟
- 選擇要透過哪個 Disk image 還原,確認後按 Enter 鍵
點我展開圖片

- 選擇要還原到哪一顆硬碟上,本篇範例為 sdc
點我展開圖片

- 選擇印象檔中的硬碟分割表
點我展開圖片

- 選擇 "是",在還原前檢查來源硬硬碟的檔案完整性
點我展開圖片

- 選擇將 log 檔也儲存在 Disk image 中
點我展開圖片

- 選擇
-p choose
,等程式跑完後再選擇關機或重開或進入命令模式點我展開圖片

- Clonezilla 出現此次工作的完整命令提示. 此命令可在製作客制化Clonezilla live時使用,按 Enter 鍵繼續
點我展開圖片

- 因為剛剛有選擇要檢查印象檔所以在還原之前, 就會進行印象檔的檢查
點我展開圖片

- 開始備份之前,詢問是否進行,按 Enter 鍵確定開始備份
點我展開圖片

- 開始備份之前,再次詢問是否進行,按
y
,再按 Enter 鍵確定開始備份點我展開圖片

- 開始備份之前,第三次詢問是否進行,按
y
,再按 Enter 鍵確定開始備份點我展開圖片

Step 10: 完成將硬碟 (sda) 備份到第二顆硬碟 (sdb) 中
- 完成時,Clonezilla 提示若要再次使用再生龍選單的方式,按 Enter 鍵繼續
點我展開圖片

- 選擇關機
點我展開圖片

- 按 Enter 繼續關機
點我展開圖片

Step 11: 驗證是否備份成功
-
設定可開機裝置的順序,點選 options -> 點選 Boot Order -> 點選 Edit
點我展開圖片

-
將第三顆硬碟 sdc 設為第一個可開機裝置,確認好後點選 OK 按鈕
點我展開圖片

-
將 VM 開機
點我展開圖片

-
檢視 K8s 節點是否健康
-
確認 K8s 核心服務是否正常
-
檢查該節點上的 Pod 是否正常
-
確認 etcd Member 是不是都還健在
-
確認 etcd 狀態
透過 IS LEADER 欄位確認 m2 主機是 etcd leader
4. 挑戰破壞 etcd leader vm 並透過再生龍備份的硬碟 restore 回去
開始備份與還原
- 確認當前哪一台節點是 etcd leader
由以上執行結果得知 leader 是 m1
節點
- 先透過再生龍備份
m1
主機的硬碟 sda (備份動作請參考前面的步驟,這裡不再贅述)
- 透過原來的硬碟 sda 將 VM 開機,並破壞 K8s
- 透過再生龍將備份好的 disk image 還原到新的硬碟 sdc 上 (還原動作請參考前面的步驟,這裡不再贅述)
- 透過還原好的硬碟 sdc 將 VM 開機
- 檢查在
m1
節點的 pod 是否有異常
發現 etcd 跟 API Server 掛了
- 查找 etcd 死亡原因
執行結果如下:
... 以上省略
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 流程。
- 提交索引
tocommit
超出日誌範圍
這裡 leader 傳來了一筆要 commit 的索引 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
- 檢查每個 etcd 的狀態
確認在 m1 node 的 etcd 已故障
- 在
m1
主機停止 etcd container
- 將
m1
移出 etcd cluster,注意此命令須在健康的 etcd 節點執行
執行結果:
- 清理在
m1
主機上 etcd 的資料
- 修改 etcd 參數
修改內容如下:
- 將
m1
etcd 以 learner
的身份加回 etcd cluster,注意此命令須在健康的 etcd 節點執行
執行結果:
- 在
m1
主機啟動 etcd container
- 確認
m1
主機的 etcd 是否成功加入叢集,並啟動
執行結果:
- 確認
m1
主機 etcd 同步狀態
執行結果:
觀察 RAFT_INDEX
與 RAFT_APPLIED_INDEX
,若兩者相同或非常接近,代表該 learner 已完全跟上 leader 的日誌,catch‑up 已完成
- 確認 etcd learner 已同步資料後,將它升級為 member
執行結果:
- 確認 API Server Contaienr 運作正常
執行結果:
- 確認
m1
主機的 Pods 運作正常
執行結果:
5. 參考資料