TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

KubeVirt 动态迁移实测 2:高负载内存、节点驱逐、并发限制

状态:实测(2026-10-31)。 第 1 篇中的嵌套集群(3 个 TT Lab 练习虚拟机,KubeVirt v1.9.0)中测得的数据。 策略测试在每个条件下重复 1–3 次,drain 在每个场景下进行一次,并发限制在每个限制下进行一次。编写规则:实测数据为基础,无法测量的标注为“未实测”, 原因未明的解释标注为“推测”。

目录

1. 持续写入内存的虚拟机

第 1 篇中的虚拟机没有执行任何操作。实际中的虚拟机是会占用内存的。预拷贝采用的是“重新发送在发送过程中发生更改的页面”的方式, 因此,如果虚拟机修改内存的速度与发送速度相似,迁移就无法完成。我们亲自复现了这一点。

测试 负载 策略 结果 耗时 虚拟机暂停时间 服务中断(新连接 / 保持连接)
light-1 128MB 无 Failed Live migration has been aborted 302秒 无(在源节点继续运行)
heavy-1 512MB 无 Failed 301秒 无
heavy-2 512MB bandwidthPerMigration: 32Mi Failed 301秒 无
heavy-3 512MB allowAutoConverge: true Succeeded 33秒 566ms 2807ms / 2805ms,中断
uffd-3 512MB allowAutoConverge: true Succeeded 36秒 547ms 1604ms / 1603ms,中断
heavy-5 512MB 32Mi + autoConverge Succeeded 43秒 433ms 4287ms / 4384ms,中断
heavy-4 512MB allowPostCopy: true Failed Postcopy is not supported: Userfaultfd not available: Operation not permitted 2秒 无
heavy-6 512MB 32Mi + postCopy Failed(相同原因) 1秒 无
uffd-1 512MB allowPostCopy: true,节点 vm.unprivileged_userfaultfd=1 Succeeded(PostCopy) 303秒 未实测 2204ms / 3006ms,中断
uffd-4 512MB 同上 Succeeded(PostCopy) 304秒 未实测 6214ms / 6670ms,中断
uffd-2 512MB 32Mi + postCopy,开启节点设置 Succeeded(PostCopy) 306秒 未实测 6211ms / 6213ms,中断
메모리를 계속 쓰는 VM: 정책별 결과 1Gi 게스트에 512MB 쓰기 부하, 결과가 날 때까지(실측) 기본(정책 없음) 실패 · 301초 대역폭 32Mi 실패 · 301초 autoConverge 성공 3/3 · 33–43초 postCopy, 노드 설정 없음 실패 2/2 · 1–2초 postCopy, 노드 설정 켬 성공 3/3 · 303–306초 0초 100초 200초 300초 왜 기본값으로는 끝나지 않나 게스트가 더럽히는 속도 74–92MiB/s ≈ 보내는 속도 → 매 반복 같은 양이 다시 남아 멈출 순간이 오지 않는다
图:内存写入负载下的各策略结果及耗时

1.1 为什么默认配置无法结束

每隔 5 秒读取一次正在进行的 libvirt 任务信息,发现未收敛的运行在 300 秒内重复了 184–244 次预拷贝。剩余数据量在 15–90MiB 之间浮动,预计发送完毕所需的暂停时间为 1,782–1,913ms。始终未能达到目标(从 300ms 开始,最大 900ms)。虚拟机产生脏页的速度约为每秒 19,000–23,000 页(74–92MiB/s),几乎与发送速度持平。这意味着每次迭代残留的数据量都差不多。

仅持续写入 128MB 也会失败。 在此期间,由于虚拟机在源节点上正常运行,因此没有发生服务中断。相反,由于虚拟机无法被迁移,该节点无法被腾空(第 2 节)。

1.2 各策略汇总

