KubeVirt virt-handler 超深度架构分析

聚焦:模块定位 · 整体结构 · 启动流程 · VMI同步控制循环 · Domain同步 · Ghost Record · Finalizer管理 · 状态上报 · 非root模式


一、模块定位

1.1 virt-handler 的业务职责

virt-handler 是 KubeVirt 集群中每个计算节点上运行的核心代理进程,以 DaemonSet 形式部署。它的核心职责可以概括为:

职责类别具体内容
VMI 生命周期管理监听本节点 VMI 对象变化,驱动虚拟机创建、更新、删除全流程
Domain 同步将 Kubernetes 期望状态(VMI Spec)与 libvirt 实际状态(Domain)进行持续对比和调和
迁移协调管理迁移源端和目标端的代理连接、网络隧道、状态交接
设备管理通过 Device Plugin 向 kubelet 注册 GPU/KVM/vhost-net 等设备资源
节点标注Node Labeller 控制器将宿主机 CPU 特性、机器类型标注到 Node 对象
网络配置在 virt-launcher Pod 的网络命名空间中配置 CNI、passt、SR-IOV 等
存储挂载ContainerDisk 挂载、Hotplug Volume 挂载/卸载、HostDisk 创建
状态上报将 Domain 的运行状态(Phase、Conditions、GuestOSInfo、VolumeStatus 等)写回 VMI Status
安全合规SELinux relabel、Seccomp profile 安装、非 root 模式权限适配
心跳与就绪定期向 Node 写入心跳标签,标记节点为虚拟化就绪
迁移代理MigrationProxyManager 在源/目标节点间建立 TLS 加密的迁移隧道

1.2 在 KubeVirt 架构中的位置

Node 2

Node 1

KubeVirt Control Plane

用户请求
CRD 操作

创建 VMI 对象
调度到节点

创建 VMI 对象
调度到节点

gRPC cmd-client
SyncVirtualMachine

libvirt API

Domain Informer
notify-server

Migration Proxy
TLS 隧道

gRPC cmd-client

libvirt API

virt-api
REST API 网关

virt-controller
VMI 调度 & Pod 管理

virt-handler
节点代理 & VMI 控制循环

virt-launcher Pod
libvirt + QEMU 进程

Libvirt Domain
虚拟机实例

virt-handler

virt-launcher Pod

Libvirt Domain

virt-handler 是连接 KubeVirt 控制面与数据面的桥梁:

  • 向上:通过 Kubernetes API Server 读写 VMI/Node 等 CRD 对象
  • 向下:通过 gRPC(cmd-client)与 virt-launcher 通信,通过 Domain Informer(notify-server)监听 libvirt 事件
  • 横向:通过 MigrationProxyManager 与对端节点的 virt-handler 建立迁移隧道

二、模块整体结构

2.1 virt-handler 完整目录结构与子模块职责表

序号子目录/文件职责
1cache/Domain 共享 Informer、Ghost Record 存储、Checkpoint 持久化
2cgroup/Cgroup v1/v2 管理器,CPU/Memory 资源限制调整
3cmd-client/与 virt-launcher 的 gRPC 命令客户端(SyncVirtualMachine、KillVirtualMachine 等)
4container-disk/ContainerDisk 镜像挂载、校验和计算
5device-manager/Device Plugin 实现(KVM/GPU/vhost-net/SEV 注册)
6dmetrics-manager/DownwardMetrics 服务(向虚拟机暴露宿主机指标)
7filewatcher/文件监听工具
8heartbeat/节点心跳写入,标记 schedulable/虚拟化就绪
9hotplug-disk/Hotplug Volume 挂载/卸载(virt-chroot 操作)
10isolation/Pod 隔离检测(检测 virt-launcher PID、Mount Namespace)
11ksm/Kernel Samepage Merging 管理
12launcher-clients/LauncherClient 连接池管理(创建/获取/关闭/检测 unresponsive)
13migration-proxy/迁移代理管理器(源/目标端 TCP 隧道,TLS 加密)
14multipath-monitor/Multipath 设备 Socket 监控
15node-labeller/节点标签控制器(CPU 特性、机器类型、特性开关)
16notify-server/Domain 事件通知服务(virt-launcher → virt-handler 的 watch 通道)
17rest/HTTP REST 端点(Console/VNC/Lifecycle 操作)
18seccomp/Seccomp Profile 安装
19selinux/SELinux 上下文管理
20virt-chroot/chroot 命令封装(以 host namespace 执行特权操作)
21vsock/VSOCK 虚拟套接字管理(virt-handler → guest 通信)
22vm.goVirtualMachineController 核心逻辑(sync、processVmUpdate/Shutdown/Delete/Cleanup)
23controller.goBaseController 定义、公共逻辑(设备所有权、网络设置、迁移状态判断)
24migration-source.goMigrationSourceController(源端迁移控制)
25migration-target.goMigrationTargetController(目标端迁移控制)
26migration.go迁移公共逻辑
27non-root.go非 root 模式适配(Block 设备/VFIO/HostDisk/网络权限)
28options.goVirtualMachineOptions 构建(SMBios、Topology、ClusterConfig)
29cbt.goChanged Block Tracking 处理(备份增量跟踪)
30guestagent.goGuest Agent 相关逻辑
31retry_manager.goIO Error 指数退避重试管理器

