ᕕ( ᐛ )ᕗ Jimyag's Blog

虚拟机热迁移实现原理

虚拟机热迁移要解决的问题是:在业务仍然运行的情况下,把一个 VM 从源节点移动到目标节点,并尽量把不可服务时间压到很短。这个能力常用于节点维护、负载均衡、硬件故障规避和集群升级。

如果只看上层命令,热迁移像是一次 virtctl migrate 或一次平台侧调度动作。但真正的迁移链路是分层协作:

  1. QEMU 负责保存和恢复虚拟机运行状态,包括内存、CPU 和设备状态。
  2. Libvirt 负责封装 QEMU 迁移接口,协调源端与目标端的迁移生命周期。
  3. KubeVirt 把这套能力放进 Kubernetes,用 CRD、Pod 和控制器管理迁移。
  4. Kube-OVN 这类网络插件在网络层补齐 Pod 重建带来的 IP、MAC 和连接连续性问题。

这篇文章按这个顺序拆开看。

QEMU 负责迁移什么

QEMU 文档把迁移描述为「把一个正在运行的 guest 从一台机器移动到另一台机器」。

它依赖 QEMU 已经具备的 save/load 能力:源端保存 guest 的设备状态,目标端再按相同配置加载这些状态。

对于 live migration,QEMU 允许 guest 在大部分状态传输期间继续运行,只在最后一小段状态传输时暂停 guest。这个框架在 QEMU Migration framework 里有明确说明。

可以把 QEMU 这一层理解成三类状态的搬迁:

  1. CPU 状态,例如寄存器、执行位置、中断相关状态。
  2. 内存状态,也就是 guest RAM。
  3. 设备状态,例如 virtio 设备、定时器、磁盘控制器和网卡设备模型中的状态。

其中最难的是内存。因为 VM 在迁移时还在运行,guest 会持续写内存,已经发到目标端的页可能又被源端改掉,这些被修改的页就是迁移里常说的 dirty page。

Pre-copy 与 Post-copy

QEMU/KVM 体系里常见的内存迁移策略是 pre-copy 和 post-copy。KubeVirt 的用户文档也用这三类策略解释迁移行为:pre-copy、post-copy 和 auto-converge。

Pre-copy 的重点是「先复制,后切换」。源端 VM 在大部分迁移时间里继续运行,QEMU 多轮追赶新产生的脏页,最后再短暂停机完成切换。

  graph TD
    A["启动迁移"] --> B["目标端创建 VM"]
    B --> C["源端继续运行"]
    C --> D["第一轮传输内存页"]
    D --> E["多轮传输新产生的脏页"]
    E --> F{"剩余脏页是否足够少"}
    F -- "否" --> E
    F -- "是" --> G["暂停源端 VM"]
    G --> H["传输最后脏页和 CPU/设备状态"]
    H --> I["目标端 VM 继续运行"]
    I --> J["清理源端 VM"]

Pre-copy 是默认策略,也更适合多数生产场景。它先把内存页从源端复制到目标端;复制期间源端 VM 继续运行,所以还会产生新的脏页。

QEMU 会多轮复制这些脏页,直到剩余状态足够少,再短暂停止源端 VM,复制最后一批状态,然后让目标端继续运行。

QEMU 文档提到 live migration 期间 guest 只需要在最后一段状态传输时停止,通常不可响应时间会依赖很多条件;KubeVirt 文档也说明 pre-copy 是默认策略,适合大多数场景。

Pre-copy 的优点是失败处理相对简单:在切换前,源端仍然保有完整 VM 状态,迁移可以取消或回退。

缺点是它依赖迁移收敛。如果 guest 的 dirty rate 很高,内存被改写的速度超过网络复制速度,迁移就会长时间追不上。

Post-copy 的重点是「先切换,后补页」。目标端 VM 会在内存尚未全部迁完时先运行,访问缺失页面时再从源端按需拉取。

  graph TD
    A["启动迁移"] --> B["目标端创建 VM"]
    B --> C["传输必要 CPU/设备状态和少量内存"]
    C --> D["目标端 VM 先运行"]
    D --> E["后台继续迁移剩余内存页"]
    D --> F{"访问页面是否已在目标端"}
    F -- "是" --> G["直接访问目标端内存"]
    F -- "否" --> H["触发缺页"]
    H --> I["目标端向源端请求该页"]
    I --> J["源端发送缺失页面"]
    J --> K["目标端恢复访问"]
    E --> L["所有内存页迁移完成"]
    K --> L
    L --> M["清理源端 VM"]

Post-copy 是为高 dirty rate 场景准备的策略。

