Proxmox VE 给了我两种选择:LXC 容器启动快、资源省,但一台宿主机内核被攻破,所有容器一起遭殃;KVM 虚拟机隔离性好,但启动要等 BIOS 自检、GRUB 加载、内核解压,一套下来 5-10 秒才看到登录提示符,每个虚拟机还要养着一整条模拟出来的 PCI 设备链。

Rui Carmo 的 pve-microvm 项目直接解决了这个问题。它利用 QEMU 的 microvm machine type,在 Proxmox 上实现了 sub-300ms 启动的 KVM 虚拟机,同时保持硬件隔离。

microVM 做了什么

QEMU 的 microvm 机型的思路很简单:去掉一切不是必须的。标准 KVM 虚拟机启动时要模拟 BIOS/UEFI 固件、IDE 控制器、VGA 显示、USB 控制器、PCI 桥接器——每一个都是几十毫秒到几百毫秒的启动开销,而且这些模拟设备在虚拟机运行期间也占着内存。

microVM 把这些全砍了。没有固件、没有引导加载器、没有传统设备。内核由宿主机直接加载进客户机内存,然后通过 virtio 通道提供块设备和网络。这就是 Firecracker(AWS 的微型虚拟机运行时)的方案,只是现在跑在 Proxmox 上。

pve-microvm 以一个 .deb 包的形式工作。安装时会 patch Proxmox 的 Perl 模块(Machine.pm、QemuServer.pm),当 VM 配置设为 machine: microvm 时,config_to_command 函数会委托给 MicroVM.pm 生成一套完全不同的 QEMU 命令行:

qemu-system-x86_64 -M microvm,x-option-roms=off,pit=off,pic=off,\
  isa-serial=on,rtc=on,acpi=on,pcie=on \
  -kernel /usr/share/pve-microvm/vmlinuz \
  -initrd /usr/share/pve-microvm/initrd \
  -append "console=ttyS0 root=/dev/vda rw quiet" \
  -device virtio-blk-pci-non-transitional,drive=drive-scsi0 \
  -device virtio-net-pci-non-transitional,netdev=net0

没有 SeaBIOS,没有 GRUB,没有 VGA。客户机只有一个串口控制台(Proxmox 的 xterm.js 原生支持),virtio 块设备,和一个 virtio 网卡。所有设备走 PCIe non-transitional(纯现代模式)以避免 MMIO 路线上的 Linux 设备探测 bug。

性能数据

实测数据很能说明问题。在一台 i7-12700 上:

  • SmolBSD(一个 NetBSD 精简版):31ms 到登录提示符
  • 带 Docker 和 QEMU guest agent 的完整 Debian:首次启动约 8 秒(大部分花在 apt 包安装上),后续冷启动稳定在 300ms 左右
  • 在 Atom x5-Z8350(2GB 内存的 2016 年古董)上也能跑 6 个 microVM 才开始变慢

关键是空闲内存开销只有 40MB 左右。KVM 实际上只分配客户机真实触及的页面,而且宿主机级别的 same-page merging 会在所有 microVM 之间去重共享页面——内核、libc、基础镜像层。一台宿主机上跑 10 个 Debian microVM,真实内存开销远小于 10 × 40MB。

启动时序:把不必要的一切砍掉

标准 KVM VM 的启动路径是:QEMU 初始化模拟芯片组 → SeaBIOS 自检 → 枚举 PCI 设备 → 从磁盘加载 GRUB → GRUB 加载内核和 initrd → 内核初始化驱动 → switch_root → systemd 启动。

microVM 的启动路径只有三步:QEMU 初始化最小设备集 → 直接从宿主机文件加载内核和 initrd 到客户机内存 → 内核初始化 virtio 驱动 → switch_root。完全跳过了固件和引导加载器。

这个差异在时间上很直观。同样一台 i7-12700:

阶段 标准 VM microVM
QEMU 设备初始化 ~300ms ~5ms
SeaBIOS/GRUB 加载 ~800ms 0
内核解压+初始化 ~400ms ~150ms
initrd + switch_root ~200ms ~150ms
systemd 启动 ~2-3s ~1-2s
总计(首次) ~4-5s ~1.5-2s
后续冷启动 ~4-5s ~300ms

300ms 这个数字的关键在于 QEMU 进程的重用。microVM 的 QEMU 实例启动后,内核已经在内存中了,后续克隆只是挂载 rootfs。相比之下,标准 VM 每次启动都要重新走一遍 SeaBIOS 的设备枚举。

MMIO vs PCIe 传输层的选择

microVM machine type 支持两种 virtio 设备传输方式:MMIO(内存映射 I/O)和 PCIe。这里有一个很有意思的技术细节。

MMIO 是 microVM 最初设计的传输方式——它最轻量,没有 PCIe 主机桥、没有 ACPI。SmolBSD(NetBSD 精简版)用 MMIO 能做到 31ms 启动。但 QEMU 10.x 上,MMIO 路径对 Linux 客户机有一个设备探测 bug:只有 virtio-blk 能绑定,网络设备、串口和 balloon 设备都无法被驱动认领。

NetBSD 的 MMIO 探测逻辑走的是正确的代码路径,所以没有问题。Linux 内核(至少作者用的 6.12.22 版本)不行。解决办法是 Linux 客户机回退到 PCIe non-transitional virtio 设备,代价是额外增加约 50ms 的启动时间——在 300ms 的整体启动时间面前,这个代价完全可以接受。

这个 bug 是作者内核配置的问题还是一个更底层的 QEMU 问题,目前还不确定。但这也说明了 microVM 生态目前的成熟度——它足够好用,但遇到奇怪的边缘情况时需要自己动手排查。

host/guest 通信:vsock 和 virtiofs

每个 microVM 启动时会自动分配一个 vsock CID(公式是 VMID + 1000),用于宿主机和客户机之间的高效通信通道。目前的应用场景包括:

  • SSH agent forwarding:宿主机密钥通过 vsock 注入客户机,不需要暴露在网络上
  • virtiofs/9p 目录共享:宿主机目录直接挂载到客户机

vsock 比网络共享更安全——它不走网桥,没有 IP 层暴露。对于 CI/CD worker 场景(宿主机要把代码传进客户机编译),vsock 比 SMB/NFS 更高效也更安全。

作者的大部分 microVM 实例目前走 SMB 挂载来共享数据("在 Docker 和 LXC 下很难搞"),vsock 的 virtiofs 支持还在完善中。

网络和 SDN 集成

microVM 的网络接入方式和普通 Proxmox VM 完全一样——挂到 Linux bridge 上,走标准 PVE 防火墙规则。每个 VM 的 nftables 规则按标准方式生效。

配置方式也一致:

qm set 900 --net0 virtio,bridge=vmbr0         # 单网卡
qm set 900 --net0 virtio,bridge=vmbr0,tag=100  # VLAN 100

一个让我觉得设计合理的地方:pve-microvm 没有重新发明网络隔离方案,而是复用 Proxmox 现有的 SDN。用 VLAN zone 加独立 VNet 来隔离不可信的 Agent 沙箱,用 VXLAN zone 配合出口节点做流量审计。microVM 只需要把网卡指向对应的 VNet——网络策略在 Proxmox 层管理,不在每个 guest 里维护一个手工 iptables 规则集。

客户机内部走 systemd-networkd(不依赖 cloud-init),默认 DHCP。提供了一个 /etc/microvm-static-net 文件用于静态 IP。作者早期用 cloud-init 发现太脆弱,换到 systemd-networkd 后克隆稳定性大幅提升。

pve-microvm 最有意思的设计决策是:内核不在客户机磁盘里,而是放在宿主机上。

