TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

学生 VM 启动记 2:KubeVirt 内部 — 为什么 VM 会在 Pod 内部运行

状态:草稿(2026-10-16),官方文档 + 部分实测。 组件说明基于 2026-10-16 查阅的 KubeVirt 仓库组件文档和用户指南。版本为 KubeVirt v1.9.0。标有“实测”的数据均在 TT Lab 的实验 VM 中测得。 编写规则:实测数据为基础,未能测得的标为“未实测”,在文档中未找到的标为“未确认”。

学生 VM 启动记 2:KubeVirt 内部

目录

第 1 篇中的第 ⑥ 点“VM → VMI → Pod → QEMU”。

1. VirtualMachine 与 VirtualMachineInstance

初学 KubeVirt 时,很容易混淆这两个名称相似的对象。

VirtualMachine (VM) VirtualMachineInstance (VMI)
含义 “存在这样一台 VM,是否应该保持开启”的期望状态 当前实际正在运行的一个实例
生命周期 保留至手动删除 关闭 VM 时消失,开启时重新创建
比喻 Deployment Pod

TT Lab 后端只创建 VM。在清单中写入 running: true 即可在创建后立即启动。KubeVirt 提供了更详细定义该行为的 runStrategy(Always·RerunOnFailure·Once·Manual·Halted),但二者不能同时使用。虽然代码注释中将 running 标记为了即将废弃,但在运行策略指南中未能找到相关说明(未确认)。为了与现有的 VM 保持行为一致,目前尚未对其进行更改。查看状态时读取的是 VMI。如果找不到 VMI,则判定为“正在启动”;如果连 VM 也没有,则判定为“已消失”。

2. 四大组件

VM 한 대 = 파드 하나 안의 QEMU 프로세스 컨트롤 플레인 kube-apiserver virt-api virt-controller 검증·기본값·서브리소스 객체를 보고 만든다 VM 켜 둘지 VMI 지금 도는 것 파드 virt-launcher 노드 (KVM 이 되는 곳) virt-handler 데몬셋 · 노드마다 VMI 를 켜고 끈다 kubelet 파드를 띄운다 virt-launcher 파드 virt-launcher (감시·신호 전달) libvirtd (도메인 관리) QEMU 프로세스 게스트 OS · systemd · root /dev/kvm 으로 하드웨어 가속 볼륨: 컨테이너 디스크 · emptyDisk PVC · cloud-init Secret 실측(2 vCPU · 4Gi VM): 파드 요청 CPU 200m · 메모리 4372Mi vCPU 하나에 CPU 0.1 개(할당 비율 기본 10), 메모리는 게스트 + 약 280Mi 파드라서 공짜로 얻는 것: 스케줄링 · 자원 요청 · 파드 네트워크 · 정책
图解:一台虚拟机就是 Pod 内部的一个 QEMU 进程

KubeVirt 仓库组件文档中所述的角色职责总结如下。

文档中将创建顺序分为了七个步骤。当 VMI 创建后,virt-controller 创建 Pod,调度器将其调度到节点上;一旦 Pod 启动,virt-controller 便将节点名称写入 VMI,此后该节点的 virt-handler 接管并通过 Pod 内的 libvirtd 创建 domain。第 1 篇中的实测数据(Pending 0秒 → Scheduled 10秒 → Running 20秒)测量的正是这个过程。

3. 为什么要把 VM 放入 Pod 内部

初看之下这显得有些奇怪。VM 已经是比容器更重的隔离级别了,为什么还要把它塞回容器里?答案是:“为了不重复造 Kubernetes 已经擅长做的事情”。在 TT Lab 中实际体验到的好处主要有以下几点:

  1. 调度与资源计算。 VM 的 CPU 和内存直接转化为 Pod 的资源请求,调度器可以照常进行计算。经实测,一个 2 vCPU、4Gi VM 对应的 Pod 请求为 CPU 200m,内存 4372Mi(实测,2026-08-29)。一个 vCPU 仅请求 0.1 个 CPU 是因为 KubeVirt 的 cpuAllocationRatio 默认值为 10(用户指南中的“Resources requests and limits”),而内存则在 Guest 的 4Gi 基础上附加了约 280Mi 的开销。
  2. 网络与策略。 Guest 直接使用 Pod 网络(masquerade)。因此,原本用于实验 Pod 的网络策略也可以同样的方式应用到 VM 上。策略选定的目标不是 VM,而是 virt-launcher Pod 的标签,因为实际的网络端点是该 Pod(第 6 篇)。
  3. 卷(Volume)。 Guest 的磁盘本质上就是 Pod 的卷。挂载 PVC、创建节点本地临时目录、注入 Secret 等操作全部由 Kubernetes 统一处理(第 3、4 篇)。
  4. 生命周期。 Pod 被删除时,VM 也会终止,附属于 Pod 的临时卷也会一同消失,从而简化了资源的回收(第 5 篇)。

