虚拟机热迁移实现原理
虚拟机热迁移要解决的问题是:在业务仍然运行的情况下,把一个 VM 从源节点移动到目标节点,并尽量把不可服务时间压到很短。这个能力常用于节点维护、负载均衡、硬件故障规避和集群升级。
如果只看上层命令,热迁移像是一次 virtctl migrate 或一次平台侧调度动作。但真正的迁移链路是分层协作:
- QEMU 负责保存和恢复虚拟机运行状态,包括内存、CPU 和设备状态。
- Libvirt 负责封装 QEMU 迁移接口,协调源端与目标端的迁移生命周期。
- KubeVirt 把这套能力放进 Kubernetes,用 CRD、Pod 和控制器管理迁移。
- Kube-OVN 这类网络插件在网络层补齐 Pod 重建带来的 IP、MAC 和连接连续性问题。
这篇文章按这个顺序拆开看。
QEMU 负责迁移什么
QEMU 文档把迁移描述为「把一个正在运行的 guest 从一台机器移动到另一台机器」。
它依赖 QEMU 已经具备的 save/load 能力:源端保存 guest 的设备状态,目标端再按相同配置加载这些状态。
对于 live migration,QEMU 允许 guest 在大部分状态传输期间继续运行,只在最后一小段状态传输时暂停 guest。这个框架在 QEMU Migration framework 里有明确说明。
可以把 QEMU 这一层理解成三类状态的搬迁:
- CPU 状态,例如寄存器、执行位置、中断相关状态。
- 内存状态,也就是 guest RAM。
- 设备状态,例如 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 之间的协调。
典型流程可以抽象成这样:
- 上层组件调用 Libvirt migration API 发起迁移。
- 源端和目标端 Libvirt 协商迁移参数、目标 URI、TLS、磁盘迁移等配置。
- 目标端按源端 VM 配置准备 incoming QEMU。
- Libvirt 驱动 QEMU 执行 pre-copy 或 post-copy。
- 迁移完成后,目标端 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 也区分了 LiveMigration 和 BlockMigration:前者表示只复制实例内存,后者表示某些 VMI 磁盘也需要从源端复制到目标端。
其次是网络。KubeVirt 文档列出的限制包括:pod network binding 使用 bridge interface type 时不允许 live migration;迁移还要求 virt-launcher Pod 里的 49152、49153 端口可用;源端和目标端 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 默认网络混在一起。
|
|
这个能力适合几个场景:
- 迁移流量很大,希望隔离业务网络和迁移网络。
- 节点有独立迁移网卡,希望把迁移带宽和延迟做得更可控。
- 集群安全策略不希望迁移流量直接暴露在默认通道上。
需要注意的是,独立迁移网络不是免费的能力。
KubeVirt 文档要求每个节点至少有第二块网卡,并且这些网卡所在网络彼此互通;如果没有 DHCP,还需要类似 whereabouts 这样的 IPAM 能力。
也就是说,它适合规划过网络拓扑的生产集群,而不是一个只改开关就能解决所有问题的通用优化。
迁移前应该检查什么
热迁移的入口很简单,但迁移失败通常不是入口问题,而是 VMI 在启动时就已经不满足迁移条件。
KubeVirt 会在 VMI status 中计算是否可迁移;官方文档也说明,迁移请求会拒绝 non-LiveMigratable 的 VMI。
实际排查时,可以先看这几类信息:
|
|
如果 LiveMigratable=False,优先检查下面几项:
| 检查项 | 关注点 |
|---|---|
| PVC access mode | 使用 PVC 的 VM 是否满足 ReadWriteMany,或者当前场景是否允许 block migration。 |
| 网络 binding | 是否使用了 KubeVirt 默认不允许迁移的 pod bridge binding,或者是否有 Kube-OVN/OVN-Kubernetes 这类插件补齐网络迁移能力。 |
| 迁移端口 | virt-launcher Pod 中 49152、49153 是否被其他用途占用。 |
| 节点网络 | 源节点与目标节点的迁移网络是否互通,独立迁移网络是否在所有节点都可用。 |
| 设备模型 | 是否挂载了当前 QEMU/KubeVirt 组合不能迁移的设备,例如某些 passthrough 设备。 |
触发迁移后,可以用 VMIM 和 VMI 状态跟踪过程:
|
|
这比只看 virtctl migrate 的返回值更可靠。virtctl migrate 成功通常只代表迁移对象提交成功,不代表目标 Pod 调度、Libvirt 迁移、网络切换都已经成功。
迁移策略怎么选
可以把策略选择先简化成两个问题:
- VM 的 dirty rate 会不会高到 pre-copy 难以收敛?
- 业务能不能接受迁移策略带来的额外扰动或风险?
对应到 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。
它不是一个“无损加速”开关,而是在完成迁移和避免业务扰动之间做选择。
因此,生产环境更稳妥的做法是:
- 全局配置保持保守,控制并发、带宽和超时。
- 用
MigrationPolicy对特殊 VM 做 label 级覆盖。 - 先在同类业务上压测 dirty rate、迁移耗时和网络抖动,再扩大范围。
- 对数据库、消息队列这类状态服务,把应用层 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。真正避免冲突的是数据面时序和端口状态控制:
- 预热阶段,源端仍然处理业务流量。
- 目标端复用源端网络信息,但端口处于临时禁用状态,不对外主动发包。
- OVS 复制或引导流量,为最后切换缩短网络收敛时间。
- 切换阶段,源端停止处理流量,目标端激活。
- Libvirt 发送 RARP,帮助网络设备刷新二层可达性。
Kube-OVN 文档给出的测试结论是:网络中断时间可以控制在 0.5 秒内,TCP 连接因为重试机制不会中断。
这个说法需要按层次理解:
| 层次 | 现象 |
|---|---|
| 二层/数据面 | 切换源端口、目标端口和刷新路径时,仍可能有短暂抖动。 |
| UDP/ICMP | 可能观察到少量瞬时丢包。 |
| TCP/SSH | 只要抖动小于 TCP 超时阈值,连接通常不会被重置;VM 内核里的连接状态也随内存状态迁到目标端。 |
开启方式也在 Kube-OVN 文档中给出:在 VM 模板上增加注解。
|
|
这个能力适合固定 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。 |
这些指标可以帮助判断根因:
data_remaining长时间不下降,同时 dirty rate 接近或超过 transfer rate,通常是 pre-copy 不收敛。transfer_rate很低,要看迁移网络、TLS、压缩、目标节点 CPU 和存储 I/O。- running phase 迁移数量过多,要先收紧并发,而不是继续调高单个迁移带宽。
non_evictable出现时,节点维护前就应该处理 VM 的存储、网络或设备限制,否则 drain 时会卡住。
对于网络连续性,也不要只看迁移是否 Completed。更接近业务体验的验证方式是,在迁移期间同时观察:
|
|
如果迁移完成但业务连接断了,问题多半不在 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 是否能提交成功。更可靠的检查顺序是:
- VMI 是否被标记为
LiveMigratable。 - 存储是共享存储迁移,还是需要 block migration。
- 网络 binding 是否在当前 KubeVirt/CNI 组合下支持迁移。
- 业务 dirty rate 是否会导致 pre-copy 不收敛。
- 迁移并发、带宽、超时和 post-copy/auto-converge 策略是否符合业务风险偏好。
小结
虚拟机热迁移的核心不是「把一个进程搬到另一台机器」,而是多层状态的一致切换。
QEMU 负责保存和恢复 VM 的 CPU、内存、设备状态;Libvirt 把 QEMU 的迁移能力封装成可管理的生命周期。
KubeVirt 用 Kubernetes CRD、Pod 和 controller 把迁移放进集群调度与状态机;Kube-OVN 则补上 Pod 网络切换时的 IP、MAC 和连接连续性问题。
这几层分别解决不同问题,也分别引入不同约束。
理解这些边界后,再看热迁移失败、迁移不收敛、网络短断、TCP 连接是否会断,就不容易把所有问题都归因到一个「热迁移支持不好」上。