2.2 启动流程(NewVirtHandler → Run → Informer → Controller 启动)

main()

service.Setup(app)
解析命令行参数

log.InitializeLogging

app.Run()

确定 HostOverride
= os.Hostname()

获取 PodIPAddress
= 必须非空

创建 KubeVirt Client
kubecli.GetKubevirtClientFromRESTConfig

markNodeAsUnschedulable
标记节点不可调度

创建 Event Recorder

创建 KubeInformerFactory
VMI/VMISource/VMITarget/BackupTracker Informer

创建 Domain SharedInformer
virtcache.NewSharedInformer

初始化 Ghost Record Cache
virtcache.InitializeGhostRecordCache

设置 Pod Isolation Detector
isolation.NewSocketBasedIsolationDetector

创建 ClusterConfig
virtconfig.NewClusterConfig

设置 TLS 配置
setupTLS()

创建 VSOCK Manager
vsock.NewVSOCKHypervisorService

创建 MigrationProxyManager

创建 Node Informer
过滤本节点

读取 capabilities.xml
解析 libvirt 能力

创建 NodeLabeller Controller

创建 LauncherClientsManager

创建 NetConf / NetStat

创建 DownwardMetricsManager

创建 CBTHandler

factory.Start(stop)
启动所有 Informer

domainSharedInformer.Run(stop)

nodeInformer.Run(stop)

cache.WaitForCacheSync
等待所有 Informer 同步完成

创建 MigrationSourceController

创建 MigrationTargetController

创建 VirtualMachineController
NewVirtualMachineController()

启动 Prometheus Server

启动 Certificate Managers

SELinux relabel /dev/net/tun, /dev/null

SetupMetrics

RunDownwardMetricsCollector

migrationSourceController.Run(5, stop)

migrationTargetController.Run(5, stop)

vmController.Run(10, stop)

ksmHandler.Run(stop)

启动 HTTP Server
Console/VNC/Lifecycle 端点

等待退出信号
SIGTERM → 优雅关闭

关键启动顺序约束:

  1. 证书管理器必须先于 TLS 配置启动
  2. Informer 缓存必须完全同步后才能创建 Controller
  3. NodeLabeller 须在 capabilities.xml 读取后才能创建
  4. DeviceManager 必须在 VMController.Run 中最先启动(kubelet 需要设备资源)
  5. Heartbeat 在 VMController.Run 的 cache.WaitForCacheSync 之后启动

2.3 VirtualMachineController 结构体全字段详解

type VirtualMachineController struct {
    *BaseController                                    // 嵌入基类,包含公共字段

    // ---- 核心配置 ----
    capabilities             *libvirtxml.Caps         // libvirt 宿主机能力(CPU、NUMA、机器类型)
    clientset                kubecli.KubevirtClient   // KubeVirt API 客户端
    hostCpuModel             string                   // 宿主机 CPU 模型名(用于 host-model 迁移判断)

    // ---- 存储与磁盘 ----
    containerDiskMounter     containerdisk.Mounter    // ContainerDisk 挂载器
    hotplugVolumeMounter     hotplugvolume.VolumeMounter // Hotplug Volume 挂载器

    // ---- 监控与指标 ----
    downwardMetricsManager   downwardMetricsManager   // DownwardMetrics 管理器

    // ---- 重试与容错 ----
    ioErrorRetryManager      *FailRetryManager        // IO Error 指数退避重试管理器

    // ---- 设备与心跳 ----
    deviceManagerController  *deviceManager.DeviceController  // Device Plugin 控制器
    heartBeat                *heartbeat.HeartBeat      // 节点心跳管理
    heartBeatInterval        time.Duration             // 心跳间隔(默认 1 分钟)

    // ---- 网络 ----
    netConf                  netconf                   // 网络配置接口(Setup/Teardown)
    sriovHotplugExecutorPool *executor.RateLimitedExecutorPool // SR-IOV 热插拔限速执行池

    // ---- 期望与缓存 ----
    vmiExpectations          *controller.UIDTrackingControllerExpectations  // VMI 状态更新期望计数
    vmiGlobalStore           cache.Store               // 全局 VMI Store(跨节点 VMI 可见性)

    // ---- 多路径与备份 ----
    multipathSocketMonitor   *multipathmonitor.MultipathSocketMonitor  // 多路径 Socket 监控
    cbtHandler               *CBTHandler               // Changed Block Tracking 处理器
}