QEMU 的 Postcopy 文档 说明,post-copy 会在所有内存传输完成前启动目标端 CPU;如果目标端访问了尚未迁移的页面,QEMU 会把这个 fault 转换成向源端请求页面。

这样做的好处是迁移流量和耗时有上界,同一个内存页通常不需要因为反复变脏而重复传输。

代价是风险更高:post-copy 阶段 VM 状态分布在源端和目标端,一侧失败都可能导致 guest 丢失。

Auto-converge 不是第三种完整迁移算法,而是让 pre-copy 更容易收敛的手段。KubeVirt 文档解释为:当 high dirty rate 让迁移难以收敛时,auto-converge 通过 throttle guest CPU 来降低内存改写速度,从而提高迁移完成概率。

Libvirt 做了什么

QEMU 提供底层迁移能力,但上层系统通常不会直接拼 QMP 命令管理整个生命周期。Libvirt 的作用是把这些能力封装成稳定 API,并负责源端、目标端和 QEMU 之间的协调。

典型流程可以抽象成这样:

  1. 上层组件调用 Libvirt migration API 发起迁移。
  2. 源端和目标端 Libvirt 协商迁移参数、目标 URI、TLS、磁盘迁移等配置。
  3. 目标端按源端 VM 配置准备 incoming QEMU。
  4. Libvirt 驱动 QEMU 执行 pre-copy 或 post-copy。
  5. 迁移完成后,目标端 VM 接管运行,源端 VM 被停止或清理。

Libvirt 的迁移 flags 直接反映了这些能力边界。例如 Libvirt domain API 中定义了:

参数 作用
VIR_MIGRATE_NON_SHARED_DISK 迁移完整磁盘镜像以及内存,适用于非共享磁盘场景。
VIR_MIGRATE_COMPRESSED 压缩迁移数据,减少网络传输量。
VIR_MIGRATE_AUTO_CONVERGE 启用让 live migration 最终收敛的算法,通常会降低 guest 运行速度,避免内存变化快于传输速度。
VIR_MIGRATE_POSTCOPY 允许 post-copy,但迁移仍会先按普通模式启动,需要再切到 post-copy。

这也是为什么讨论热迁移时不能只看“是否支持迁移”。更关键的问题是:是否共享存储、是否允许 post-copy、是否能接受 CPU throttle、是否能接受压缩带来的 CPU 开销,以及网络带宽是否足够。

KubeVirt 如何把迁移放进 Kubernetes

KubeVirt 没有重写 QEMU/Libvirt 的迁移算法。它做的是把传统虚拟化迁移放进 Kubernetes 的对象模型和 Pod 生命周期里。

在 KubeVirt 中,每个 VMI 运行在一个 virt-launcher Pod 中。热迁移发生时,KubeVirt 会在目标节点创建新的 virt-launcher Pod,并把 VM 状态从源 Pod 迁到目标 Pod。

用户可以通过创建 VirtualMachineInstanceMigration 对象触发迁移,也可以用 virtctl migrate 发起迁移;KubeVirt 官方的 Live Migration 文档 明确说明了这两种入口。

  graph TD
    A["virtctl migrate / VMIM"] --> B["KubeVirt controller 观察迁移对象"]
    B --> C["目标节点创建 virt-launcher Pod"]
    C --> D["源端 virt-launcher / Libvirt 发起迁移"]
    D --> E["迁移内存、设备状态,必要时迁移磁盘块"]
    E --> F["VMI.status 更新迁移状态"]
    F --> G["迁移完成后清理源端 Pod"]

KubeVirt 这一层还有几个重要约束。

首先是存储。官方文档写得很直接:使用 PVC 的 VM 默认要求 PVC 具备 ReadWriteMany 访问模式才能 live migrate;如果不是共享存储,就可能需要 block migration,也就是把磁盘块也迁过去。

文档里的 Migration Method 也区分了 LiveMigrationBlockMigration:前者表示只复制实例内存,后者表示某些 VMI 磁盘也需要从源端复制到目标端。

其次是网络。KubeVirt 文档列出的限制包括:pod network binding 使用 bridge interface type 时不允许 live migration;迁移还要求 virt-launcher Pod 里的 4915249153 端口可用;源端和目标端 Pod 的 primary network interface 名称也要一致。

再次是集群级保护。KubeVirt 默认会限制迁移并发和带宽,避免迁移把集群资源打满。官方文档给出的默认值包括:

配置 默认值 含义
parallelMigrationsPerCluster 5 集群内同时迁移数量上限。
parallelOutboundMigrationsPerNode 2 单节点同时作为源端迁出的数量上限。
bandwidthPerMigration 64Mi 单个迁移的带宽限制。
allowAutoConverge false 是否允许自动收敛。
allowPostCopy false 是否允许 post-copy。

