jimyag's Blog

KubeVirt RWO 热迁移参数实测:Pre-copy、Auto-converge、Post-copy 与超时

allowAutoConverge: true 可以直接用于普通的 pre-copy 迁移。迁移仍从 pre-copy 开始;这个开关允许 QEMU 在难以收敛时逐步限制 Guest CPU,让脏页产生速度降下来。

本地 RWO PVC 不能直接创建普通 VirtualMachineInstanceMigration。KubeVirt 会在准入阶段拒绝:

1
2
3
Cannot migrate VMI, Reason: DisksNotLiveMigratable,
Message: cannot migrate VMI: PVC main-source is not shared,
live migration requires that all PVCs must be shared (using ReadWriteMany access mode)

RWO 场景要使用 KubeVirt 的 VolumeMigration:准备位于另一节点的目标 PVC,在 VM 上配置 updateVolumesStrategy: Migration,再修改 VM 的卷引用。控制器会创建 VMIM,同时迁移内存、运行状态和磁盘内容。

实验环境

实验运行在一台 Ubuntu 主机上的三节点 Kind 集群中:

项目
KubeVirtv1.9.0
Kubernetesv1.35.0,kindest/node:v1.35.0
Kindv0.31.0
节点1 个 control-plane,2 个 worker
虚拟化/dev/kvm,AMD KVM
数据盘2 GiB local PV,ReadWriteOnce,源、目标分别固定在两个 worker
Guest 负载768 MiB 内存持续写脏、数据盘随机写入并 fsync、HTTP 心跳
Host 内核6.8.0-139-generic

Kind 集群定义、RWO local PV/PVC 和测试 VM 使用下面三份清单。修改 VM 的 claimName 会触发 VolumeMigration。

Loading GitHub file…
Loading GitHub file…
Loading GitHub file…

KubeVirt CR 还需要允许 LiveUpdate 和 LiveMigrate:

Loading GitHub file…

完整实验文件固定在 k8sdev fcac8be。其中 setup.sh 创建集群并安装 KubeVirt,run-profile.sh 负责切换策略和源、目标 PVC,results/summary.tsv 保存汇总结果。

本文的字段范围和默认值以 KubeVirt v1.9.0 的 MigrationConfigurationMigrationPolicySpec 定义为准,行为解释对照官方 Live MigrationMigration PoliciesVolume Migration 文档。MigrationPolicy 当前仍是 v1alpha1,接口可能继续变化;experimental 下的选项不能当作稳定生产 API。

迁移模式不是单选项

KubeVirt 的迁移总是从 pre-copy 开始。completionTimeoutPerGiB 先根据 VMI 的迁移数据量计算 acceptable completion time;官方文档中的数据量包括内存和需要复制的 ephemeral disk。超过这个时间后,KubeVirt 再根据 allowPostCopyallowWorkloadDisruption 决定取消、切 post-copy,还是暂停 Guest 完成复制。

下面六个字段既能作为 KubeVirt CR 的集群默认值,也能由 MigrationPolicy 按 namespace/VMI label 覆盖。

参数v1.9.0 默认值触发条件和作用主要影响本次验证
allowAutoConvergefalsepre-copy 难以收敛时,允许 QEMU 逐步限制 Guest CPU,降低 dirty rate迁移更容易收敛,但 Guest 可用 CPU 和业务性能会下降;不保证一定成功短窗口失败,长窗口成功;未测量实际 CPU 限制比例
allowPostCopyfalseacceptable completion time 到达后,允许从 pre-copy 切换到 post-copy能迁移高 dirty-rate VM;目标运行依赖源端剩余内存页,网络或源节点故障可能导致 VM 崩溃因 userfaultfd 不可用而失败
allowWorkloadDisruptionfalse超时后不立即取消;允许 post-copy 时优先切换,否则可以暂停 Guest 完成复制提高完成概率,但可能出现可见停顿;暂停时间取决于剩余数据和带宽短窗口失败,较高带宽用例成功
bandwidthPerMigration0,不限制限制每个迁移的数据传输速率,单位是 Kubernetes Quantity/秒值低会延长迁移并占用迁移并发槽位;值高会与业务流量和其他迁移争抢网络4 MiB/s 为 124 秒,64 MiB/s 为 30 秒
completionTimeoutPerGiB150 秒/GiB按 VMI 内存和需复制 ephemeral disk 的大小计算可接受完成时间设置过短会在 auto-converge 尚未起效时取消或回退;设置过长会让不收敛迁移长期占用资源20 秒/GiB 用例在 acceptable completion time 80 秒后失败
maxDowntimeMs900 毫秒MigrationStallDetection 启用后,限制切换阶段可接受停机时间;主要用于 allowWorkloadDisruption: false 的收敛判断值越高越容易切换,但业务停顿预算也越大;超过预算仍无法切换时迁移失败v1.9.0 未开 gate 时被准入拒绝;开 gate 后遇到 Alpha 路径错误