BaseController 字段:

type BaseController struct {
    logger                      *log.FilteredLogger     // 过滤日志器
    host                        string                  // 本节点主机名
    clientset                   kubecli.KubevirtClient  // API 客户端
    queue                       workqueue.TypedRateLimitingInterface[string]  // 工作队列
    vmiStore                    cache.Store             // 本节点 VMI Store
    domainStore                 cache.Store             // Domain Store(来自 Informer)
    clusterConfig               *virtconfig.ClusterConfig  // 集群配置
    podIsolationDetector        isolation.PodIsolationDetector  // Pod 隔离检测器
    launcherClients             launcherclients.LauncherClientsManager  // gRPC 客户端池
    migrationProxy              migrationproxy.ProxyManager  // 迁移代理管理器
    virtLauncherFSRunDirPattern string                  // virt-launcher 运行目录模式
    netStat                     netstat                 // 网络状态接口
    recorder                    record.EventRecorder    // 事件记录器
    hasSynced                   func() bool             // 缓存同步检查函数
    hypervisorNodeInfo          hypervisor.HypervisorNodeInformation  // Hypervisor 节点信息
    hypervisorRuntime           hypervisor.VirtRuntime  // Hypervisor 运行时接口
}

2.4 核心接口定义

«interface»

netconf

+Setup(vmi, networks, launcherPid) : error

+Teardown(vmi) : error

«interface»

netstat

+UpdateStatus(vmi, domain) : error

+Teardown(vmi)

«interface»

downwardMetricsManager

+Run(stopCh)

+StartServer(vmi, pid) : error

+StopServer(vmi)

«interface»

LauncherClientsManager

+GetLauncherClient(vmi)(LauncherClient, error)

+GetVerifiedLauncherClient(vmi)(LauncherClient, error)

+IsLauncherClientUnresponsive(vmi)(bool, bool, error)

+CloseLauncherClient(vmi)

+GetLauncherClientInfo(vmi) : LauncherClientInfo

«interface»

PodIsolationDetector

+Detect(vmi)(IsolationResult, error)

«interface»

ProxyManager

+StartTargetListener(vmiUID, targetAddr) : error

+StopTargetListener(vmiUID)

+StartSourceListener(vmiUID, sourceAddr) : error

+StopSourceListener(vmiUID)

+OpenListenerCount() : int

+InitiateGracefulShutdown()

«interface»

VirtRuntime

+AdjustResources(vmi, config) : error

+HandleHousekeeping(vmi, cgroupManager, domain) : error

«interface»

HypervisorNodeInformation

+GetHypervisorDevice() : string

2.5 依赖注入关系

基础设施组件

Controllers

virt-handler.go (App)

注入

注入

注入

注入

注入

注入

注入

NewVirtualMachineController

嵌入

NewMigrationSourceController

NewMigrationTargetController

NewNodeLabeller

virtHandlerApp

VirtualMachineController

MigrationSourceController

MigrationTargetController

NodeLabellerController

kubecli.KubevirtClient

virtconfig.ClusterConfig

isolation.PodIsolationDetector

launcherclients.LauncherClientsManager

migrationproxy.ProxyManager

downwardMetricsManager

containerdisk.Mounter

hotplugvolume.VolumeMounter

netconf

netstat

record.EventRecorder

CBTHandler

heartbeat.HeartBeat

deviceManager.DeviceController

BaseController

关键依赖注入路径:

  • virtHandlerApp.Run() 是唯一的组合根(Composition Root)
  • 所有 Controller 所需依赖在 Run() 中构建后通过构造函数注入
  • ClusterConfig 通过 SetConfigModifiedCallback 实现运行时配置热更新
  • LauncherClientsManager 作为共享连接池被 VM/Migration Controller 共同使用

三、核心业务逻辑深度解析

3.1 VMI 同步控制循环完整流程

3.1.1 Informer 事件注册

VirtualMachineController 在构造时注册了两套 Informer 事件处理器:

Domain Informer (domainSharedInformer)

VMI Informer (vmiSourceHost)

key → queue

key → queue

key → queue

key → queue

key → queue

key → queue

key

AddFunc → addDeleteFunc

DeleteFunc → addDeleteFunc

UpdateFunc → updateFunc

AddFunc → addDomainFunc

DeleteFunc → deleteDomainFunc

UpdateFunc → updateDomainFunc

workqueue.RateLimitingQueue

execute(key)

事件处理器行为:

事件处理函数行为
VMI AddaddDeleteFunc重置 expectations 为 0/0,入队 key
VMI UpdateupdateFunc重置 expectations 为 0/0,入队 key
VMI DeleteaddDeleteFunc重置 expectations 为 0/0,入队 key
Domain AddaddDomainFunc仅入队 key
Domain UpdateupdateDomainFunc仅入队 key
Domain DeletedeleteDomainFunc处理 tombstone,入队 key

注意:VMI 和 Domain 共享同一个工作队列,通过 namespace/name 作为 key 关联。这意味着同一个 VMI 的两套事件会合并处理,避免并发冲突。

3.1.2 控制循环主框架(Run → runWorker → Execute → execute → sync)

key, quit := queue.Get()

Yes

No

err != nil

err == nil

Run(threadiness=10, stopCh)

初始化阶段

defer queue.ShutDown()

go deviceManagerController.Run(stop)

go downwardMetricsManager.Run(stop)

cache.WaitForCacheSync(stop, hasSynced)

遍历 domainStore
将无对应 VMI 的 Domain 入队

multipathSocketMonitor.Run()

heartBeat.Run(interval=1m, stop)

go ioErrorRetryManager.Run(stop)

启动 10 个 worker goroutine
go wait.Until(runWorker, 1s, stop)

runWorker()

Execute()

quit?

return false

execute(key)

queue.AddRateLimited(key)
指数退避重试

queue.Forget(key)

return true → 继续循环

3.1.3 execute() 函数:同步决策核心

No

Yes

Yes

No

No

Yes

Yes

Yes

No

No

Yes

No

Yes

No

Yes

No

Yes

No

execute(key)

getVMIFromCache(key)
获取本地 VMI

vmiExists?

从 vmiGlobalStore 查找
确认 VMI 是否真正删除

getDomainFromCache(key)
获取 Domain

vmiExists in global?

vmiExpectations.DeleteExpectations(key)

expectations
Satisfied?

return nil
等待 expectation 满足

vmiExists && domainExists
且 Domain.UID ≠ VMI.UID?

检测旧 Domain 的 launcher client
是否 unresponsive

expired?

processVmCleanup(oldVMI)
5s 后重新入队

等待 launcher client 初始化
1s 后重新入队

domainExists &&
(domainMigrated || DeletionTimestamp != nil)?

deleteVM(vmi)
孤立项处理

vmi 正在迁移中?

return nil
跳过迁移中的 VMI

vmiExists && !isVMIOwnedByNode?

return nil

vmiExists && IsMigrationSource?

return nil
源端由 MigrationSourceController 处理

sync(key, vmi, vmiExists, domain, domainExists)

/ Ghost Record UID 补充
若 vmi.UID 为空
尝试从 GhostRecord 获取

execute() 关键决策点解析:

  1. UID 冲突处理:当 Domain.UID ≠ VMI.UID 时,说明有同名但不同代的 VMI 存在。旧 Domain 如果 launcher client 已过期,则先清理旧 VMI,然后延迟重新入队处理新 VMI。

  2. Ghost Record UID 补充:如果 VMI 被删除但 UID 丢失(本地 informer 未捕获到完整信息),从 Ghost Record 磁盘缓存获取最后已知的 UID。

  3. 迁移中跳过:正在迁移的 VMI 不由 VMController 处理,而由专用的 MigrationSourceController/MigrationTargetController 处理。

  4. Expectations 机制:vmiExpectations 用于防止 VMI Status Update 操作尚未生效时重复处理。调用 Update API 时设置 expectation +1,收到 Informer 回调时重置。

3.1.4 sync() 函数:核心调和逻辑

Yes

No

Yes & vmi.IsRunning & domainAlive

Yes & vmi.IsRunning & !domainAlive

Yes & domainAlive

Yes & !domainAlive

Yes & domainAlive

Yes & !domainAlive

Yes

Yes

Yes

phase 一致

phase 不一致

Yes

No

shouldShutdown

forceShutdownIrrecoverable

shouldDelete

shouldUpdate

default

sync(key, vmi, vmiExists, domain, domainExists)

oldStatus := vmi.Status.DeepCopy()
oldSpec := vmi.Spec.DeepCopy()

初始化决策标志
shouldShutdown=false
shouldDelete=false
shouldUpdate=false
forceShutdownIrrecoverable=false

打印 VMI/Domain 状态日志

vmiExists && domainExists?

domainAlive = domain不是Shutoff/Crashed

domainAlive 判断

hasGracefulShutdownTrigger?

shouldShutdown = true

shouldDelete = true

!vmiExists?

shouldShutdown = true
VMI已删,域还活着

shouldDelete = true
VMI已删,域也不活

vmi.DeletionTimestamp != nil?

