SSD Nodes Learn
指南 Matt Connor作者: Matt Connor · 更新于 2026-07-19

VPS 上的嵌套虚拟化:KVM 与 Proxmox

检查您的 VPS 是否暴露 VT-x 或 AMD-V,开启嵌套 KVM,并在 guest hypervisor 中运行 Proxmox 或 Windows。含您真正会遇到的报错。

简短的答案

嵌套虚拟化就是在一台虚拟机内部运行一个 hypervisor(虚拟机监控程序):您的 VPS 本身已经是一个 guest(客户机),而您想让它托管属于自己的 guest。只有当您服务商的 hypervisor 特意把 CPU 的虚拟化扩展暴露给您的实例时,它才能工作。请在 /proc/cpuinfo 中查找 vmx 标志(Intel)或 svm(AMD),如果两者都没有出现,那么无论您在 VPS 内部怎么配置都无济于事。

先摆正一个预期:Docker 完全不需要这些。 容器共享您的 VPS 内核,从不接触 /dev/kvm。如果真正的目标是"在我的服务器上用容器运行若干服务",那么您已经具备了所需的一切。只有当您想要一个第二内核时,嵌套才有意义,比如一个 Proxmox 实验环境、一个 Windows guest、Firecracker microVM、一个 Android 模拟器、一个由真实 VM 组成的 Kubernetes 测试床,或者需要引导 VM 镜像的 CI runner。

到底嵌套的是什么

三个层次:

  • L0 — 服务商的 hypervisor,运行在物理机上。您对它没有任何访问权限。
  • L1 — 您的 VPS。对 L0 来说,它只是一个 guest。
  • L2 — 您想在 VPS 内部运行的那台 VM。

硬件虚拟化在 Intel 上是 VT-x(vmx 标志)加 EPT,在 AMD 上是 AMD-V / SVM(svm)加 RVI/NPT。hypervisor 利用这些指令进入 guest 模式,并让 CPU 一次遍历两级页表。

两者都不是为可重入而设计的,所以嵌套是模拟出来的:当 L1 执行一条 VMX 指令时,它会陷入(trap)到 L0,由 L0 代替 L1 维护 L2 的影子结构。KVM 把这件事做得很好,但这是 L0 在每一次退出(exit)时都要做的额外工作,这正是服务商必须主动开启它的原因。

要获得一个带加速的 L2,必须同时满足两个条件:

  1. L0 的 KVM 模块以 nested=1 加载。
  2. L0 给您的 VPS 提供一个带有该标志的 CPU 型号,即 libvirt 中的 <cpu mode='host-passthrough'/>、Proxmox 中的 cpu: host、原始 QEMU 中的 -cpu host。一个通用的模拟型号(qemu64kvm64)即使在全局开启嵌套时也会把 vmx 隐藏掉。

一分钟检查您的 VPS

# 1. Are you in a VM, and under what?
systemd-detect-virt          # kvm, vmware, xen, microsoft, or "none" on metal

# 2. Does the CPU expose the extensions to you?
grep -o -E 'vmx|svm' /proc/cpuinfo | sort -u
lscpu | grep -i -E 'virtual|hypervisor'

# 3. The definitive check
sudo apt update && sudo apt install -y cpu-checker
kvm-ok

# 4. The device node the whole stack depends on
ls -l /dev/kvm

一个可用的实例会打印出 vmxsvmkvm-ok 会说 KVM acceleration can be used,并且 /dev/kvmroot:kvm、权限 660 的形式存在。如果标志在但设备节点不在,就手动加载模块并查看内核日志:

sudo modprobe kvm_intel     # or kvm_amd
sudo dmesg | tail -n 20

有一个文件被反复引用,也被广泛误读:

cat /sys/module/kvm_intel/parameters/nested   # Y or N

在您的 VPS 内部,这是您自己的 KVM 模块的设置,它决定的是一个 L2 guest 能否再嵌套出第三层。它完全不能说明 L0 是否为您开启了嵌套,这个问题由 /proc/cpuinfokvm-ok 来回答。nested 参数是您在一台完全归自己所有的机器上才去设置的旋钮:

echo 'options kvm_intel nested=1' | sudo tee /etc/modprobe.d/kvm-nested.conf
sudo modprobe -r kvm_intel && sudo modprobe kvm_intel

