【kubevirt】(virt-handler Part 1)KubeVirt virt-handler 超深度架构分析
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 架构中的位置
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 完整目录结构与子模块职责表
| 序号 | 子目录/文件 | 职责 |
|---|---|---|
| 1 | cache/ | Domain 共享 Informer、Ghost Record 存储、Checkpoint 持久化 |
| 2 | cgroup/ | Cgroup v1/v2 管理器,CPU/Memory 资源限制调整 |
| 3 | cmd-client/ | 与 virt-launcher 的 gRPC 命令客户端(SyncVirtualMachine、KillVirtualMachine 等) |
| 4 | container-disk/ | ContainerDisk 镜像挂载、校验和计算 |
| 5 | device-manager/ | Device Plugin 实现(KVM/GPU/vhost-net/SEV 注册) |
| 6 | dmetrics-manager/ | DownwardMetrics 服务(向虚拟机暴露宿主机指标) |
| 7 | filewatcher/ | 文件监听工具 |
| 8 | heartbeat/ | 节点心跳写入,标记 schedulable/虚拟化就绪 |
| 9 | hotplug-disk/ | Hotplug Volume 挂载/卸载(virt-chroot 操作) |
| 10 | isolation/ | Pod 隔离检测(检测 virt-launcher PID、Mount Namespace) |
| 11 | ksm/ | Kernel Samepage Merging 管理 |
| 12 | launcher-clients/ | LauncherClient 连接池管理(创建/获取/关闭/检测 unresponsive) |
| 13 | migration-proxy/ | 迁移代理管理器(源/目标端 TCP 隧道,TLS 加密) |
| 14 | multipath-monitor/ | Multipath 设备 Socket 监控 |
| 15 | node-labeller/ | 节点标签控制器(CPU 特性、机器类型、特性开关) |
| 16 | notify-server/ | Domain 事件通知服务(virt-launcher → virt-handler 的 watch 通道) |
| 17 | rest/ | HTTP REST 端点(Console/VNC/Lifecycle 操作) |
| 18 | seccomp/ | Seccomp Profile 安装 |
| 19 | selinux/ | SELinux 上下文管理 |
| 20 | virt-chroot/ | chroot 命令封装(以 host namespace 执行特权操作) |
| 21 | vsock/ | VSOCK 虚拟套接字管理(virt-handler → guest 通信) |
| 22 | vm.go | VirtualMachineController 核心逻辑(sync、processVmUpdate/Shutdown/Delete/Cleanup) |
| 23 | controller.go | BaseController 定义、公共逻辑(设备所有权、网络设置、迁移状态判断) |
| 24 | migration-source.go | MigrationSourceController(源端迁移控制) |
| 25 | migration-target.go | MigrationTargetController(目标端迁移控制) |
| 26 | migration.go | 迁移公共逻辑 |
| 27 | non-root.go | 非 root 模式适配(Block 设备/VFIO/HostDisk/网络权限) |
| 28 | options.go | VirtualMachineOptions 构建(SMBios、Topology、ClusterConfig) |
| 29 | cbt.go | Changed Block Tracking 处理(备份增量跟踪) |
| 30 | guestagent.go | Guest Agent 相关逻辑 |
| 31 | retry_manager.go | IO Error 指数退避重试管理器 |
2.2 启动流程(NewVirtHandler → Run → Informer → Controller 启动)
关键启动顺序约束:
- 证书管理器必须先于 TLS 配置启动
- Informer 缓存必须完全同步后才能创建 Controller
- NodeLabeller 须在 capabilities.xml 读取后才能创建
- DeviceManager 必须在 VMController.Run 中最先启动(kubelet 需要设备资源)
- 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 核心接口定义
2.5 依赖注入关系
关键依赖注入路径:
virtHandlerApp.Run()是唯一的组合根(Composition Root)- 所有 Controller 所需依赖在
Run()中构建后通过构造函数注入 - ClusterConfig 通过
SetConfigModifiedCallback实现运行时配置热更新 - LauncherClientsManager 作为共享连接池被 VM/Migration Controller 共同使用
三、核心业务逻辑深度解析
3.1 VMI 同步控制循环完整流程
3.1.1 Informer 事件注册
VirtualMachineController 在构造时注册了两套 Informer 事件处理器:
事件处理器行为:
| 事件 | 处理函数 | 行为 |
|---|---|---|
| VMI Add | addDeleteFunc | 重置 expectations 为 0/0,入队 key |
| VMI Update | updateFunc | 重置 expectations 为 0/0,入队 key |
| VMI Delete | addDeleteFunc | 重置 expectations 为 0/0,入队 key |
| Domain Add | addDomainFunc | 仅入队 key |
| Domain Update | updateDomainFunc | 仅入队 key |
| Domain Delete | deleteDomainFunc | 处理 tombstone,入队 key |
注意:VMI 和 Domain 共享同一个工作队列,通过 namespace/name 作为 key 关联。这意味着同一个 VMI 的两套事件会合并处理,避免并发冲突。
3.1.2 控制循环主框架(Run → runWorker → Execute → execute → sync)
3.1.3 execute() 函数:同步决策核心
execute() 关键决策点解析:
-
UID 冲突处理:当 Domain.UID ≠ VMI.UID 时,说明有同名但不同代的 VMI 存在。旧 Domain 如果 launcher client 已过期,则先清理旧 VMI,然后延迟重新入队处理新 VMI。
-
Ghost Record UID 补充:如果 VMI 被删除但 UID 丢失(本地 informer 未捕获到完整信息),从 Ghost Record 磁盘缓存获取最后已知的 UID。
-
迁移中跳过:正在迁移的 VMI 不由 VMController 处理,而由专用的 MigrationSourceController/MigrationTargetController 处理。
-
Expectations 机制:
vmiExpectations用于防止 VMI Status Update 操作尚未生效时重复处理。调用UpdateAPI 时设置 expectation +1,收到 Informer 回调时重置。
3.1.4 sync() 函数:核心调和逻辑
sync() 决策优先级(从高到低):
| 优先级 | 条件 | 动作 | 说明 |
|---|---|---|---|
| 1 | shouldShutdown | processVmShutdown | VMI 被删除/DeletionTimestamp/gracefulShutdown 触发 |
| 2 | forceShutdownIrrecoverable | processVmDestroy | Post-copy 迁移失败,强制销毁 |
| 3 | shouldDelete | deleteVM | Domain 已停止或 VMI 终态,清理 Domain 和本地资源 |
| 4 | shouldUpdate | processVmUpdate | VMI Spec 需要同步到 Domain |
| 5 | default | 无操作 | 无需处理 |
3.2 VMI 创建 → virt-launcher Pod 创建 → Domain 同步全链路
全链路关键步骤:
- virt-controller 调度 VMI 到节点并创建 virt-launcher Pod
- virt-handler 通过 VMI Informer 收到本地 VMI Add 事件
- processVmUpdate 检测 launcher client 未就绪,延迟重试
- virt-launcher Pod 启动完成后暴露 gRPC socket
- launcher-clients 检测到 socket,标记 client 为 initialized
- handleStartingVMI 按序执行:ContainerDisk → HotplugVolume → Network → DeviceOwnership → ResourceAdjust
- SyncVirtualMachine gRPC 调用将 VMI Spec 传递给 virt-launcher
- virt-launcher 调用 libvirt API 定义并启动 Domain
- Domain Informer 通过 notify-server 推送 Domain 事件回 virt-handler
- updateVMIStatus 将 Domain 状态转换为 VMI Phase/Conditions 写回 API Server
3.3 Domain 同步(目标域与实际域对比 → 创建/更新/删除)
Domain 同步的核心是 syncVirtualMachine() 函数,它通过 gRPC 将 VMI 的期望状态传递给 virt-launcher:
Domain 状态对比逻辑(在 sync() 中实现):
3.4 Ghost Record 机制(未追踪 VMI 的处理)
Ghost Record 是 virt-handler 的"记忆恢复"机制,解决以下问题:
场景:virt-handler 重启后,可能存在已经启动的 VMI,但由于 Informer 缓存尚未同步,virt-handler 丢失了这些 VMI 的 UID 信息。
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 启动时):
3.5 Finalizer 管理与清理
KubeVirt 中 VMI 的 Finalizer 由 virt-controller 管理,virt-handler 不直接操作 Finalizer。但 virt-handler 负责在 VMI 到达终态后执行本地资源清理。
清理顺序的设计哲学:
- Watchdog 文件和 gRPC Client 必须最后删除——这是因为在清理过程中可能还需要与 virt-launcher 通信,如果先关闭连接就无法完成剩余清理
- 迁移代理先停止——防止清理过程中有新的迁移连接进入
- 网络最后清理——因为 virtiofs 和网络设备需要在 Domain 销毁后才可安全清理
3.6 VMI 状态上报(Phase/Conditions 迁移)
3.6.1 Phase 迁移
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 |
| Paused | PostcopyFailed | Failed |
| Paused | 其他 | Running |
| Shutoff/Crashed | Crashed/Panicked | Failed |
| Shutoff | Destroyed + ACPI enabled | Failed |
| Shutoff | Destroyed + !ACPI | Succeeded |
| Shutoff | Shutdown/Saved/FromSnapshot | Succeeded |
| Shutoff | Migrated | 保持当前 Phase |
3.6.2 Conditions 更新
Conditions 类型清单:
| Condition Type | 含义 | Status |
|---|---|---|
VirtualMachineInstanceIsMigratable | VMI 是否可迁移 | True/False |
VirtualMachineInstanceIsStorageLiveMigratable | VMI 是否可存储实时迁移 | True/False |
VirtualMachineInstanceAgentConnected | QEMU Guest Agent 连接 | True/False |
VirtualMachineInstanceUnsupportedAgent | GA 版本不兼容 | True |
VirtualMachineInstancePaused | VMI 被暂停 | True |
VirtualMachineInstanceAccessCredentialsSynchronized | 访问凭证同步 | True/False |
3.6.3 完整状态上报流程
3.7 非 root 模式适配
非 root 模式是 KubeVirt 的安全增强特性,允许 virt-launcher Pod 以非 root 用户运行。virt-handler 需要在启动流程中为非 root 场景做额外的设备权限适配。
非 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 图索引
| 序号 | 图名 | 类型 | 位置 |
|---|---|---|---|
| 1 | KubeVirt 架构位置图 | graph TB | 一、1.2 |
| 2 | 启动流程图 | flowchart TD | 二、2.2 |
| 3 | VirtualMachineController 类图 | classDiagram | 二、2.4 |
| 4 | 依赖注入图 | graph LR | 二、2.5 |
| 5 | Informer 注册图 | flowchart LR | 三、3.1.1 |
| 6 | 控制循环主框架图 | flowchart TD | 三、3.1.2 |
| 7 | execute() 决策核心图 | flowchart TD | 三、3.1.3 |
| 8 | sync() 调和逻辑图 | flowchart TD | 三、3.1.4 |
| 9 | VMI 创建全链路时序图 | sequenceDiagram | 三、3.2 |
| 10 | Domain 同步流程图 | flowchart TD | 三、3.3 |
| 11 | Ghost Record 机制图 | flowchart TD | 三、3.4 |
| 12 | Finalizer/清理流程图 | flowchart TD | 三、3.5 |
| 13 | Phase 迁移状态图 | stateDiagram-v2 | 三、3.6.1 |
| 14 | Conditions 更新图 | flowchart TD | 三、3.6.2 |
| 15 | 完整状态上报流程图 | flowchart TD | 三、3.6.3 |
| 16 | 非 root 模式适配图 | flowchart TD | 三、3.7 |
| 17 | Domain 状态对比图 | 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 模式等关键主题。
更多推荐
所有评论(0)