議實現虛擬機熱遷移的完整原理(含回滾機制))
RancherVM 源碼剖析③基于 QEMU QMP 協(xié)議實現虛擬機熱遷移的完整原理含回滾機制【免費下載鏈接】old-vm(OBSOLETE) Package and Run Virtual Machines as Docker Containers項目地址: https://gitcode.com/gh_mirrors/ol/old-vmRancherVM一個將虛擬機打包并以 Docker 容器形式運行在 Kubernetes 上的開源項目Package and Run Virtual Machines as Docker Containers不僅能讓虛擬機像 Pod 一樣被調度還實現了真正的虛擬機熱遷移在不停機的情況下把正在運行的虛擬機從一個節(jié)點搬到另一個節(jié)點。本文從源碼出發(fā)帶你完整理解它是如何利用QEMU QMP 協(xié)議完成遷移的以及遷移中途取消時回滾機制是如何兜底的。什么是虛擬機熱遷移熱遷移Live Migration指在虛擬機持續(xù)運行的狀態(tài)下將其內存、CPU 狀態(tài)、磁盤等遷移到目標主機全程業(yè)務幾乎無感知。在 RancherVM 中觸發(fā)方式極其簡單把 VirtualMachine 資源里的nodeName字段改成另一個節(jié)點控制器就會自動開始遷移。源碼中的狀態(tài)機為此新增了migrating狀態(tài)定義見 pkg/apis/ranchervm/v1alpha1/types.gorunning→ 用戶修改nodeName后 →migrating→ 遷移完成回到running但落在新節(jié)點遷移中途用戶把nodeName改回原節(jié)點 →觸發(fā)回滾虛擬機原地恢復運行整體架構一次熱遷移的 4 步走熱遷移涉及三個角色源節(jié)點上正在跑的 VM Pod、目標節(jié)點上新建的 VM Pod以及一個專門執(zhí)行遷移指令的遷移 Job Pod。步驟動作關鍵源碼① 狀態(tài)切換檢測到nodeName變化狀態(tài)置為migratingpkg/controller/vm/machine.go② 準備目標在目標節(jié)點創(chuàng)建第二個 VM Pod等待兩邊就緒pkg/controller/vm/migrate.go③ 執(zhí)行遷移創(chuàng)建 Job通過 QMP 向源 QEMU 下發(fā)migrate命令pkg/qemu/job.go、pkg/qemu/client.go④ 清理收尾刪除舊 Pod 和 Job切換 VirtualMachine 歸屬pkg/controller/vm/migrate.go入口邏輯在pkg/controller/vm/machine.go的start()中當發(fā)現 VM 處于運行狀態(tài)、但期望節(jié)點與 Pod 實際所在節(jié)點不一致時就調用migrateMachine()。核心原理如何與 QEMU 對話QMP 協(xié)議QMPQEMU Machine Protocol是 QEMU 提供的JSON over Socket管理接口。每個 VM Pod 內都會暴露一個 Unix Socket形如vm名稱_monitor.sockRancherVM 的所有 QEMU 操作都通過它完成。1?? 建立連接三步握手客戶端實現非常精巧見 pkg/qemu/client.goNewMonitorClient()通過 Unix Socket 連接到 QEMU 監(jiān)控端讀取 QEMU 主動發(fā)來的greeting 消息聲明 QMP 能力立即發(fā)送qmp_capabilities命令完成能力協(xié)商——這一步是 QMP 協(xié)議的硬性要求之后才能執(zhí)行真正的操作命令。連接命令的拼裝邏輯在 pkg/qemu/commands.go所有命令都是統(tǒng)一的 JSON 結構{execute: 命令名, arguments: { ... 參數 ... }}2?? 下發(fā)遷移指令migrate(uri)命令把源 VM 的全部狀態(tài)通過目標端提供的TCP 地址tcp:目標PodIP:遷移端口推送過去{execute: migrate, arguments: {uri: tcp:10.42.0.7:43501, detach: true}}detach: true表示 QEMU 異步執(zhí)行遷移客戶端立刻返回之后靠輪詢查進度。值得一提的是readReply()的實現細節(jié)它從 512 字節(jié)緩沖區(qū)起步每讀滿一次就翻倍512 → 1024 → …直到某次讀取不滿從而優(yōu)雅地應對任意長度的 JSON 應答。3?? 輪詢遷移進度query-migrate真正的遷移引擎在 pkg/qemu/migrate.go 的Migrate()方法它每秒輪詢一次邏輯清晰得像一段狀態(tài)機每秒循環(huán) ├─ readSilently() // 靜默吞掉 QEMU 的事件推送如 STOP 事件避免污染應答流 ├─ query-migrate // 查詢遷移狀態(tài) ├─ status active → 記錄已傳輸內存 / 總內存 / 速率估算剩余時間 ├─ status completed → 遷移成功返回 └─ status failed → 遷移失敗報錯兩個值得學習的細節(jié)readSilently()給連接設一個 100ms 的讀超時把 QEMU 主動推送的事件悄悄讀走否則事件消息會混進query-migrate的應答導致 JSON 解析錯亂——這是用長連接驅動 QEMU 時最容易踩的坑剩余時間估算用已耗時 × 總量 / 已傳輸量 - 已耗時做瞬時速率推算簡單實用。遷移 Job把 QMP 客戶端打包成 K8s Job控制器本身跑在集群里、夠不到節(jié)點上的 QEMU Socket所以它采用了一個巧妙的設計創(chuàng)建一個一次性 Kubernetes Job通過 Pod 親和性把它調度到源 VM 所在的節(jié)點上詳見 pkg/qemu/job.go 的NewMigrationJob()。這個 Job Pod 有 3 個關鍵配置Pod 親和性TopologyKey: kubernetes.io/hostname 源 Pod 標簽保證遷移 Pod 和源 VM 落在同一臺機器掛載 hostPath把節(jié)點上 VM 狀態(tài)目錄掛到/vm這樣就能摸到 QEMU 的_monitor.sock執(zhí)行參數/ranchervm -migrate -sock-path /vm/pod_monitor.sock -target-uri tcp:目標IP:端口對應 cmd/main.go 中的migrate分支——它會創(chuàng)建MonitorClient并直接調用Migrate()。目標端口哪來的新建的目標 VM Pod 在創(chuàng)建時addMigratePort()見 pkg/controller/vm/util.go會隨機分配一個 32768~65535 之間的端口寫入環(huán)境變量MIGRATE_PORT和注解migrate_port。源 QEMU 遷移時就把內存狀態(tài)流推送到這個端口目標端的 QEMU 早已在該端口監(jiān)聽并接收最終完成 CPU/設備狀態(tài)的切換?;貪L機制遷移失敗或用戶取消怎么辦這是本設計最有工程味的部分核心在 pkg/controller/vm/migrate.go 的migrateMachine()// Check if the user canceled mid-migration if oldPod ! nil machine.Spec.NodeName oldPod.Spec.NodeName machine.Status.State api.StateMigrating { return ctrl.migrateRollback(machine, newPod) }只要用戶把nodeName改回原節(jié)點控制器在下一次調和循環(huán)就會發(fā)現期望節(jié)點 舊 Pod 所在節(jié)點判定為用戶取消隨即執(zhí)行migrateRollback()做三件事刪除還在跑的遷移 Job源端 QEMU 停止向目標推流遷移自然終止刪除目標節(jié)點上已創(chuàng)建的影子 Pod把 VirtualMachine 狀態(tài)從migrating恢復為running。這個機制之所以零數據風險是因為 QEMU 熱遷移本身的語義就保證了遷移未成功完成時源端 VM 一直是主執(zhí)行者目標端只是接收副本任何時刻掐掉遷移業(yè)務都還活在源節(jié)點。RancherVM 的回滾只是把這個天然安全的狀態(tài)顯式地整理干凈。遷移成功后的清理migrationCleanup()則負責收尾更新 VirtualMachine 關聯新 Pod、刪除源 Pod、刪除遷移 Job最后還會刪掉noVNC 控制臺 Pod——因為控制臺也是通過節(jié)點上的 Unix Socket 連接 QEMU 的VM 換了節(jié)點后必須重建。小結這套設計值得借鑒的 3 個地方協(xié)議層極薄pkg/qemu/包不到 300 行就把 QMP 連接、命令封裝、進度輪詢寫得干凈利落是學習用長連接驅動外部守護進程的好樣例編排層復用 K8s 原語親和性保證同機執(zhí)行、Job 承載一次性任務、Informers 的事件驅動調和——遷移全程沒有一行自定義調度代碼冪等 可取消控制器每一步建目標 Pod、查 Job 是否存在、狀態(tài)比對都是冪等檢查任意時刻被中斷都能從狀態(tài)機里自愈用戶改回nodeName即回滾體驗上遷移和回退完全對稱。至此RancherVM 熱遷移的完整鏈路——nodeName 變更 → 狀態(tài)機流轉 → 目標 Pod 準備 → QMP 下發(fā) migrate → 輪詢 query-migrate → 清理 / 回滾——就全部講完了。【免費下載鏈接】old-vm(OBSOLETE) Package and Run Virtual Machines as Docker Containers項目地址: https://gitcode.com/gh_mirrors/ol/old-vm創(chuàng)作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考