策略 在 512MB 负载下的表现 代价
无(默认) 无法收敛,约 300 秒后放弃 无法腾空节点
带宽限制 32Mi 同样放弃 设置了限制,但实际传输超出了限制(见下文)
autoConverge 33–43 秒内成功(3/3),暂停 433–566ms 逐渐降低虚拟机 CPU 速度以减缓脏页产生速率。虚拟机吞吐量下降了多少未进行测量(未实测)
postCopy 成功(3/3),但必须在耗尽预拷贝超时时间后才转入 PostCopy。服务中断 2.2–6.7 秒 节点需满足 vm.unprivileged_userfaultfd=1 要求。如果在后拷贝过程中源节点或目标节点宕机,将丢失虚拟机(根据文档说明)

autoConverge 顾名思义是一个“使其收敛”的机制。当进展受阻时,QEMU 会稍微限制虚拟机的 vCPU,从而降低脏页产生速率。在此负载下,仅凭这一点就将结果从失败扭转为了在 30 秒内成功。

postCopy 则相反。它先在目标端运行虚拟机,尚未传输的页面在虚拟机触及的瞬间从源节点拉取。由于 Linux 的 userfaultfd 用于捕获该瞬间,因此需要节点内核的允许,而 Ubuntu 的默认值是 0(嵌套 worker 也是 0)。开启后虽然成功了,但 KubeVirt 必须在耗尽预拷贝超时时间后才切换到 PostCopy。并且在后拷贝期间,虚拟机的内存分散在两个节点上,因此无论哪一方宕机都会丢失虚拟机。 如果环境中存在高负载的虚拟机,建议首先尝试开启 autoConverge ,这是本次实测得出的先后顺序。

1.3 带宽限制未生效

bandwidthPerMigration: 32Mi 当设置了时,配置已经写入了所有层级。VMI 的应用配置中显示了 32Mi,QEMU 的 max-bandwidth: 33554432, virsh migrate-getspeed 32 也都已生效。然而,QEMU info migrate 的吞吐量为 630.76Mbps(约 75MiB/s),10 秒内处理的数据量也从 17.316GiB 增至 18.040GiB(约 73.5MiB/s)。传输通过 8 通道 multifd 进行。这与无限制时(86–90MiB/s)几乎相同。

究竟是由于 multifd 与限速的关系,还是 KubeVirt 方面的配置导致的,需要通过关闭 multifd 的对照测试来辨别(未实测)。因此目前建议不要盲目相信“设置限制就能保护网络”,而是应当通过 virsh domjobinfo 来确认实际传输量。顺便一提,用户指南中记载的默认带宽 64MiB/s 是旧值,v1.9.0 的实际默认值是不限制(0)。修复该文档的 PR 已经处于开启状态。

1.4 应用策略的方法

MigrationPolicy 通过命名空间或 VMI 标签来选择目标。以下是在集中了高负载虚拟机的命名空间中仅开启 autoConverge 的示例。

apiVersion: migrations.kubevirt.io/v1alpha1
kind: MigrationPolicy
metadata:
  name: busy-vms-auto-converge
spec:
  allowAutoConverge: true
  selectors:
    namespaceSelector:
      kubernetes.io/metadata.name: busy-vms

是否成功应用可通过迁移后 VMI 的 status.migrationState.migrationConfiguration 和 status.migrationState.migrationPolicyName 进行确认。

2. 节点腾空(drain)

最常使用动态迁移的场景是在腾空节点时。如果在虚拟机中配置了 evictionStrategy: LiveMigrate , kubectl drain 将在不关闭虚拟机的情况下将其迁移。我们对这一路径进行了四种场景的测试。