因此“标准模式开启 allowAutoConverge”是有效组合:仍然是 pre-copy,只是允许通过 CPU throttling 帮助收敛。全部策略都在同一份清单中:

Loading GitHub file…

这组实验只证明配置进入迁移状态,并在 127 秒内完成。它没有直接测量 Guest 被限制了多少 CPU,不能把“成功”进一步解释成某个确定的降频比例。

结果

模式与超时

用例负载结果用时关键证据
标准 pre-copy内存脏页 + 磁盘 fsync成功125 秒全程 PreCopy;首次目标镜像拉取包含在总时长内
Auto-converge,短窗口同上失败约 81 秒dirty rate 最高记录到 889 Mbps;acceptable completion time 为 80 秒
Auto-converge,长窗口同上成功127 秒全程 PreCopy,策略为 21-auto-converge-long
Post-copy同上失败12 秒Userfaultfd not available: Operation not permitted
Pause,短窗口同上失败约 16 秒日志出现 Pausing the guest,但暂停后仍未在窗口内完成
Pause,较高带宽同上成功43 秒PreCopy 后进入 Paused,随后完成

汇总数据和准入、失败边界保存在固定提交中:

Loading GitHub file…
Loading GitHub file…

Post-copy 的失败不是 KubeVirt 配置没有生效。Host 上的 vm.unprivileged_userfaultfd=0,QEMU 在切换 capability 时明确返回 userfaultfd 不可用。我没有为了让结果变绿而修改 Host sysctl;生产环境启用 post-copy 前也应先验证内核、容器权限和故障恢复能力。Post-copy 已经让目标依赖源端剩余内存页,迁移网络中断可能直接造成 VM 崩溃,风险高于 pre-copy。

Pause 回退的语义更直白:允许短暂停止 Guest 写入,再复制剩余内存和磁盘数据。本次 8 MiB/s、短窗口的组合进入 Paused 后仍失败;提高到 64 MiB/s 并给出稍长窗口后成功。它可以提高完成概率,但代价就是可见停顿。

带宽、压缩和策略优先级

关闭内存脏页负载、保留磁盘 fsync 后,4 MiB/s 和 64 MiB/s 的同类往返测试分别用了 124 秒和 30 秒。这个结果只适用于本次稀疏 2 GiB local PV、Guest 数据量与节点性能,不能外推成通用的 4.1 倍性能结论;它能确认 bandwidthPerMigration 已经实际限制传输,而不是只写进 CR。

MigrationPolicy 同时匹配时,选择器更具体的策略获胜。实验中 60-specific-policy 同时匹配 namespace、profile 和 tier,70-broad-policy 只匹配 profile,最终 VMI 的 migrationPolicyName60-specific-policy

v1.9.0 的 MigrationPolicy.spec.experimental.compression 还支持 nonezstd。设置 zstd 后迁移在 19 秒内完成,virt-launcher 日志明确记录:

1
Migration compression enabled: method=zstd

这次没有做 CPU 使用量和网络字节数对照,因此只能确认压缩被启用并成功完成,不能宣称它一定更快。

并发限制

并发参数只能在 KubeVirt CR 上设置,不能由 MigrationPolicy 覆盖。两个对照 patch 分别隔离集群级上限和单节点迁出上限:

Loading GitHub file…
Loading GitHub file…

两个辅助 VM 同时变更 RWO PVC 时,parallelMigrationsPerCluster: 1、单节点上限为 3 的实验观测到 max_running=1。反向迁移时改成集群上限 3、parallelOutboundMigrationsPerNode: 1,同样只有一个 VMIM 进入 Running。两轮结束后都恢复为 3。

取消 VolumeMigration

VolumeMigration 不能照搬普通 VMIM 的取消方式。正确动作是把 VM 的卷集合精确恢复到迁移前的值。实验在 VMIM 进入 Running 后把 claimName 从目标 PVC 改回源 PVC,VMIM 进入 Failed,VM 仍使用原 PVC 并留在原节点。

失败恢复还要检查 targetNodeDomainReadyTimestamp。如果目标端 Domain 已经启动,但 VMIM 最终标为失败,KubeVirt 文档要求保留目标卷并走手工恢复,不能机械地切回源卷。

参数分层与验证边界

KubeVirt v1.9.0 的参数分成三个层次。

KubeVirt CR 与 MigrationPolicy 都支持

