KubeVirt RWO 热迁移参数实测:Pre-copy、Auto-converge、Post-copy 与超时
allowAutoConverge: true 可以直接用于普通的 pre-copy 迁移。迁移仍从 pre-copy 开始;这个开关允许 QEMU 在难以收敛时逐步限制 Guest CPU,让脏页产生速度降下来。
本地 RWO PVC 不能直接创建普通 VirtualMachineInstanceMigration。KubeVirt 会在准入阶段拒绝:
| |
RWO 场景要使用 KubeVirt 的 VolumeMigration:准备位于另一节点的目标 PVC,在 VM 上配置 updateVolumesStrategy: Migration,再修改 VM 的卷引用。控制器会创建 VMIM,同时迁移内存、运行状态和磁盘内容。
实验环境
实验运行在一台 Ubuntu 主机上的三节点 Kind 集群中:
| 项目 | 值 |
|---|---|
| KubeVirt | v1.9.0 |
| Kubernetes | v1.35.0,kindest/node:v1.35.0 |
| Kind | v0.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。
KubeVirt CR 还需要允许 LiveUpdate 和 LiveMigrate:
完整实验文件固定在 k8sdev fcac8be。其中 setup.sh 创建集群并安装 KubeVirt,run-profile.sh 负责切换策略和源、目标 PVC,results/summary.tsv 保存汇总结果。
本文的字段范围和默认值以 KubeVirt v1.9.0 的 MigrationConfiguration 和 MigrationPolicySpec 定义为准,行为解释对照官方 Live Migration、Migration Policies 和 Volume Migration 文档。MigrationPolicy 当前仍是 v1alpha1,接口可能继续变化;experimental 下的选项不能当作稳定生产 API。
迁移模式不是单选项
KubeVirt 的迁移总是从 pre-copy 开始。completionTimeoutPerGiB 先根据 VMI 的迁移数据量计算 acceptable completion time;官方文档中的数据量包括内存和需要复制的 ephemeral disk。超过这个时间后,KubeVirt 再根据 allowPostCopy 和 allowWorkloadDisruption 决定取消、切 post-copy,还是暂停 Guest 完成复制。
下面六个字段既能作为 KubeVirt CR 的集群默认值,也能由 MigrationPolicy 按 namespace/VMI label 覆盖。
| 参数 | v1.9.0 默认值 | 触发条件和作用 | 主要影响 | 本次验证 |
|---|---|---|---|---|
allowAutoConverge | false | pre-copy 难以收敛时,允许 QEMU 逐步限制 Guest CPU,降低 dirty rate | 迁移更容易收敛,但 Guest 可用 CPU 和业务性能会下降;不保证一定成功 | 短窗口失败,长窗口成功;未测量实际 CPU 限制比例 |
allowPostCopy | false | acceptable completion time 到达后,允许从 pre-copy 切换到 post-copy | 能迁移高 dirty-rate VM;目标运行依赖源端剩余内存页,网络或源节点故障可能导致 VM 崩溃 | 因 userfaultfd 不可用而失败 |
allowWorkloadDisruption | false | 超时后不立即取消;允许 post-copy 时优先切换,否则可以暂停 Guest 完成复制 | 提高完成概率,但可能出现可见停顿;暂停时间取决于剩余数据和带宽 | 短窗口失败,较高带宽用例成功 |
bandwidthPerMigration | 0,不限制 | 限制每个迁移的数据传输速率,单位是 Kubernetes Quantity/秒 | 值低会延长迁移并占用迁移并发槽位;值高会与业务流量和其他迁移争抢网络 | 4 MiB/s 为 124 秒,64 MiB/s 为 30 秒 |
completionTimeoutPerGiB | 150 秒/GiB | 按 VMI 内存和需复制 ephemeral disk 的大小计算可接受完成时间 | 设置过短会在 auto-converge 尚未起效时取消或回退;设置过长会让不收敛迁移长期占用资源 | 20 秒/GiB 用例在 acceptable completion time 80 秒后失败 |
maxDowntimeMs | 900 毫秒 | MigrationStallDetection 启用后,限制切换阶段可接受停机时间;主要用于 allowWorkloadDisruption: false 的收敛判断 | 值越高越容易切换,但业务停顿预算也越大;超过预算仍无法切换时迁移失败 | v1.9.0 未开 gate 时被准入拒绝;开 gate 后遇到 Alpha 路径错误 |
因此“标准模式开启 allowAutoConverge”是有效组合:仍然是 pre-copy,只是允许通过 CPU throttling 帮助收敛。全部策略都在同一份清单中:
这组实验只证明配置进入迁移状态,并在 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,随后完成 |
汇总数据和准入、失败边界保存在固定提交中:
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 的 migrationPolicyName 是 60-specific-policy。
v1.9.0 的 MigrationPolicy.spec.experimental.compression 还支持 none 和 zstd。设置 zstd 后迁移在 19 秒内完成,virt-launcher 日志明确记录:
| |
这次没有做 CPU 使用量和网络字节数对照,因此只能确认压缩被启用并成功完成,不能宣称它一定更快。
并发限制
并发参数只能在 KubeVirt CR 上设置,不能由 MigrationPolicy 覆盖。两个对照 patch 分别隔离集群级上限和单节点迁出上限:
两个辅助 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 覆盖:
allowAutoConvergeallowPostCopyallowWorkloadDisruptionbandwidthPerMigrationcompletionTimeoutPerGiBmaxDowntimeMs
MigrationPolicy 另外暴露两组实验参数。默认值和算法含义来自 v1.9.0 的 StallDetectorOptions 定义。
| 字段 | v1.9.0 默认值 | 作用与影响 | 本次验证 |
|---|---|---|---|
experimental.compression | 未设置 | none 关闭压缩,zstd 在 multifd 通道启用 Zstandard;通常以额外 CPU 开销换取更少的传输字节 | zstd 生效并在 19 秒内完成;未做 CPU 和网络字节对照 |
stallDetector.stallMargin | 4% | 将剩余字节与历史最佳值比较时的容差;容差大会更容易把当前状态判定为停滞或局部最小值 | 进入 Alpha 路径后失败,未单独验证 |
stallDetector.ewmaAlpha | 0.4 | 迁移带宽 EWMA 平滑因子,范围是 (0, 1];值越高,估算越看重最近样本 | 同上 |
stallDetector.stallProgressTimeout | 40 秒 | 跟踪最小剩余字节、判定停滞的滑动窗口 | 同上 |
stallDetector.switchoverTimeout | 60 秒 | 触发 stop-and-copy 或 post-copy 后允许的切换时间;超时则中止迁移 | 同上 |
stallDetector.precopyPossibleFactor | 1.5 | 估算停机时间可超出 maxDowntimeMs 的最大倍数,仍可尝试软 stop-and-copy;超过后中止 | 同上 |
stallDetector.patienceWindowDecayFactor | 0.5 | 每次放宽最佳剩余字节基线后,缩短下一个等待窗口的倍数 | 同上 |
stallDetector.searchLocalMinima | true | 延后收敛动作,等待剩余字节接近已观测的局部最小值;为 false 时可在检测到停滞后立即动作 | 同上 |
stallDetector.completionTimeoutFactor | 2 | 放大基于 completionTimeoutPerGiB 计算的时间预算,用于判断强制切换是否还来得及完成 | 同上 |
本次启用 MigrationStallDetection 后,RWO block migration 在 pause 用例中返回 value of ewmaBandwidthBps not set!。这是一条 Alpha 路径上的实测失败,不应作为生产配置示例。
只能在 KubeVirt CR 设置
下面的字段只能在 KubeVirt CR 设置。表中的默认值来自 v1.9.0 源码定义;实验为了隔离变量,把并发基线改成了 3/3,并在对应用例中临时覆盖。
| 字段 | v1.9.0 默认值 | 能做什么、何时触发 | 影响和风险 | 本次验证 |
|---|---|---|---|---|
parallelMigrationsPerCluster | 5 | 限制整个集群同时处于运行阶段的迁移数 | 太低会排队;太高会同时消耗网络、CPU、存储吞吐和目标节点资源 | 设置为 1 后,两台 VM 同时迁移时 max_running=1 |
parallelOutboundMigrationsPerNode | 2 | 限制单个源节点同时迁出的 VM 数 | 避免维护或故障转移时把单节点网络、CPU 打满;也可能延长 drain 时间 | 设置为 1 后,同一源节点只有一个迁移进入 Running |
progressTimeout | 150 秒 | 剩余数据连续完全没有进展达到该时间后,把迁移视为卡死并取消;只要出现进展,计时就会重置 | 它处理的是“完全卡住”,不是 dirty rate 上下波动导致的不收敛;过短可能误杀短时网络抖动 | 设置为 5 秒并做断流实验,但未隔离出单一计时器结果 |
utilityVolumesTimeout | 150 秒 | VMIM 在 Pending 阶段等待 containerDisk、cloud-init 等 utility volume detach;超时仍未释放则标记 Failed | 只影响迁移准备阶段,不是内存或磁盘复制超时 | 字段进入迁移配置;未故意制造 utility volume detach 卡住 |
unsafeMigrationOverride | false | 兼容性检查认为迁移对 Guest 不安全时,允许继续创建迁移 | 只绕过 KubeVirt 检查,不会让不兼容 CPU、设备或存储突然具备迁移能力,可能造成 Guest 故障 | 保持 false,只验证 API 和状态传播 |
disableTLS | false | true 会关闭 KubeVirt 迁移代理上的额外 TLS 保护 | v1.9.0 明确将其视为高风险配置;迁移内存和磁盘数据失去该层加密,必须依赖可信迁移网络 | true 时迁移成功,实验后恢复 false |
network | 未设置,使用 Pod 网络 | 指定迁移数据使用的 CNI 网络名称 | 可以隔离业务和迁移流量,但所有节点必须具备一致、可达的 Multus/NAD 配置 | Kind 环境没有第二迁移网络,未做行为验证 |
matchSELinuxLevelOnMigration | false | 默认把目标 virt-launcher 的 SELinux level 固定为源端 level;设为 true 后由 CRI 为目标随机分配 level | 随机 level 能减少同节点 category 共享,但不自动处理 SELinux level 的 RWX 卷可能导致迁移失败 | Host 无可用 SELinux 环境,未做行为验证 |
nodeDrainTaintKey | kubevirt.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 | 指定要迁移的 VMI | VMI 必须存在于 VMIM 所在 namespace | VolumeMigration 由控制器填写,多轮成功和失败用例均覆盖 |
addedNodeSelector | 在 VM 原有 nodeSelector 或 nodeAffinity 上增加目标节点约束 | 只能收紧约束;key 冲突时保留 VM 上的值,不能用它绕过 VM 的限制 | 直接 VMIM 在调度前先被 RWO 磁盘校验拒绝,未进入节点选择 |
priority | 控制器内部区分 system-critical、user-triggered 和 system-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.sh 和 collect-migration.sh 采集状态与证据。
按下面的顺序执行。setup.sh 使用独立 kubeconfig,不覆盖默认 context。
| |
实验完成后删除专用集群:
| |
参数定义与行为边界可继续对照 KubeVirt 的 Live Migration、Migration Policies 和 API Reference。