本文根据一次 Libvirt CPU 模型讨论和官方文档整理。示例用于说明设计方法,尚未在客户环境完成跨 CPU 热迁移实测。

我之前认为,只要把虚拟机 CPU 配置成 host-model,Libvirt 就能屏蔽宿主机 CPU 差异,从而解决 CPU 异构问题。

这个理解只对了一半:host-model 确实比 host-passthrough 更容易迁移,但它优化的是虚拟机在当前宿主机启动时所看到的 CPU,并不会自动计算整个集群都支持的公共 CPU 基线。

如果客户真正需要的是在不同代 CPU 之间进行双向热迁移,核心问题不是“如何尽量接近当前宿主机”,而是“如何让虚拟机从启动开始就只使用所有目标宿主机都能提供的 CPU ABI”。

先区分三种需求

“支持 CPU 异构”可能指三件不同的事:

需求 典型场景 host-model 是否合适
异构节点都能创建虚拟机 虚拟机关机后可以调度到另一代 CPU 通常可以,但重启后 Guest 看到的 CPU 可能变化
单向热迁移 从旧 CPU 迁移到能力更强的新 CPU 可能可行,必须预检查和实测
双向跨代热迁移 新旧 CPU 节点之间可以来回迁移 不能只依赖 host-model,应设计公共 CPU 基线

客户说“跨 CPU”,还需要继续明确边界:

  • 是同厂商不同代,例如 Intel Broadwell 与 Skylake;
  • 还是 Intel 与 AMD 跨厂商;
  • 只要求旧节点迁往新节点,还是必须双向迁移;
  • 是热迁移,还是允许关机后冷迁移;
  • 迁移池中具体包含哪些宿主机,而不是泛指整个集群。

这些答案会直接改变 CPU 模型设计。

Libvirt 的四种 CPU 模式

CPU 模式 Guest 看到的 CPU 性能与特性 迁移边界
host-passthrough 尽可能等同宿主机 最接近物理 CPU 依赖硬件、QEMU、微码和配置高度一致
host-model 启动时选择最接近当前宿主机的命名模型,再补充特性 接近宿主机 适合 CPU 基本同构的迁移池,不保证跨代双向迁移
custom 显式指定命名模型和 CPU Features 会舍弃部分新 CPU 特性 可固定 Guest CPU ABI,适合跨代迁移池
maximum 暴露 Hypervisor 能提供的最大特性集 特性最多 不适合追求迁移兼容性的 KVM Guest

host-passthrough 的目标是“像宿主机”,host-model 的目标是“尽量接近宿主机且更可迁移”,而 custom 的目标是“无论落在哪台宿主机,都向 Guest 提供相同的 CPU 契约”。

这三者解决的不是同一个问题。

host-model 实际做了什么

下面的 XML 看起来没有指定具体 CPU:

1
<cpu mode='host-model'/>

虚拟机启动前,Libvirt 会根据当前宿主机的 Domain Capabilities,选择一个最接近宿主机的 QEMU 命名 CPU 模型,并补充必要的 CPU Features。

假设两台宿主机的能力如下:

1
2
Host A:支持 f1、f2、f3、f4
Host B:支持 f1、f2、f3

虚拟机在 Host A 启动后,可能看到:

1
Guest CPU:f1、f2、f3、f4

热迁移时,Libvirt 会把这台运行中虚拟机的完整 CPU 定义传给 Host B。为了保证 Guest 在迁移前后看到相同硬件,Host B 也必须提供 f4。如果它做不到,迁移预检查或恢复阶段就会失败。

反过来,虚拟机如果最初在 Host B 启动,只暴露 f1、f2、f3,迁移到能力更强的 Host A 往往更容易成功。因此 host-model 的迁移兼容性可能具有方向性:

1
2
旧 CPU → 新 CPU:可能成功
新 CPU → 旧 CPU:可能失败

这里的“可能”很重要。CPU 不是热迁移的唯一约束,QEMU 版本、Machine Type、内核、微码、设备模型、存储和网络也要兼容。

最容易混淆的地方

同一份 host-model XML,不等于同一个 Guest CPU

相同的持久化 XML:

1
<cpu mode='host-model'/>

在不同宿主机冷启动时,可能展开成不同的有效 CPU。也就是说,它保证的是“在当前节点尽可能合理”,而不是“整个迁移池的 CPU ABI 永远一致”。

运行中的虚拟机热迁移后,Guest 仍会保持迁移前的 CPU 定义;但关机并在目标宿主机重新启动后,Libvirt 会重新计算 host-model,Guest 可能看到更多或更少的 CPU Features。

migratable=’on’ 不是异构迁移开关

例如:

1
<cpu mode='host-passthrough' migratable='on'/>

migratable='on' 只会尝试移除已知会阻塞迁移的 CPU Features,并不会自动求出两台异构宿主机的公共特性集。Libvirt 官方文档明确提醒:即使开启它,host-passthrough 在宿主机不一致时仍然危险。