在有 VM 正在运行时,模块无法被卸载,所以请先关闭 guest。

为什么大多数 VPS 主机商不开启它

  • 热迁移(Live migration)。vmx 交给您,意味着要暴露一个带有该标志的 CPU 型号,而一个依赖这些 CPU 特性的 guest 无法被安全地迁移到一台 CPU 缺少这些特性的机器上。一家靠迁移客户来腾空节点的主机商,一旦开启嵌套就失去了这种能力。
  • 攻击面。 嵌套的 VMX/SVM 代码路径是内核虚拟化层中最错综复杂的代码之一,并有着与之相称的 CVE 历史。
  • L0 可能不是 KVM。 如果 systemd-detect-virt 打印出 vmwarexenmicrosoft,那么嵌套的规则就属于那套栈,而不是 KVM 的。

您的实例上没有这个标志?可以联系技术支持(有些会按单台 VM 开启)、选择一个明确写明支持嵌套的套餐,或者换用一台独立服务器。本文后续内容都假定您在一台显示出该标志的机器上拥有 root 权限。

用 libvirt 运行一个 L2 guest

sudo apt install -y qemu-system-x86 libvirt-daemon-system virtinst ovmf
sudo systemctl enable --now libvirtd
sudo usermod -aG libvirt,kvm "$USER"   # log out and back in

virt-install \
  --name guest1 \
  --memory 2048 \
  --vcpus 2 \
  --cpu host-passthrough \
  --disk path=/var/lib/libvirt/images/guest1.qcow2,size=20,format=qcow2,bus=virtio \
  --network network=default,model=virtio \
  --os-variant debian13 \
  --location https://deb.debian.org/debian/dists/trixie/main/installer-amd64/ \
  --graphics none \
  --console pty,target_type=serial \
  --extra-args 'console=ttyS0,115200n8'

不需要图形界面。串口安装会运行一段时间,所以请在一个持久的 shell 中启动它:同样是那套让 Claude Code 会话在 VPS 上保持存活的 tmux 工作流,能让 virt-install 控制台在 SSH 连接掉线后仍保持挂载。如果 --os-variant debian13 被拒绝,说明您的 osinfo-db 早于该版本发布,请运行 osinfo-query os 并挑选一个确实存在的名字。--cpu host-passthrough 会把 vmx 继续向下转发进 L2,仅当 L2 需要再做一层虚拟化时才需要它。用 virsh autostart guest1 让 guest 能安全地随机器启动。

磁盘和网卡上的 virtio 总线不是装饰:模拟的 IDE 和 e1000 设备陷入 hypervisor 的次数远远多于 virtio 队列,而在嵌套之下,每一次陷入都要付出两倍代价。

网络:教程略过的那部分

您的 VPS 只有一个公网 IP,并且处在一套会过滤未知 MAC 地址的网络结构之后。由此带来两个后果。

把 L2 guest 桥接到公网通常行不通。 在公网网卡上放一个 br0,给 guest 分配它自己的 MAC,您会看到 ARP 发出去却什么都收不回来,因为服务商的交换机会丢弃来自一个从未租给您的 MAC 的帧。如果这就是您的症状,别再调试网桥了;这就是背后的机制。

改用 NAT 网络。 libvirt 自带 defaultvirbr0192.168.122.0/24、dnsmasq 租约,出站流量立刻就能用。至于入站,请在 L1 上终结 TLS 再把流量代理进去,下面的证书路径来自用 Certbot 在 Nginx 上签发 Let's Encrypt 证书