shouldShutdown = true

shouldDelete = true

vmi.IsFinal()?

shouldDelete = true
最终态清理

!domainAlive && domainExists && !vmi.IsFinal()?

shouldDelete = true
不活跃的 Domain

vmiExists && !vmi.IsFinal()?

calculateVmPhaseForStatusReason
检查 phase 是否一致

shouldUpdate = true

shouldUpdate = false
等待 phase 迁移

IO Error Retry
ShouldDelay?

shouldUpdate = false
延迟重入队列

shouldUpdate 保持 true

决策分发

processVmShutdown(vmi, domain)

processVmDestroy(vmi, domain)
→ vmiIrrecoverableError

deleteVM(vmi)

processVmUpdate(vmi, domain)

No update processing required

updateVMIStatus(oldStatus, vmi, domain, syncErr)
状态上报

sync() 决策优先级(从高到低):

优先级条件动作说明
1shouldShutdownprocessVmShutdownVMI 被删除/DeletionTimestamp/gracefulShutdown 触发
2forceShutdownIrrecoverableprocessVmDestroyPost-copy 迁移失败,强制销毁
3shouldDeletedeleteVMDomain 已停止或 VMI 终态,清理 Domain 和本地资源
4shouldUpdateprocessVmUpdateVMI Spec 需要同步到 Domain
5default无操作无需处理

3.2 VMI 创建 → virt-launcher Pod 创建 → Domain 同步全链路

libvirtvirt-launcherlauncher-clientsvirt-handlerAPI Servervirt-controllerlibvirtvirt-launcherlauncher-clientsvirt-handlerAPI Servervirt-controlleraddDeleteFunc → 入队 keyvirt-launcher Pod 启动libvirt 守护进程就绪监听 gRPC socketaddDomainFunc → 入队 key创建 VMI CRD 对象创建 virt-launcher Pod (调度到节点)VMI Informer: Add 事件execute(key) → sync()vmiExists=true, domainExists=falseshouldUpdate=trueprocessVmUpdate()checkLauncherClient(vmi)等待 launcher client 就绪isInitialized=false → 1s后重试检测到 gRPC socketisInitialized=truehandleStartingVMI()containerDiskMounter.MountAndVerify(vmi)hotplugVolumeMounter.Mount(vmi, cgroupManager)setupNetwork(vmi, startupNetworks, netConf)setupDevicesOwnerships(vmi)adjustResources(vmi)GetLauncherClient(vmi)clientclient.SyncVirtualMachine(vmi, options) [gRPC]libvirt virDomainDefineXMLvirDomainCreateDomain 创建成功Domain Informer: Add 事件 (notify-server)execute(key) → sync()vmiExists=true, domainExists=trueshouldUpdate=trueupdateVMIStatus() → Phase=RunningVMI Status Update (Phase=Running)

全链路关键步骤:

  1. virt-controller 调度 VMI 到节点并创建 virt-launcher Pod
  2. virt-handler 通过 VMI Informer 收到本地 VMI Add 事件
  3. processVmUpdate 检测 launcher client 未就绪,延迟重试
  4. virt-launcher Pod 启动完成后暴露 gRPC socket
  5. launcher-clients 检测到 socket,标记 client 为 initialized
  6. handleStartingVMI 按序执行:ContainerDisk → HotplugVolume → Network → DeviceOwnership → ResourceAdjust
  7. SyncVirtualMachine gRPC 调用将 VMI Spec 传递给 virt-launcher
  8. virt-launcher 调用 libvirt API 定义并启动 Domain
  9. Domain Informer 通过 notify-server 推送 Domain 事件回 virt-handler
  10. updateVMIStatus 将 Domain 状态转换为 VMI Phase/Conditions 写回 API Server

3.3 Domain 同步(目标域与实际域对比 → 创建/更新/删除)

Domain 同步的核心是 syncVirtualMachine() 函数,它通过 gRPC 将 VMI 的期望状态传递给 virt-launcher:

EFI OVMF rom missing

其他错误

成功

syncVirtualMachine(client, vmi, preallocatedVolumes)

构建 VirtualMachineOptions
SMBios + MemBalloonStatsPeriod
+ PreallocatedVolumes + Topology
+ ClusterConfig

设置 InterfaceDomainAttachment
domainspec.DomainAttachmentByInterfaceName

client.SyncVirtualMachine(vmi, options)
gRPC 调用

err?

virtLauncherCriticalSecurebootError
→ Phase=Failed

返回 err

return nil

Domain 状态对比逻辑(在 sync() 中实现):

No

Yes

No

Yes

Yes

No

Yes & domainAlive

Yes & !domainAlive

No

No

Yes

Yes

No

Yes & Shutoff/Crashed

Crashed/Panicked