宿主机 /usr/share/pve-microvm/vmlinuz ──┐
                                          ├── → microVM A(直接内核引导)
                                          ├── → microVM B(同一份 vmlinuz)
                                          └── → microVM C(同一份 vmlinuz)

客户机磁盘里只有一个 rootfs——没有 /boot、没有 GRUB、没有内核包、没有 initramfs。这意味着:

  • 内核更新在宿主机上一次性完成,所有客户机下次重启自动生效
  • 没有客户机因为 apt upgrade 拉到一个坏内核而崩溃
  • rootfs 镜像可以复用,完全不受内核版本影响
  • 每个 microVM 的磁盘体积极小(纯 Docker 的 Debian rootfs 不到 500MB)

pve-microvm 附带一个 12MB 的自编译 Linux 6.12.22 内核,从 x86_64_defconfig 加微 overlay 构建,包含 virtio、vsock、virtiofs、9p 以及 Docker 需要的模块(overlay、veth、bridge、netfilter、BPF)。如果不够用,也可以换回 Debian 的 stock 内核——只是体积大 3 倍、启动慢一些。

模板和工作流

创建过程不像普通 VM 那样「挂 ISO 镜像→点安装向导」,而是直接从 OCI 镜像组装 rootfs:

# 安装
dpkg -i pve-microvm_0.3.12-1_all.deb

# 创建 Debian 模板
pve-microvm-template --vmid 9000 --storage local-lvm --profile standard

# 克隆成正式 VM
qm clone 9000 100 --name my-microvm --full
qm set 100 --machine microvm --memory 1024 --cores 2
qm start 100

模板构建大约 60 秒(拉 OCI 镜像、安装包、写 rootfs),之后克隆几乎是瞬时的(特别是用了 linked clone 的情况下)。

支持 21 种客户机 OS 类型:Debian、Alpine、Fedora、Rocky Linux、Amazon Linux、OpenWrt、OPNsense、NetBSD、甚至 Plan9。

对比 LXC:什么时候选哪个

LXC 仍然是正确的选择,当你:

  • 信任工作负载(自己的代码)
  • 需要零启动开销
  • 需要宿主机直接访问文件系统
  • 内存分配越少越好(而且能享受共享页缓存)

microVM 赢在:

  • 真正内核隔离:跑不可信代码、不同内核版本、嵌套 Docker、任何需要 CAP_SYS_ADMIN 的 workload
  • 可复现镜像:可以 vzdump 备份、离线迁移、克隆(包括 linked clone)
  • 非 Linux 客户机:NetBSD、FreeBSD 防火墙、Plan9——LXC 根本跑不了
  • 启动速度:命令敲完之前 VM 已经起来了

我自己的经验是,过去需要在「安全但慢的 VM」和「快但不安全的 LXC」之间做妥协的场景,现在有了第三种选择。比如跑 CI/CD worker、Agent 沙箱、反向代理——这些 workload 需要隔离性,但又不能接受每次部署等 10 秒的 VM 启动时间。

需要注意的地方

microVM 不是银弹。以下几个限制需要考虑:

无 VGA、无 USB。只有串口控制台。桌面类客户机不适合,USB 设备直通(比如 Zigbee 控制器)没法用。

无 GPU/PCI 直通。pve-microvm 默认禁用了 hostpci。作者在早期测试中成功直通了一张 RTX 3060,但因为缺乏可测试的硬件,暂时关闭了这个功能。

无热迁移。QEMU microvm machine type 不支持 live migration。离线迁移没问题(microVM 启动不到一秒,stop→migrate→start 一轮实测约 2 秒),但不能做到无缝。

patch Perl 内核实属脆弱。每次 qemu-server 升级都可能破坏 patch。虽然用了 dpkg trigger 自动重打,但升级时还是可能遇到棘手的失败模式。

如果这些限制在你的场景里不是阻碍,那 pve-microvm 可能是你一直在等的东西——一个把 AWS Firecracker 级别的微型虚拟机带到你自己 Proxmox 集群里的解决方案。

参考链接