server {
    listen 443 ssl;
    server_name lab.example.com;

    ssl_certificate     /etc/letsencrypt/live/lab.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/lab.example.com/privkey.pem;

    location / {
        proxy_pass http://192.168.122.50:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

先给 guest 一个静态租约(virsh net-edit default),这样那条 proxy_pass 里的地址才不会变。

管理界面要远离互联网:5900 端口上的 VNC 和 8006 端口上的 Proxmox 网页界面都应放在 loopback 上,通过 SSH 隧道(ssh -N -L 8006:127.0.0.1:8006 you@your-vps)或一条自建接入 VPS 的 WireGuard VPN来访问,后者把整个 192.168.122.0/24 guest 网段拉到一个私有跳数之外。让防火墙保持狭窄:sudo ufw allow 22,80,443/tcp,别的都不开。如果 guest 在您开启 ufw 之后立刻失去了出站连接,通常的元凶是 /etc/default/ufw 中的 DEFAULT_FORWARD_POLICY="DROP",把它设为 ACCEPT 并重新加载 ufw。

在 VPS 上运行 Proxmox

Proxmox VE 9 底层就是 Debian 13,所以只要添加 pve-no-subscription 仓库和 proxmox-ve 软件包,它就能装到一台 Debian VPS 上。请从 Proxmox 自己当前的官方文档中获取仓库和 keyring 那几行,从一篇旧博客里复制来的 URL 会让安装失败。

软件包不是难点。Proxmox 期望 vmbr0 桥接到一块物理网卡,而这正好一头撞进上面那个 MAC 过滤的死胡同。在 VPS 上行得通的形态是:一个 NAT 或路由式、不挂任何物理端口的 vmbr0,guest 位于一个私有网段,再由主机上的 DNAT 规则或反向代理来暴露任何对外服务。如果对外的服务是容器而不是 VM,用一个 Docker Compose 文件让 Traefik 为多个应用提供前端能完成同样的路由工作,还带自动证书。请先给 /etc/network/interfaces 拍个快照:一份糟糕的网桥定义会把您锁在一台可能连控制台都没有的机器之外。

老实说说性能

嵌套比单层要慢,而其机制是具体的、而非弥漫性的:开销不落在内存访问上,而是落在退出(exit)上。 在有 EPT/NPT 的情况下,L0 为 L2 维护影子页表,普通的内存读取以硬件速度运行。变得昂贵的是每一次离开 guest 模式的操作,即 I/O、定时器中断、MMIO、处理器间中断,因为一次 L2 退出由 L0 处理,还可能被反射回 L1。对已经在内存中的数据做 CPU 密集型运算,表现接近原生;而任何以系统调用、网络包和磁盘 I/O 为主的工作都会感受到这些层次。

所以:处处使用 virtio 设备。而且您的 qcow2 文件位于一块服务商已经虚拟化过的磁盘上,两层精简置备叠在一起,此时对 guest 磁盘使用 cache=none 能避免同样的数据块同时躺在两个页缓存里。这里不给基准测试数字:请在您自己的实例上测量您自己的工作负载。

故障模式,以及您会看到的字符串

kvm-ok 报出 INFO: /dev/kvm does not exist / KVM acceleration can NOT be used 要么是模块没加载,要么是标志没暴露。先检查 /proc/cpuinfo

dmesg 中的 kvm: disabled by bios 在物理机上,去固件里打开 VT-x/SVM 开关。在 VPS 内部,它意味着 L0 没有把扩展交给您,您在 guest 里敲什么都改变不了。

modprobe: ERROR: could not insert 'kvm_intel': Operation not supported 您内核看到的 CPU 没有 vmx,这同样是一个 L0 的决定。

Could not access KVM kernel module: Permission denied 是权限问题,不是硬件问题。ls -l /dev/kvm 应显示属组 kvm、权限 660;把自己加入该组,然后开一个全新的登录 shell,因为组成员身份不会应用到一个已经在运行的会话上。

QEMU 启动时报 kvm: Device or resource busy 另一个 hypervisor 模块占用了 CPU:运行 lsmod,看看有没有 vboxdrv 或 VMware 的模块和 kvm_intel 并存,卸载掉您不想要的那个。

virsh/var/run/libvirt/libvirt-sock: No such file or directory 守护进程没在运行:sudo systemctl enable --now libvirtd

Proxmox:KVM virtualisation configured, but not available. 某个 guest 在一台无法提供 KVM 加速的主机上勾选了 KVM 加速。修好嵌套,或者取消勾选并接受纯模拟。

Android 模拟器:x86_64 emulation currently requires hardware acceleration! 又是 /dev/kvm,通常是属组那种情况。

完全没有报错,但一切都慢如冰川。 没有加速器标志的 QEMU 会退回到 TCG,也就是它的软件模拟器。它是正确的,但很慢,一次以秒计的启动会变成以分钟计。请显式传入 -accel kvm,让 QEMU 在无法加速时直接报错停下,而不是悄悄地去做模拟。

一个 guest 在运行中途消失了。dmesg 里找 Out of memory: Killed process ... qemu-system-x86_64。一个 L2 guest 就是 L1 上的一个进程,OOM killer 会像对待任何其他进程一样对待它。L2 的 RAM 来自 L1 固定的分配额,无法向主机借用。

运维:备份、升级、限制

备份。 复制一台正在运行的 guest 的 qcow2 会得到一个损坏的镜像。要么 virsh shutdown guest1 之后再复制,要么做一次外部快照(virsh snapshot-create-as guest1 snap1 --disk-only --atomic),让写入转向一个 overlay,同时您去复制那个现在已经静止的基底,之后再用 virsh blockcommit 把它合并回去。把副本运出 VPS,一份和数据放在同一块磁盘上的快照什么都保护不了。

升级。 apt full-upgrade 会装上新的 kvm_intel/kvm_amd 模块,但正在运行的内核会一直用着旧模块,直到您重启。请保留上一个内核,并在每次内核变更后重新运行 kvm-ok:一台重启后不再带 vmx 的主机,此时离恢复正常只差选对一个启动项。

它在哪里不再扩展得下去。 只有一个公网 IP,意味着每一个 L2 服务都要通过 L1 上的代理或一条 DNAT 规则才能触达外界。热迁移根本不在选项之内。在 CPU 争用之下,嵌套的退出路径是最先感受到压力的地方。而一台带着好几个 guest 的 hypervisor,是一台您的 RAM 已经花光了的机器,嵌套 VM 无法靠超额分配从一个固定的分配额里挣脱出来。当一个实验环境成长到超出这一点时,答案不是叠一个更高的嵌套栈,而是一台您自己就是 L0、本文这一切都不再适用的独立服务器。

FAQ

在 VPS 上运行 Docker 需要嵌套虚拟化吗?

不需要。容器共享您的 VPS 内核,从不打开 /dev/kvm,所以一台没有 vmxsvm 标志的普通实例也能正常运行 Docker 和 Docker Compose。只有当您想要一个第二内核时,嵌套才有意义:一个 Proxmox 实验环境、一个 Windows guest、Firecracker microVM、一个 Android 模拟器,或者需要引导 VM 镜像的 CI runner。

我怎么检查我的 VPS 是否支持嵌套虚拟化?

运行 grep -o -E 'vmx|svm' /proc/cpuinfo | sort -u,然后运行来自 cpu-checker 软件包的 kvm-ok。一个可用的实例会打印出 vmx(Intel)或 svm(AMD),kvm-ok 会报告 KVM acceleration can be used,并且 /dev/kvm 存在、属组为 kvm、权限为 660。对这个问题请忽略 /sys/module/kvm_intel/parameters/nested,那个文件描述的是您自己的 KVM 模块,而不是服务商的 hypervisor 暴露给您的东西。

为什么大多数 VPS 服务商禁用嵌套虚拟化?

暴露 vmx 意味着把一个带有该标志的 CPU 型号交给 guest,而一个依赖这些 CPU 特性的 guest 无法被热迁移到一台 CPU 缺少这些特性的机器上,一家靠四处移动客户来腾空节点的服务商会因此失去这种能力。嵌套的 VMX/SVM 代码路径还背着一长串 CVE 历史。有些主机商仍会按请求为单台 VM 开启它,另一些则把嵌套作为套餐特性写进文档。

我的嵌套 VM 在公网网桥上没有网络。哪里出了问题?

服务商的交换机会丢弃来自一个从未租给您的 MAC 地址的帧,所以一个桥接到公网网卡的 L2 guest 发出 ARP 却听不到任何回应。别再调试 br0 了,改用 libvirt 的 NAT default 网络(virbr0192.168.122.0/24),给 guest 一个静态租约,任何对外的东西都通过 VPS 本身上的反向代理或 DNAT 规则来发布。

一台嵌套 VM 会慢多少?

开销落在 VM 退出上,而不是内存访问上。在 EPT/NPT 生效时,L2 内部普通的读写以硬件速度运行,而 I/O、定时器中断、MMIO 和 IPI 由 L0 处理,并且可能被弹回 L1。对已经在内存中的数据做 CPU 密集型运算,表现接近原生;以系统调用、网络包和磁盘为主的工作负载则会感受到每一层。请处处使用 virtio 设备、对 guest 磁盘用 cache=none,然后测量您自己的工作负载。

#nested-virtualization#kvm#proxmox#vps#qemu#libvirt