Destroyed + ACPI

Destroyed + !ACPI

Shutdown/Saved/Snapshot

Migrated

VMI期望状态 vs Domain实际状态

VMI 存在?

Domain Alive?

shouldShutdown
Domain 需关闭

shouldDelete
Domain 需删除

VMI.IsFinal()?

shouldDelete
清理已终止 VMI

DeletionTimestamp?

shouldShutdown

shouldDelete

Domain 存在?

shouldUpdate
需创建 Domain
(通过 SyncVirtualMachine)

Phase 一致?

shouldUpdate
需同步 Domain Spec

等待 Phase 迁移

reason?

Phase=Failed

Phase=Failed

Phase=Succeeded

Phase=Succeeded

保持当前 Phase

3.4 Ghost Record 机制(未追踪 VMI 的处理)

Ghost Record 是 virt-handler 的"记忆恢复"机制,解决以下问题:

场景:virt-handler 重启后,可能存在已经启动的 VMI,但由于 Informer 缓存尚未同步,virt-handler 丢失了这些 VMI 的 UID 信息。

Ghost Record 存储

内存缓存
map[string]ghostRecord
key = namespace/name

磁盘持久化
Checkpoint 文件
路径 = VirtPrivateDir/ghost-records/
文件名 = VMI UID

ghostRecord 结构

Name: VMI 名称

Namespace: VMI 命名空间

SocketFile: gRPC socket 路径

UID: VMI 唯一标识

生命周期

写入时机
VMI 启动流程中
launcherClients 创建连接时

读取时机
execute() 中 vmi.UID 为空时
从 GhostRecordGlobalStore.LastKnownUID 获取

删除时机
processVmCleanup() 清理时

安全性

不可覆盖
同名不同 UID 的 Ghost Record
会返回错误

不可覆盖
同名不同 SocketFile
也会返回错误

Ghost Record 在 execute() 中的使用:

// execute() 中的 Ghost Record UID 补充逻辑(三层回退)

// 第一层:从 Domain 缓存获取 UID
if !vmiExists && string(domainCachedUID) != "" {
    vmi.UID = domainCachedUID
}

// 第二层:从 Ghost Record 获取 UID
if string(vmi.UID) == "" {
    uid := virtcache.GhostRecordGlobalStore.LastKnownUID(key)
    if uid != "" {
        vmi.UID = uid
    }
}

初始化流程(virt-handler 启动时):

checkpointPath = VirtPrivateDir/ghost-records

util.MkdirAllWithNosec(checkpointPath)

virtcache.InitializeGhostRecordCache
(NewIterableCheckpointManager)

遍历磁盘 Checkpoint 文件

读取每个 ghostRecord
加入内存 cache

3.5 Finalizer 管理与清理

KubeVirt 中 VMI 的 Finalizer 由 virt-controller 管理,virt-handler 不直接操作 Finalizer。但 virt-handler 负责在 VMI 到达终态后执行本地资源清理。

清理顺序(关键:Watchdog/Client 最后删除)

client 可用

client 不可用

VMI 到达终态
(Failed/Succeeded)

sync() 检测 vmi.IsFinal()

shouldDelete = true

deleteVM(vmi)

processVmDelete(vmi)

GetVerifiedLauncherClient(vmi)

client.DeleteDomain(vmi)
通知 virt-launcher 删除 libvirt Domain

Pod 已拆除,跳过

processVmCleanup(vmi)

migrationProxy.StopTargetListener(vmiId)

migrationProxy.StopSourceListener(vmiId)

downwardMetricsManager.StopServer(vmi)

containerDiskMounter.Unmount(vmi)

hotplugVolumeMounter.UnmountAll(vmi, cgroupManager)

teardownNetwork(vmi)
清理网络缓存文件

sriovHotplugExecutorPool.Delete(vmi.UID)

launcherClients.CloseLauncherClient(vmi)
关闭 gRPC 连接 + 删除 socket

domainStore.Delete(domain)
从 Domain 缓存移除

清理顺序的设计哲学:

  • Watchdog 文件和 gRPC Client 必须最后删除——这是因为在清理过程中可能还需要与 virt-launcher 通信,如果先关闭连接就无法完成剩余清理
  • 迁移代理先停止——防止清理过程中有新的迁移连接进入
  • 网络最后清理——因为 virtiofs 和网络设备需要在 Domain 销毁后才可安全清理

3.6 VMI 状态上报(Phase/Conditions 迁移)

3.6.1 Phase 迁移

VMI 被调度到节点

Domain Running/Blocked/PMSuspended
或 Paused(非PostcopyFailed)

launcher client unresponsive
且非迁移目标

迁移目标端
等待 Domain 同步