当然也有代价。

4. KVM:硬件加速

Guest 通过 QEMU 利用 KVM(/dev/kvm)进行硬件加速运行。KubeVirt 会在支持 KVM 的节点上暴露设备插件资源(devices.kubevirt.io/kvm),virt-launcher Pod 会请求该资源。因此,不支持 KVM 的节点不会调度 VM。

对于需要在 Guest 内部再次运行虚拟化的实验(例如在 VM 中启动 VM 的实验),需要将 CPU 模型更改为 host-passthrough 。默认的 CPU 模型会隐藏硬件虚拟化标志,如果保持默认,Guest 内的 QEMU 将仅通过软件仿真运行,导致启动需要数分钟。开启 host-passthrough 后,Guest 内部便会出现 /dev/kvm 并在 lscpu 中看到虚拟化标志(实测,2026-08-29)。代价则是 迁移受阻。如果各个节点的 CPU 模型不同,就无法进行迁移。由于实验 VM 本身就不需要迁移,所以可以接受这一点,但在不需要的实验中则不会开启。

5. 无法在 Guest 内部执行命令

Pod 拥有 exec ,后端可以通过它在 Pod 内部执行判题命令。而在 KubeVirt 的 VMI 子资源中,虽然有 console、vnc、portforward、guestosinfo 等,但 并没有 exec(通过实测确认了子资源列表)。这是因为 Guest 是一个独立的操作系统,只有当 Guest 内部预装了某些组件时,才可能在其中执行命令。

因此,TT Lab 在 Guest 内部嵌入了一个轻量级的判题代理(第 1 篇 ③)。学生的 Shell 则通过串口控制台(console 子资源)打开。由于无需开放 SSH,从而减少了一条连接路径。在原型验证中,我们甚至在完全关闭 SSH 的情况下也成功完成了判题。

6. 实际遇到的事故:仅仅因为一个功能门,VM 就完全无法启动了

刚部署 KubeVirt v1.9.0 时,没有一个 VM 能够启动。virt-launcher Pod 总是因以下错误而崩溃:

exec: "/container-disk-binary/usr/bin/container-disk": no such file or directory

起初以为是容器运行时配置损坏,但原因其实在别处。KubeVirt 1.9 将处于 Beta 阶段的功能门默认开启,导致 ImageVolume 自动启用了。该功能将容器磁盘作为 Pod 的镜像卷挂载,而 由于 Kubernetes 1.34 侧关闭了该功能门,导致 API Server 静默删除了镜像卷及其挂载。 由于 Pod 没有被拒绝而是被正常接受了,因此没有任何警告日志留存。直到检查运行中 Pod 的规格发现镜像卷为空时,才最终确认了这一点。容器运行时也是第二道关卡。由于只有 containerd 2.1 及以上版本才支持镜像卷,因此如果仅仅开启了 Kubernetes 的功能门,最终也只会将静默删除变成挂载失败。

我们通过关闭 KubeVirt 侧的功能门,恢复了旧的方式(空目录与用于复制的 init 容器)。由于此配置会写入 KubeVirt 的自定义资源中,因此 每次升级 KubeVirt 时都需要重新确认 。在撰写本文的当天,我们通过阅读确认了已关闭的功能门列表中依然包含 ImageVolume 。

得到的教训是:“Pod 中的 VM”建立在 Kubernetes、KubeVirt 和容器运行时这三个版本的交集之上。仅仅升级其中一个,就可能会悄悄打破与其他二者的约定。

7. 局限性

TT 寄语。 把 VM 放入 Pod 一开始看起来就像是“套娃”,但多亏了它,调度、网络策略和卷管理都可以由 Kubernetes 代劳了。不过一定要记住,这依赖于三个版本的协同配合。千万不要只升级了一个组件,结果导致 VM 悄悄无法启动哦!

参考

所有文档均于 2026-10-16 查阅。

登录后即可点赞

评论

还没有评论。

登录后即可发表评论