这些默认值说明了 KubeVirt 对热迁移的定位:它不是越快越好,而是要在迁移完成率、业务扰动和集群稳定性之间做约束。

如果不同 VM 对迁移的要求差异很大,不应该只靠全局配置硬扛。

KubeVirt 还提供了 Migration Policies,可以按 namespace label 或 VMI label 匹配一组 VM,分别覆盖带宽、auto-converge、post-copy、超时等迁移配置。

官方文档也提醒这个 API 是 v1alpha1,意味着它适合做精细化控制,但使用前要确认当前集群版本的 API 稳定性。

一个典型用法是:普通业务 VM 走全局默认策略;内存写入很频繁的 VM 单独匹配一条 policy,允许 auto-converge;对可用性要求高但能接受 post-copy 风险的少量 VM,再单独开启 post-copy。

迁移网络可以独立配置

源稿里提到了迁移流量可以走 Host 网络或 CNI 网络。

更准确地说,KubeVirt 官方文档提供的是「使用不同网络进行迁移」的能力:管理员可以创建一个专用的 NetworkAttachmentDefinition,再在 KubeVirt CR 的 spec.configuration.migrations.network 指向这个网络。

这样迁移流量就可以走独立网络,而不是和 Kubernetes 默认网络混在一起。

1
2
3
4
5
6
7
8
9
apiVersion: kubevirt.io/v1
kind: Kubevirt
metadata:
  name: kubevirt
  namespace: kubevirt
spec:
  configuration:
    migrations:
      network: migration-network

这个能力适合几个场景:

  1. 迁移流量很大,希望隔离业务网络和迁移网络。
  2. 节点有独立迁移网卡,希望把迁移带宽和延迟做得更可控。
  3. 集群安全策略不希望迁移流量直接暴露在默认通道上。

需要注意的是,独立迁移网络不是免费的能力。

KubeVirt 文档要求每个节点至少有第二块网卡,并且这些网卡所在网络彼此互通;如果没有 DHCP,还需要类似 whereabouts 这样的 IPAM 能力。

也就是说,它适合规划过网络拓扑的生产集群,而不是一个只改开关就能解决所有问题的通用优化。

迁移前应该检查什么

热迁移的入口很简单,但迁移失败通常不是入口问题,而是 VMI 在启动时就已经不满足迁移条件。

KubeVirt 会在 VMI status 中计算是否可迁移;官方文档也说明,迁移请求会拒绝 non-LiveMigratable 的 VMI。

实际排查时,可以先看这几类信息:

1
2
3
4
5
6
7
8
# 看 VMI 条件,重点关注 LiveMigratable
kubectl get vmi <vmi-name> -n <namespace> -o jsonpath='{range .status.conditions[*]}{.type}{"="}{.status}{" "}{.reason}{"\n"}{end}'

# 看迁移方法,LiveMigration 表示主要迁移内存,BlockMigration 表示还需要复制磁盘块
kubectl get vmi <vmi-name> -n <namespace> -o jsonpath='{.status.migrationMethod}{"\n"}'

# 看完整状态和事件
kubectl describe vmi <vmi-name> -n <namespace>

如果 LiveMigratable=False,优先检查下面几项:

检查项 关注点
PVC access mode 使用 PVC 的 VM 是否满足 ReadWriteMany,或者当前场景是否允许 block migration。
网络 binding 是否使用了 KubeVirt 默认不允许迁移的 pod bridge binding,或者是否有 Kube-OVN/OVN-Kubernetes 这类插件补齐网络迁移能力。
迁移端口 virt-launcher Pod 中 4915249153 是否被其他用途占用。
节点网络 源节点与目标节点的迁移网络是否互通,独立迁移网络是否在所有节点都可用。
设备模型 是否挂载了当前 QEMU/KubeVirt 组合不能迁移的设备,例如某些 passthrough 设备。

触发迁移后,可以用 VMIM 和 VMI 状态跟踪过程:

1
2
3
4
5
6
7
8
virtctl migrate <vmi-name> -n <namespace>

kubectl get vmim -n <namespace>
kubectl describe vmim <migration-name> -n <namespace>
kubectl describe vmi <vmi-name> -n <namespace>

# 需要取消时
virtctl migrate-cancel <vmi-name> -n <namespace>

这比只看 virtctl migrate 的返回值更可靠。virtctl migrate 成功通常只代表迁移对象提交成功,不代表目标 Pod 调度、Libvirt 迁移、网络切换都已经成功。