Domain Shutoff
reason=Shutdown/Saved/FromSnapshot
或 Destroyed(!ACPI)

Domain Crashed/Panicked
或 Destroyed(ACPI)
或 PostcopyFailed

Domain Paused
(各种原因)

Scheduled

Running

Failed

WaitingForSync

Succeeded

Paused

Phase 计算逻辑(calculateVmPhaseForStatusReason):

Domain 状态Domain 原因VMI Phase
nil + VMI Scheduled + launcher responsive-Scheduled
nil + VMI Scheduled + launcher unresponsive-Failed
nil + VMI Running + 非迁移目标-Failed
nil + VMI Running + 迁移目标-WaitingForSync
Running/Blocked/PMSuspended-Running
PausedPostcopyFailedFailed
Paused其他Running
Shutoff/CrashedCrashed/PanickedFailed
ShutoffDestroyed + ACPI enabledFailed
ShutoffDestroyed + !ACPISucceeded
ShutoffShutdown/Saved/FromSnapshotSucceeded
ShutoffMigrated保持当前 Phase
3.6.2 Conditions 更新

updateVMIConditions(vmi, domain, condManager)

updateAccessCredentialConditions
访问凭证同步状态

updateLiveMigrationConditions
迁移可行性判断

updateGuestAgentConditions
Guest Agent 连接状态

updatePausedConditions
暂停状态

checkVolumesForMigration
PVC 共享模式检查

checkNetworkInterfacesForMigration
网络接口可迁移性

isHostModelMigratable
CPU host-model 迁移性

检查 PCI HostDevice/GPU

检查 SEV/TDX/SecureExecution

检查 SCSI Persistent Reservation

检查 TSC Frequency

检查 Hyperv Passthrough

综合判断
IsMigratable=True/False
MigrationMethod=Live/Block

Conditions 类型清单:

Condition Type含义Status
VirtualMachineInstanceIsMigratableVMI 是否可迁移True/False
VirtualMachineInstanceIsStorageLiveMigratableVMI 是否可存储实时迁移True/False
VirtualMachineInstanceAgentConnectedQEMU Guest Agent 连接True/False
VirtualMachineInstanceUnsupportedAgentGA 版本不兼容True
VirtualMachineInstancePausedVMI 被暂停True
VirtualMachineInstanceAccessCredentialsSynchronized访问凭证同步True/False
3.6.3 完整状态上报流程

Yes

No

Yes

No

Yes

No

Yes

No

Yes

No

updateVMIStatus(oldStatus, vmi, domain, syncError)

vmi.IsFinal()?

跳过状态更新

updateVMIStatusFromDomain(vmi, domain)

updateIsoSizeStatus
ISO 镜像文件大小

updateSELinuxContext
SELinux 上下文

updateGuestInfoFromDomain
GuestOS 信息

updateVolumeStatusesFromDomain
卷状态(含 Hotplug/MemoryDump)

updateFSFreezeStatus
文件系统冻结状态

updateBackupStatus
CBT 备份状态

updateMachineType
机器类型

updateMemoryInfo
当前内存用量

cbtHandler.HandleChangedBlockTracking
CBT 状态迁移

netStat.UpdateStatus
网络接口状态

setVmPhaseForStatusReason
计算并设置 Phase

updateVMIConditions
更新 Conditions

updateChecksumInfo
ContainerDisk/KernelBoot 校验和

handleSyncError
处理同步错误

CriticalNetworkError?

Phase=Failed

SecurebootError?

Phase=Failed

vmiIrrecoverableError?

Phase=Failed

condManager.CheckFailure
设置 SyncFailed condition

SetVMIPhaseTransitionTimestamp
记录 Phase 变迁时间

Status 是否变化?

vmiExpectations.SetExpectations(key, 1, 0)
clientset.Update VMI

跳过 API 调用

记录 Phase 变迁事件

3.7 非 root 模式适配

非 root 模式是 KubeVirt 的安全增强特性,允许 virt-launcher Pod 以非 root 用户运行。virt-handler 需要在启动流程中为非 root 场景做额外的设备权限适配。

Yes

No

setupDevicesOwnerships(vmi)

podIsolationDetector.Detect(vmi)
获取 Pod 隔离信息

isolationRes.MountRoot()
获取 virt-launcher 根文件系统挂载点

claimDeviceOwnership
/dev/kvm (或 /dev/vhost-vsock)

configureHostDisks
HostDisk 创建(含空间容忍度检查)

configureSEVDeviceOwnership
SEV 设备 /dev/sev 所有权

vmitrait.IsNonRoot(vmi)?

nonRootSetup(vmi)

跳过非 root 适配

prepareStorage(vmi, res)
Block 设备所有权
HostDisk 目录/文件所有权