CPU 兼容不等于整机可热迁移

CPU 基线通过,只能说明 CPU 这一层满足条件。完整热迁移还依赖:

  • 源、目标使用兼容的 QEMU 版本和相同的版本化 Machine Type;
  • Guest 设备模型及其 Features 一致;
  • 磁盘、网络和迁移通道满足迁移方式要求;
  • NUMA、CPU Pinning、Huge Page、VFIO 等资源能在目标节点重建;
  • 目标宿主机内核、KVM、微码和安全缓解特性满足 Guest CPU 定义。

因此,不能把“CPU Compare 通过”写成“热迁移一定成功”。

跨代双向热迁移:固定公共 CPU 基线

如果迁移池中包含多代 CPU,并要求双向热迁移,更稳妥的设计是:

1
2
3
4
Host A CPU Features ─┐
├─ 求交集 ─→ Cluster CPU Baseline ─→ Guest CPU ABI
Host B CPU Features ─┤
Host C CPU Features ─┘

QEMU 的建议也是:在混合 CPU 型号的迁移池中,选择所有目标宿主机都支持的最新命名 CPU 模型

1. 固定迁移域

先列出真正需要互相热迁移的宿主机,不要把所有计算节点默认放进同一个兼容域。跨厂商 CPU 的边界更复杂,不能只看少量相同 Flags 就假设兼容;更稳妥的做法是拆分迁移域,再分别验证。

2. 收集能力并计算 Baseline

可以先在各宿主机收集能力:

1
2
virsh capabilities > host-a.xml
virsh capabilities > host-b.xml

将多个宿主机的 Capabilities 汇总到一个输入文件后,计算可迁移基线:

1
virsh cpu-baseline hosts.xml --migratable

较新的 Libvirt 还提供 hypervisor-cpu-baseline。它会结合指定 QEMU、架构和 Machine Type 的实际能力计算基线,比只比较物理 CPU 更贴近虚拟机最终能获得的 CPU。生产设计应优先使用 Domain Capabilities 和该命令,并确保所有节点使用相同参数。

3. 把结果固化为 custom CPU

不要为异构迁移池保留动态 host-model,而是将计算和验证后的基线固化到虚拟机模板。例如:

1
2
3
4
<cpu mode='custom' match='exact'>
<model fallback='forbid'>Broadwell-v4</model>
<feature policy='require' name='pcid'/>
</cpu>

这只是结构示例,Broadwell-v4pcid 不能直接套用。真实模型与 Features 必须来自迁移池的能力交集,并结合 Guest 安全、性能和业务需求评审。

fallback='forbid' 可以避免 Hypervisor 静默退回到另一个模型。代价是目标宿主机无法严格提供该 CPU 时,虚拟机会直接启动失败;这正是生产环境需要的失败方式。

4. 在每台目标宿主机做预检查

至少验证:

1
2
virsh hypervisor-cpu-compare baseline.xml \
kvm /usr/bin/qemu-system-x86_64 x86_64 <machine-type> --error

QEMU 路径和 Machine Type 以实际 Domain XML 为准。还应比较各节点的:

1
2
3
4
virsh version
qemu-system-x86_64 --version
uname -r
virsh domcapabilities kvm /usr/bin/qemu-system-x86_64 x86_64 <machine-type>

版本号相同也不是充分条件,还要保证发行版补丁、微码和关键配置的兼容策略一致。

验收矩阵

不要只做一次“迁移命令返回成功”。最小验收应覆盖:

场景 预期结果
Host A → Host B 热迁移 迁移成功,业务连续
Host B → Host A 热迁移 迁移成功,业务连续
迁移前后检查 Guest CPU Model、Flags 和 ABI 保持一致
CPU 压力下迁移 业务计算结果正确,无 Guest Crash 或 Hang
目标宿主机冷启动 Guest CPU 仍与模板定义一致
迁移失败回滚 源端虚拟机可继续运行,状态明确
宿主机升级后回归 双向迁移矩阵重新通过

迁移前后可在 Guest 中记录:

1
2
lscpu
grep -m1 '^flags' /proc/cpuinfo

如果使用了特定指令集,还应运行能实际执行这些指令的业务或测试程序。只比较 Flags,不能证明应用状态和设备状态都正确迁移。

最终结论

我原来的判断是:

host-model 可以屏蔽 CPU 差异,所以能解决 CPU 异构。

更准确的结论应该是:

host-model 能在当前宿主机上生成接近物理 CPU、相对易迁移的 Guest CPU,适合 CPU 基本同构的资源池;它不是集群级公共 CPU 基线,也不保证不同代 CPU 之间可以双向热迁移。

如果客户只是希望异构节点都能运行虚拟机,host-model 可能已经足够;如果客户要求跨代双向热迁移,应为明确的迁移域计算公共 CPU Baseline,固化为 custom CPU,并连同 Machine Type、QEMU、内核、微码和设备模型一起完成双向实测。

参考资料