迁移策略怎么选

可以把策略选择先简化成两个问题:

  1. VM 的 dirty rate 会不会高到 pre-copy 难以收敛?
  2. 业务能不能接受迁移策略带来的额外扰动或风险?

对应到 KubeVirt 配置,可以这样取舍:

场景 优先策略 原因
常规业务 VM,内存写入不激烈 Pre-copy 默认策略,失败时更容易取消或回退。
迁移偶尔不收敛,但业务能接受短时间 CPU 降速 Pre-copy + auto-converge 通过 throttle guest CPU 降低 dirty rate,换取迁移完成率。
高 dirty rate,维护窗口有限,且可以接受 post-copy 风险 Post-copy 目标端先运行,迁移更容易完成;但 post-copy 阶段任一侧失败都有更高风险。
大量 VM 同时维护迁移 限制并发和带宽 避免迁移流量、目标节点资源和存储 I/O 被打满。
固定 IP / 长连接敏感 VM 网络插件配合迁移优化 只迁移内存和磁盘不够,还要保证 IP、MAC 和连接路径连续。

KubeVirt 的 allowWorkloadDisruption 也要谨慎理解。

官方文档说明,当迁移超过可接受完成时间时,如果允许 workload disruption,控制器可以优先让迁移完成:允许 post-copy 时会切到 post-copy;不允许 post-copy 时则可能暂停对应 VMI。

它不是一个“无损加速”开关,而是在完成迁移和避免业务扰动之间做选择。

因此,生产环境更稳妥的做法是:

  1. 全局配置保持保守,控制并发、带宽和超时。
  2. MigrationPolicy 对特殊 VM 做 label 级覆盖。
  3. 先在同类业务上压测 dirty rate、迁移耗时和网络抖动,再扩大范围。
  4. 对数据库、消息队列这类状态服务,把应用层 HA 和备份恢复方案作为兜底,而不是完全依赖热迁移。

Kube-OVN 解决的是网络连续性

到这里为止,QEMU/Libvirt/KubeVirt 主要解决的是 VM 状态迁移和迁移生命周期。

网络还有一个独立问题:VM 被包装在 Pod 里,迁移时源 Pod 和目标 Pod 会发生切换。

如果网络插件不能保留或平滑切换 IP、MAC 和流量路径,业务连接就可能中断。

Kube-OVN 针对 KubeVirt live migration 做了专门处理。

它的 Live Migration 文档 先列出了 KubeVirt 在网络迁移上的几个挑战。

默认情况下,KubeVirt 不支持 bridge 网络模式 VM 的 live migration;KubeVirt 主要处理内存和磁盘迁移,没有针对网络迁移做特定优化。

如果 VM IP 改变或网络中断,就无法做到 seamless live migration。

Kube-OVN 的做法是让迁移过程中的源 Pod 和目标 Pod 在一段时间内共享网络身份,并通过 OVS 控制流量。

  graph LR
    A["KubeVirt 创建目标 Pod"] --> B["Kube-OVN 识别迁移目标 Pod"]
    B --> C["目标 Pod 复用源端口网络信息"]
    C --> D["配置流量复制"]
    D --> E["目标端口临时禁用,避免流量混乱"]
    E --> F["KubeVirt 同步 VM 内存"]
    F --> G["源端 VM 停止处理流量"]
    G --> H["目标端 VM 激活"]
    H --> I["Libvirt 发送 RARP"]
    I --> J["清理源 Pod 和复制规则"]

这里的关键不是简单地让两个 Pod 拿到同一个 IP/MAC。真正避免冲突的是数据面时序和端口状态控制:

  1. 预热阶段,源端仍然处理业务流量。
  2. 目标端复用源端网络信息,但端口处于临时禁用状态,不对外主动发包。
  3. OVS 复制或引导流量,为最后切换缩短网络收敛时间。
  4. 切换阶段,源端停止处理流量,目标端激活。
  5. Libvirt 发送 RARP,帮助网络设备刷新二层可达性。

Kube-OVN 文档给出的测试结论是:网络中断时间可以控制在 0.5 秒内,TCP 连接因为重试机制不会中断。

这个说法需要按层次理解:

层次 现象
二层/数据面 切换源端口、目标端口和刷新路径时,仍可能有短暂抖动。
UDP/ICMP 可能观察到少量瞬时丢包。
TCP/SSH 只要抖动小于 TCP 超时阈值,连接通常不会被重置;VM 内核里的连接状态也随内存状态迁到目标端。

开启方式也在 Kube-OVN 文档中给出:在 VM 模板上增加注解。

1
2
3
metadata:
  annotations:
    kubevirt.io/allow-pod-bridge-network-live-migration: "true"