prepareVFIO(res)
VFIO 组设备所有权
chmod 0666 /dev/vfio/vfio

prepareNetwork(vmi, res)
vhost-net 设备所有权
tun 设备所有权

configureVirtioFS(vmi, isolationRes)
virtiofs socket 所有权

非 root 适配的核心原则:

  • virt-launcher 以非 root 运行时,无法直接访问 /dev/kvm、/dev/vhost-net、/dev/net/tun 等设备
  • virt-handler 以 root 身份在宿主机上运行,通过 Pod Isolation Detector 进入 virt-launcher 的 Mount Namespace
  • 使用 diskutils.DefaultOwnershipManager.SetFileOwnership 将设备文件的所有权改为 virt-launcher 的 UID
  • VFIO 设备需要额外的 chmod 0666 操作,因为 runc 的 eBPF 过滤程序会拒绝非 owner 的 access 系统调用

3.7.1 非 root 与 root 模式对比

操作root 模式非 root 模式
KVM 设备访问默认可访问需 claimDeviceOwnership
Block Device默认可访问需 changeOwnershipOfBlockDevices
VFIO 设备默认可访问需 prepareVFIO (chmod 0666)
HostDisk直接创建需 changeOwnershipOfHostDisks
vhost-net/tun默认可访问需 prepareNetwork
SEV 设备直接访问需 configureSEVDeviceOwnership
VirtioFS Socket直接访问需 configureVirtioFS

Mermaid 图索引

序号图名类型位置
1KubeVirt 架构位置图graph TB一、1.2
2启动流程图flowchart TD二、2.2
3VirtualMachineController 类图classDiagram二、2.4
4依赖注入图graph LR二、2.5
5Informer 注册图flowchart LR三、3.1.1
6控制循环主框架图flowchart TD三、3.1.2
7execute() 决策核心图flowchart TD三、3.1.3
8sync() 调和逻辑图flowchart TD三、3.1.4
9VMI 创建全链路时序图sequenceDiagram三、3.2
10Domain 同步流程图flowchart TD三、3.3
11Ghost Record 机制图flowchart TD三、3.4
12Finalizer/清理流程图flowchart TD三、3.5
13Phase 迁移状态图stateDiagram-v2三、3.6.1
14Conditions 更新图flowchart TD三、3.6.2
15完整状态上报流程图flowchart TD三、3.6.3
16非 root 模式适配图flowchart TD三、3.7
17Domain 状态对比图flowchart TD三、3.3
18迁移可行性检查图flowchart TD三、3.6.2

关键设计模式总结

1. Level-Driven Reconciliation(水平驱动调和)

virt-handler 采用经典的 Kubernetes Controller 模式:

  • 不依赖事件顺序:每次从队列取出 key,重新获取 VMI 和 Domain 的当前状态
  • 幂等处理:多次执行 sync() 产生相同结果
  • 期望状态 vs 实际状态:VMI Spec 是期望,Domain 是实际,差距驱动操作

2. 双 Informer 融合

VMI Informer(Kubernetes 层)和 Domain Informer(libvirt 层)的事件汇聚到同一个工作队列,通过 key(namespace/name)关联。这确保了:

  • VMI 变化触发 Domain 同步
  • Domain 事件触发 VMI 状态更新
  • 避免两套事件循环的竞争条件

3. Expectations 机制

vmiExpectations 防止 VMI Status Update 操作的 “抖动”:

  • 调用 clientset.Update() 前设置 Add=1
  • Informer 收到更新回调后重置为 0
  • 在 Expectation 满足前跳过该 key 的处理

4. Ghost Record 持久化

解决 virt-handler 重启后的 VMI UID 丢失问题:

  • 启动时写入磁盘 Checkpoint
  • 运行时维护内存缓存
  • 清理时删除 Checkpoint

5. IO Error 指数退避

FailRetryManager 针对 Domain 因 IO Error 暂停的场景:

  • 首次失败:不延迟,立即重试
  • 连续失败:指数退避(10s → 20s → 40s → … → 最大 3min)
  • 成功后:重置退避状态
  • 超过 maxFailResponseTime(30s)无失败信号:视为修复成功

6. 优雅关闭与强制销毁的分层

  • processVmShutdown:tryGracefully=true,先发 ACPI 信号,等待 grace period
  • processVmDestroy:tryGracefully=false,直接 KillVirtualMachine
  • handleVMIShutdown:每 5s 重发 ACPI 信号,防止启动中的 OS 丢失信号
  • grace period 过期:从 Shutdown 切换到 Kill

本文档基于 KubeVirt 源码深度分析,覆盖 virt-handler 核心架构、启动流程、VMI 同步控制循环、Domain 同步、Ghost Record、Finalizer 管理、状态上报和非 root 模式等关键主题。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