노드 비우기(drain)가 VM 을 옮기는 길 KubeVirt v1.9.0, evictionStrategy: LiveMigrate(실측) kubectl drain 파드 축출 요청, 5초마다 다시 승인 웹훅이 축출을 거절 PDB 가 아니다. VMI 에 축출 표시를 남김 축출 마이그레이션 생성 동시 한도(노드당 2)를 따른다 성공 원본 파드가 사라져 다음에 통과 실패하면 다시, 또 다시 수렴 못 함: 약 5분마다 포기 받는 노드 메모리 부족: 5분 Pending 뒤 실패 옮길 수 없는 VM: 끝없이 거절 실측: 한 대 22초, 다른 한 대 13초 되돌려도(uncordon) 축출 표시가 남는다 status.evacuationNodeName 이 그대로라 마이그레이션이 계속 생기고, 결국 VM 이 되돌린 노드를 떠났다 virtctl evacuate-cancel (v1.9.1 이상 virtctl)
图:drain 迁移虚拟机的路径:驱逐请求、准入 Webhook 的拒绝与驱逐迁移、成功与失败、恢复后依然残留的驱逐标记
场景 操作内容 结果
A 对带有 512MB 负载的虚拟机(LiveMigrate)所在节点执行 drain(超时 600 秒) drain 失败(600.9 秒)。 Webhook 以 Evacuation in progress: Eviction triggered evacuation of VMI … 拒绝并创建了驱逐迁移,但由于无法收敛,约每 5 分钟放弃一次并重新创建
B 对磁盘为 RWO 且为 evictionStrategy: LiveMigrate 的虚拟机所在节点执行 drain(超时 120 秒) drain 失败(120.8 秒)。 VMI … is configured with an eviction strategy but is not live-migratable 持续拒绝
C 在 A、B 之后将节点恢复为可调度状态(uncordon) 驱逐并未停止。 恢复后又产生了两次驱逐迁移,关闭负载后最后一次成功,虚拟机 离开了已恢复的节点
D 对包含 3 个关闭负载的虚拟机(LiveMigrate)和 1 个(evictionStrategy None)的节点执行 drain(超时 400 秒) 其中两台在 22 秒·13 秒 内被迁移。设置为 None 的虚拟机在 7.4 秒时关闭,并在 17.9 秒时作为新 VMI 在其他节点上启动(重启)。其余一台由于目标节点的内存请求达到 98% 而 Insufficient memory,5 分钟后失败并重试,最终 drain 400 秒超时

2.1 是 Webhook 而非 PDB