这个能力适合固定 IP、长连接敏感、不能接受秒级断连的 VM,例如数据库、中间件和状态服务。

但它仍然依赖具体 Kube-OVN 版本、KubeVirt 版本、网络拓扑和业务协议特征,不能简单等同于所有协议、所有负载都绝对零丢包。

迁移中看哪些指标

KubeVirt 官方 metrics 文档列出了多组迁移指标,适合把“迁移很慢”拆成几个可观测问题。

指标 用途
kubevirt_vmi_dirty_rate_bytes_per_second 观察 guest 当前 dirty rate。
kubevirt_vmi_migration_dirty_memory_rate_bytes 观察迁移期间内存变脏速度。
kubevirt_vmi_migration_memory_transfer_rate_bytes 观察内存迁移传输速度。
kubevirt_vmi_migration_data_remaining_bytes 观察剩余待迁移数据是否收敛。
kubevirt_vmi_migration_failed / kubevirt_vmi_migration_succeeded 判断迁移结果。
kubevirt_vmi_migrations_in_running_phase 观察当前运行中的迁移数量。
kubevirt_vmi_non_evictable 发现设置了 live migration eviction strategy 但不可迁移的 VM。

这些指标可以帮助判断根因:

  1. data_remaining 长时间不下降,同时 dirty rate 接近或超过 transfer rate,通常是 pre-copy 不收敛。
  2. transfer_rate 很低,要看迁移网络、TLS、压缩、目标节点 CPU 和存储 I/O。
  3. running phase 迁移数量过多,要先收紧并发,而不是继续调高单个迁移带宽。
  4. non_evictable 出现时,节点维护前就应该处理 VM 的存储、网络或设备限制,否则 drain 时会卡住。

对于网络连续性,也不要只看迁移是否 Completed。更接近业务体验的验证方式是,在迁移期间同时观察:

1
2
3
4
5
6
7
8
9
# VM 内或外部客户端持续看丢包和 RTT
ping <vm-ip>

# 长连接类业务可以保持 SSH、数据库连接或应用连接
ssh <vm-ip>

# 集群侧观察迁移对象和事件
kubectl get vmim -A
kubectl get events -n <namespace> --sort-by=.lastTimestamp

如果迁移完成但业务连接断了,问题多半不在 QEMU 状态迁移本身。

更常见的位置是网络 binding、CNI 数据面切换、RARP/ARP 收敛或上层协议超时设置。

分层看待风险

热迁移不是单点能力,而是一组条件同时满足后的结果。排查或设计时,可以按层看风险。

层次 主要风险 常见缓解方式
QEMU 设备状态不可迁移、dirty rate 过高、post-copy 失败风险 控制设备模型,评估 dirty rate,谨慎开启 post-copy。
Libvirt 参数选择不当、非共享磁盘迁移成本高、TLS/URI 配置错误 明确迁移 flags,区分共享存储和块迁移。
KubeVirt PVC 不满足 RWX、bridge 网络限制、迁移并发过高 使用 LiveMigratable 条件、配置并发和带宽上限、规划迁移网络。
CNI/Kube-OVN IP/MAC 切换、二层收敛、连接抖动 使用支持 KubeVirt 迁移优化的网络插件,验证 RARP/流表切换行为。

所以,一个 VM 能不能热迁移,不能只看 virtctl migrate 是否能提交成功。更可靠的检查顺序是:

  1. VMI 是否被标记为 LiveMigratable
  2. 存储是共享存储迁移,还是需要 block migration。
  3. 网络 binding 是否在当前 KubeVirt/CNI 组合下支持迁移。
  4. 业务 dirty rate 是否会导致 pre-copy 不收敛。
  5. 迁移并发、带宽、超时和 post-copy/auto-converge 策略是否符合业务风险偏好。

小结

虚拟机热迁移的核心不是「把一个进程搬到另一台机器」,而是多层状态的一致切换。

QEMU 负责保存和恢复 VM 的 CPU、内存、设备状态;Libvirt 把 QEMU 的迁移能力封装成可管理的生命周期。

KubeVirt 用 Kubernetes CRD、Pod 和 controller 把迁移放进集群调度与状态机;Kube-OVN 则补上 Pod 网络切换时的 IP、MAC 和连接连续性问题。

这几层分别解决不同问题,也分别引入不同约束。

理解这些边界后,再看热迁移失败、迁移不收敛、网络短断、TCP 连接是否会断,就不容易把所有问题都归因到一个「热迁移支持不好」上。

#Virtualization #KubeVirt #QEMU #Libvirt #Kube-OVN