以下字段可以在集群级设置,也可以由策略按 namespace/VMI label 覆盖:

  • allowAutoConverge
  • allowPostCopy
  • allowWorkloadDisruption
  • bandwidthPerMigration
  • completionTimeoutPerGiB
  • maxDowntimeMs

MigrationPolicy 另外暴露两组实验参数。默认值和算法含义来自 v1.9.0 的 StallDetectorOptions 定义。

字段v1.9.0 默认值作用与影响本次验证
experimental.compression未设置none 关闭压缩,zstd 在 multifd 通道启用 Zstandard;通常以额外 CPU 开销换取更少的传输字节zstd 生效并在 19 秒内完成;未做 CPU 和网络字节对照
stallDetector.stallMargin4%将剩余字节与历史最佳值比较时的容差;容差大会更容易把当前状态判定为停滞或局部最小值进入 Alpha 路径后失败,未单独验证
stallDetector.ewmaAlpha0.4迁移带宽 EWMA 平滑因子,范围是 (0, 1];值越高,估算越看重最近样本同上
stallDetector.stallProgressTimeout40 秒跟踪最小剩余字节、判定停滞的滑动窗口同上
stallDetector.switchoverTimeout60 秒触发 stop-and-copy 或 post-copy 后允许的切换时间;超时则中止迁移同上
stallDetector.precopyPossibleFactor1.5估算停机时间可超出 maxDowntimeMs 的最大倍数,仍可尝试软 stop-and-copy;超过后中止同上
stallDetector.patienceWindowDecayFactor0.5每次放宽最佳剩余字节基线后,缩短下一个等待窗口的倍数同上
stallDetector.searchLocalMinimatrue延后收敛动作,等待剩余字节接近已观测的局部最小值;为 false 时可在检测到停滞后立即动作同上
stallDetector.completionTimeoutFactor2放大基于 completionTimeoutPerGiB 计算的时间预算,用于判断强制切换是否还来得及完成同上

本次启用 MigrationStallDetection 后,RWO block migration 在 pause 用例中返回 value of ewmaBandwidthBps not set!。这是一条 Alpha 路径上的实测失败,不应作为生产配置示例。

只能在 KubeVirt CR 设置

下面的字段只能在 KubeVirt CR 设置。表中的默认值来自 v1.9.0 源码定义;实验为了隔离变量,把并发基线改成了 3/3,并在对应用例中临时覆盖。

字段v1.9.0 默认值能做什么、何时触发影响和风险本次验证
parallelMigrationsPerCluster5限制整个集群同时处于运行阶段的迁移数太低会排队;太高会同时消耗网络、CPU、存储吞吐和目标节点资源设置为 1 后,两台 VM 同时迁移时 max_running=1
parallelOutboundMigrationsPerNode2限制单个源节点同时迁出的 VM 数避免维护或故障转移时把单节点网络、CPU 打满;也可能延长 drain 时间设置为 1 后,同一源节点只有一个迁移进入 Running
progressTimeout150 秒剩余数据连续完全没有进展达到该时间后,把迁移视为卡死并取消;只要出现进展,计时就会重置它处理的是“完全卡住”,不是 dirty rate 上下波动导致的不收敛;过短可能误杀短时网络抖动设置为 5 秒并做断流实验,但未隔离出单一计时器结果
utilityVolumesTimeout150 秒VMIM 在 Pending 阶段等待 containerDisk、cloud-init 等 utility volume detach;超时仍未释放则标记 Failed只影响迁移准备阶段,不是内存或磁盘复制超时字段进入迁移配置;未故意制造 utility volume detach 卡住
unsafeMigrationOverridefalse兼容性检查认为迁移对 Guest 不安全时,允许继续创建迁移只绕过 KubeVirt 检查,不会让不兼容 CPU、设备或存储突然具备迁移能力,可能造成 Guest 故障保持 false,只验证 API 和状态传播
disableTLSfalsetrue 会关闭 KubeVirt 迁移代理上的额外 TLS 保护v1.9.0 明确将其视为高风险配置;迁移内存和磁盘数据失去该层加密,必须依赖可信迁移网络true 时迁移成功,实验后恢复 false
network未设置,使用 Pod 网络指定迁移数据使用的 CNI 网络名称可以隔离业务和迁移流量,但所有节点必须具备一致、可达的 Multus/NAD 配置Kind 环境没有第二迁移网络,未做行为验证
matchSELinuxLevelOnMigrationfalse默认把目标 virt-launcher 的 SELinux level 固定为源端 level;设为 true 后由 CRI 为目标随机分配 level随机 level 能减少同节点 category 共享,但不自动处理 SELinux level 的 RWX 卷可能导致迁移失败Host 无可用 SELinux 环境,未做行为验证
nodeDrainTaintKeykubevirt.io/drain指定哪一个 taint key 表示节点进入 drain依赖已弃用的 node taint 功能;key 与维护流程不一致时不会按预期触发迁移API 已验证;RWO VolumeMigration 用例未覆盖节点 drain