在 drain 期间, kubectl get pdb -A 是 No resources found 。虽然用户指南的节点维护文档中写着“为每个 VMI 创建 PodDisruptionBudget”,但 v1.9.0 是通过将 virt-launcher Pod 的驱逐请求发送给 准入 Webhook(virt-launcher-eviction-interceptor.kubevirt.io)来接收的。Webhook 在拒绝驱逐的同时会在 VMI 上留下驱逐标记(status.evacuationNodeName),virt-controller 看到该标记后便会创建驱逐迁移(kubevirt-evacuation-…)。 drain 每隔 5 秒重新请求一次驱逐,当迁移完成且源 Pod 消失时才算通过。针对该文档不一致的问题,我们已提交了修正文档的 PR(user-guide#1051).

因此,drain 是否能完成取决于 该节点上的所有虚拟机是否都能被成功迁移。

2.2 回退后依然残留的驱逐标记

场景 C 是最出乎意料的。由于 drain 失败,我们对节点执行了 uncordon,但 VMI 的 status.evacuationNodeName 标记却依然保留着。virt-controller 看到这个标记后,继续创建了驱逐迁移(执行了两次 uncordon,之后又生成了两次),关闭负载后其中一个成功了,VM 离开了刚刚恢复的节点。也就是说,即使喊出“先停下 drain 并回退”,VM 实际上也仍在搬迁途中。

用于清除此标记的命令是 virtctl evacuate-cancel 。然而,如果使用 v1.9.0 的 virtctl 执行,则会像这样失败。

Internal error occurred: the server rejected our request due to an error in our request

服务器(virt-api)会附加一个“仅当当前正在驱逐的节点名称与请求相同时才清除”的条件,这是因为 v1.9.0 的 virtctl 在请求中没有填入节点名称而导致的漏洞。如果在同一个请求的正文中加入 {"evacuationNodeName": "<노드>"} 并发送,就会以 200 状态码成功清除。该问题已经修复(kubevirt#17008,release-1.9 的 backport #18864),使用 v1.9.1 及以上版本的 virtctl 即可。即便服务器是 v1.9.0,只要 virtctl 是新版本就行。

3. 并发迁移限制

我们将六台 cirros VM 汇聚到一个节点上,然后同时创建了六个 VMIM。我们只修改了 KubeVirt CR 的 parallelOutboundMigrationsPerNode ,而集群限制(parallelMigrationsPerCluster: 5)保持不变。

单节点限制 直到六台全部完成 同时 Running 最大值 单次 Running → Succeeded
1 33.9秒 1 2–6秒
2(默认) 22.3秒 2 3–4秒
4 23.2秒 4 8–9秒

即使将限制从 2 提高到 4,速度也没有加快。因为单个节点能够输出的带宽(约 90MiB/s)被分摊了,导致单次迁移变得相应缓慢。如果是多个小型 VM,将限制从 1 提高到 2 的收益(34 秒 → 22 秒)很明显,再往上就触及了 网络带宽上限。

我们未能测量迁移专用网络(spec.configuration.migrations.network)(未实际测量)。由于嵌套节点只有两个 NIC 且没有 Multus,因此无法构建独立的网络。在这种配置下,迁移、NFS 磁盘和业务流量共享了大约 800Mbps 的带宽。在实际生产环境中,如果 VM 较大或数量较多,应当优先配置专用网络,限制则排在其次。

# KubeVirt CR 의 일부. 비워 두면 위 기본값(클러스터 5, 노드당 2, 대역폭 무제한)
spec:
  configuration:
    migrations:
      parallelMigrationsPerCluster: 5
      parallelOutboundMigrationsPerNode: 2

4. 本次测量的局限性

在引用这些数据之前,请先核实相关条件。

5. 总结:在信任热迁移之前

检查项 原因(基于第 1、2 篇的实测)
磁盘非 RWX,网络非 masquerade 否则 Webhook 会从 VMIM 创建阶段就予以拒绝
将 CPU 模型设置为节点共同支持的命名模型 host-model 即使显示 True,也会在 5 分钟后失败
对繁忙的 VM 启用 autoConverge 默认情况下,即使写入 128MB 也会在 300 秒后放弃,而 autoConverge 仅需 33–43 秒
若要使用 postCopy,需先配置节点 sysctl 否则会在 1–2 秒内失败
带宽限制需通过实际传输量来验证 设置了 32Mi 但实际跑出了 75MiB/s
无法迁移的 VM 应设置 evictionStrategy None 如果是 LiveMigrate,则 drain 无法完成
目标节点的内存余量 若剩余 98%,则每 5 分钟失败并重试一次
v1.9.1 及以上版本的 virtctl 用于清除 uncordon 后依然残留的驱逐标记
客户端重新连接 服务中断时间比 Guest 暂停时间长好几倍

如果您想亲自尝试,TT Lab 的实操课程“KubeVirt 热迁移”(网站路径 /course/kubevirt-live-migration)已准备好与本测量相同的 VM 集合和相同的环境。从选择公共 CPU 模型开始,到迁移并读取暂停时间、读取 RWO 拒绝、再到 drain,一共分为八个步骤。

Titi 的小贴士。 如果在打包行李的时候旁边有个一直把房间弄乱的朋友,那搬家就永远也结束不了。遇到这种情况,只要让这位朋友稍微放慢速度(autoConverge),30 秒就能搞定。而且,取消搬家并不意味着事情就结束了!如果不把贴在门上的“搬家中”便条(evacuationNodeName)撕掉,行李可是会自己跑出去的哦!

登录后即可点赞

评论

还没有评论。

登录后即可发表评论