progressTimeout 这组结果值得保守解释。设置为 5 秒后,阻断控制和数据端口会让迁移失败,但直接原因是 libvirt client socket closed;只阻断两个数据端口 20 秒,迁移恢复后仍然成功。实验没有证明“连续 5 秒没有网络字节就一定失败”,也没有否定该参数。它说明迁移栈还有 proxy、keepalive、QEMU 进度上报等状态,必须用源端 virt-launcher 日志确认真正触发者。

VMIM 自身字段

VirtualMachineInstanceMigration.spec 的完整字段定义见 v1.9.0 VirtualMachineInstanceMigrationSpec

字段能做什么限制与影响本次验证
vmiName指定要迁移的 VMIVMI 必须存在于 VMIM 所在 namespaceVolumeMigration 由控制器填写,多轮成功和失败用例均覆盖
addedNodeSelector在 VM 原有 nodeSelectornodeAffinity 上增加目标节点约束只能收紧约束;key 冲突时保留 VM 上的值,不能用它绕过 VM 的限制直接 VMIM 在调度前先被 RWO 磁盘校验拒绝,未进入节点选择
priority控制器内部区分 system-criticaluser-triggeredsystem-maintenance 迁移不是 Kubernetes PriorityClass 名称,普通用户不能直接设置提交 system-cluster-critical 被准入拒绝:only virt-controller is allowed to set priority field
sendTo把该 VMIM 标记为去中心化迁移的源端;包含 migrationID 和目标 synchronization controller 的 connectURL需要预先在目标集群建立 VM 和必要资源,两端 migrationID 必须匹配未覆盖
receive把该 VMIM 标记为去中心化迁移的目标端;提供与源端匹配的 migrationID按官方 decentralized live migration 流程配置目标 VM、同步地址和集群间 CA 信任未覆盖

怎么选

生产基线仍应从 pre-copy 开始,并为 bandwidthPerMigration、集群并发和单节点迁出并发设置明确上限。

持续高 dirty-rate 的工作负载可以先尝试 allowAutoConverge: true。它保留 pre-copy 的故障边界,但会牺牲 Guest 性能;监控应同时观察迁移时长、Guest CPU、业务延迟和 dirty rate。只把 completionTimeoutPerGiB 调得很短,auto-converge 还没来得及起作用,迁移就可能先被取消。

如果业务接受短暂停顿,可以考虑 allowWorkloadDisruption: true 的 pause-and-copy 回退。Post-copy 应当放在更严格的环境验证之后:userfaultfd、迁移网络可靠性和中断恢复缺一不可。

最后,不要只看到 VMIM Running 或目标 Pod 已创建就判定成功。本次验收以 VMIM Succeeded、VMI 节点切换、VM 卷引用切换和 Guest 心跳持续为准;失败用例还要确认 VM 回到源节点和源 PVC,或者进入文档规定的手工恢复分支。

复现实验

实验使用七个脚本。setup.sh 负责环境,run-*.sh 执行用例,monitor-vmim.shcollect-migration.sh 采集状态与证据。

Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…

按下面的顺序执行。setup.sh 使用独立 kubeconfig,不覆盖默认 context。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
git clone https://github.com/jimyag/k8sdev.git k8sdev-kubevirt-migration
cd k8sdev-kubevirt-migration
git checkout fcac8be06c7a3323df008dc5156b761f8576f6c0
cd kubevirt-migration-parameters

# 创建三节点 Kind 集群、安装 KubeVirt,并部署 RWO PVC 与测试 VM。
./setup.sh

# 运行单个策略;第三个参数是结果目录。
./run-profile.sh migration-main standard results/01-standard
./run-profile.sh migration-main auto-converge-long results/07-auto-converge-long

# 分别验证集群级和单节点迁出并发。
./run-concurrency.sh 07-concurrency-cluster-patch.yaml results/12-concurrency-cluster
./run-concurrency.sh 08-concurrency-node-patch.yaml results/13-concurrency-node

# 验证恢复原卷集合会取消正在运行的 VolumeMigration。
./run-cancel.sh results/14-cancel

实验完成后删除专用集群:

1
kind delete cluster --name kv-migration-params

参数定义与行为边界可继续对照 KubeVirt 的 Live MigrationMigration PoliciesAPI Reference

#Kubernetes #KubeVirt #Live Migration #Kind #RWO