一、为什么要在一台机器上跑虚拟机
大多数个人站长的情况是:手上有一台还算不错的独立服务器或者高配 VPS,八核十六线程、64G 内存、两块 SSD。但实际只跑了一个网站,CPU 长期闲置在 5% 以下,内存也用不到四分之一。想再跑点别的东西比如测试环境、监控、内网服务,又怕影响主站,于是要么再买一台机器,要么就这么浪费着。
在这类场景下,在宿主机上装一个虚拟化层是性价比很高的一步。它的好处不是「省机器钱」这么简单,而是把「一台机器」变成「几个互不干扰的故障域」:测试环境搞崩了不影响主站,监控服务升级失败只影响它自己,某个虚拟机的内核出问题也不会把宿主机拖死。同时资源还是共享的,磁盘空间和带宽都不会闲着。
本文只讲 Proxmox VE(简称 PVE)这条路线,因为它是目前个人和小团队环境下最省心的选择:基于 Debian,内置 Web 管理界面,KVM 和 LXC 都支持,快照和备份开箱即用,社区文档也足够多。下面说的内容都是从「装完什么都不懂」到「能稳定跑两年」之间真正会卡住的地方。
二、装之前的三个决定
第一个决定是文件系统。PVE 安装器默认给 ext4 或 LVM,另一个选项是 ZFS。如果机器只有一块盘,选 ext4 就好,简单、省内存、性能稳定。如果有两块以上盘,ZFS 的镜像或者 RAIDZ 能带来数据校验能力,同时快照是零成本的,回滚一个虚拟机的状态只需要几秒。代价是 ZFS 对内存的占用:默认 ARC 会用掉可用内存的一半,在 16G 内存的机器上,这会显得很紧张,需要在 /etc/modprobe.d/zfs.conf 里限制上限。
# 限制 ZFS ARC 最大 4G,并立即生效
echo "options zfs zfs_arc_max=4294967296" > /etc/modprobe.d/zfs.conf
echo 4294967296 > /sys/module/zfs/parameters/zfs_arc_max
# 查看当前 ARC 使用与命中率
arc_summary | head -30第二个决定是网络桥接方式。PVE 安装完成后会创建一个叫 vmbr0 的网桥,默认绑定到第一块物理网卡,虚拟机的虚拟网卡桥在这个桥下,才能分配宿主机网段的 IP。这里最常见的错误是物理网卡写错名字,在较新的 Debian 上,网卡名可能是 enp4s0 而不是 eth0,如果安装时选错,装完之后宿主机就断网,只能靠本地控制台救回来。
第三个决定是是否用单独的管理网段。如果机器有多块网卡、且有内网交换机,建议把管理口和业务口分开:管理口只允许从固定 IP 访问 PVE 的 8006 端口,业务口跑虚拟机的流量。没有这个条件的,至少要把 8006 端口限制在可信 IP 范围内,这台机器的管理界面直接暴露在公网上等于把 root 权限挂出来。
三、创建第一台虚拟机时的真实参数
PVE 的创建向导里有一堆参数,重要的其实只有几个。CPU 类型建议选 host,这样虚拟机能用到宿主机的全部指令集(比如 AES-NI、AVX2),比默认的 kvm64 快不少,尤其是跑数据库或者压缩类负载时差别明显。代价是不能在不同代的 CPU 之间迁移,但个人场景下这根本不是问题。
磁盘总线选 VirtIO Block,不要选 IDE 或 SATA。VirtIO 是半虚拟化驱动,在 Linux 客户机里是原生支持的,性能接近物理盘;IDE 是纯模拟,吞吐会被砍掉一大截。网络设备同理,选 VirtIO (paravirtualized),并且在客户机里确认网卡名是 ens18 之类的 VirtIO 设备,而不是 e1000。
# 客户机内确认是否是 VirtIO 驱动
lspci | grep -i -E 'virtio|ethernet'
# 期望看到 Red Hat, Inc. Virtio network device
# 确认磁盘调度与是否启用了 discard(配合宿主机 thin 存储)
lsblk -o NAME,ROTA,DISC-GRAN
cat /sys/block/vda/queue/discard_granularity还有一个很容易被忽略的选项:Discard。如果虚拟机的磁盘建在 LVM-thin 或者 ZFS 上,开启 discard 之后,客户机里删除文件会把空间真正还给宿主机。不开启的话,客户机删掉 20G 日志,宿主机上占用的空间一点都不会少,最后磁盘满了还找不到是谁占的。在 PVE 界面里勾选「Discard」,或者在 /etc/pve/qemu-server/100.conf 里给磁盘加上 ,discard=on。
四、内存超卖与 balloon 的真实边界
PVE 默认给每台虚拟机分配固定内存,宿主机内存加起来不能超过物理内存。如果手上只有 32G 内存,想跑四台 8G 的机器,就刚好卡满,宿主机自己还要留出 2G 到 4G 给系统,否则一开容器宿主机会先 OOM。
把 balloon 打开可以让虚拟机动态让出内存:客户机里的驱动检测到自己空闲时,会把内存页还给宿主机,宿主机再分给别的虚拟机。这听起来很美好,但在实际使用中要谨慎。第一,客户机必须安装 virtio_balloon 驱动,大多数发行版默认就有,但没有的话 balloon 完全不生效。第二,内存被回收时,如果客户机正在用,会触发它自己 swap,性能会抖得很厉害。第三,数据库、缓存这类对内存敏感的服务,不适合开 balloon,因为它们的峰值内存波动很大,被回收一次就要重新读盘。
更实际的建议是:不要指望超卖能省下多少,把内存当作不能共享的资源来规划,宁可贵一点买大内存。用 balloon 只在「临时跑一个测试机」这类场景上,跑完就删。宿主机上始终留 10% 到 15% 内存余量,这是保证不发生全站雪崩的底线。
五、备份与快照:PVE 的两个不同东西
快照和备份经常被混为一谈,但它们的用途完全相反。快照是「点的状态」,存在同一个存储上,用来做「等会儿可能要把这次改动回滚」的风险对冲,比如升级一个服务之前打一个快照,升级失败了一键回滚。备份是「完整的一份副本」,应该存在另一台机器或者对象存储上,用来应对磁盘坏了、宿主物理机没了这种级别的事故。
关键点:快照不能替代备份。如果 ZFS 池坏了,池上的快照跟着一起没。这在现实中发生过很多次,站长以为「我每天都打快照」,结果宿主机磁盘故障,什么都没剩下来。
# 命令行打快照(VM 100,名字 pre-upgrade)
qm snapshot 100 pre-upgrade --description "before nginx upgrade"
# 列出快照
qm listsnapshot 100
# 回滚
qm rollback 100 pre-upgrade
# 删除
qm delsnapshot 100 pre-upgrade
# 一次性备份到 NFS 存储( vzdump,保留 7 份)
vzdump 100 --storage backup-nfs --mode snapshot --compress zstd \
--prune-backups keep-last=7--mode snapshot 的意思是先给虚拟机打一个临时快照,然后在快照上做备份,这样备份期间虚拟机可以继续运行、数据是一致的。这是最常用的模式,代价是需要一定的额外空间。PVE 界面里配置好备份任务后,会自动按计划执行,记得把「保留策略」设上,否则备份会无限增长把存储撑满。
六、宿主机与客户机的分工
一个常见的疑问是:主站应该放在宿主机上还是放进虚拟机?稳妥的答案是放进虚拟机。理由有三个:第一,宿主机的内核和 PVE 本身需要升级时,重启宿主机不影响其它虚拟机的话最好,但如果主站在宿主机上,每次内核更新就是一次全站停机;第二,虚拟机可以随时迁移到另一台宿主机(前提是共享存储),宿主机出硬件问题时停机时间能压到分钟级;第三,宿主机上只装虚拟化必需的东西,攻击面最小。
宿主机本身该装的只有:PVE 基础包、必要的监控 agent、fail2ban 之类的基础防护。不要装 Web 服务器、数据库、Docker。如果为了图方便在宿主机上跑了一个服务,三年后你会在升级 PVE 大版本时发现它卡住了整个升级流程。
客户机里则按用途拆:一台放 Web 和 PHP,一台放数据库,一台做监控和备份。小规模站甚至可以把 Web 和数据库放同一台虚拟机,但一定要把监控单独放一台,因为监控的价值就是在「主站挂了」的时候还能工作。
七、网络与防火墙的具体做法
PVE 的防火墙有两层:宿主机的 pve-firewall,以及每个虚拟机的独立规则。用起来很容易混乱,所以建议只在宿主机层面做一个最简单的默认策略:入站全拒绝,只放行 SSH 和 PVE 管理端口,且限制源 IP。虚拟机的防火墙则用客户机自己的 iptables 或 ufw,谁的服务谁负责,出了问题好排查。
# 宿主机只允许可信 IP 访问 8006 和 22
cat >> /etc/pve/nodes/$(hostname)/host.fw << 'EOF'
[OPTIONS]
enable: 1
[RULES]
IN ACCEPT -source 203.0.113.0/24 -p tcp -dport 8006
IN ACCEPT -source 203.0.113.0/24 -p tcp -dport 22
IN DROP
# 应用
pve-firewall compile
pve-firewall restart如果站点要对外提供 HTTPS,必须想清楚 80 和 443 由谁接。推荐的做法是宿主机跑一个轻量反向代理(Nginx 或者 HAProxy),把请求按域名分发到不同虚拟机,证书只在宿主机上维护一份。这样加一个新站点不用再申请一次证书,也方便统一做限流和封禁。缺点是宿主机上多了一个服务,所以要格外注意它的配置变更要先在测试机上验证。
八、真实会遇到的问题
第一个,虚拟机时间漂移。KVM 客户机默认会同步宿主机时间,但如果宿主机自己没配好 NTP,虚拟机会跟着一起漂。PVE 宿主机务必装 chrony 或者 systemd-timesyncd 并确认同步正常,客户机里也装一个,双保险。
第二个,磁盘 IO 争抢。所有虚拟机共享同一块物理盘,某一台在跑 rsync 全量备份时,其它虚拟机的响应时间会明显上升。iostat -x 1 看 %util 和 await,如果 await 经常超过 20 毫秒,就要考虑把 IO 密集的虚拟机挪到另一块盘上,或者在宿主机上给虚拟磁盘加 IOPS 限制。
第三个,宿主机重启顺序。如果虚拟机设置了开机自启,宿主机重启时会依次拉起所有虚拟机,这时候磁盘和网络还没稳定,虚拟机可能出现各种奇怪的报错。解决办法是在虚拟机选项里设置 startup 顺序和延迟,比如数据库先起、等三十秒再起 Web。
第四个,升级 PVE 大版本前一定要先看官方升级指引。PVE 8 到 9 这类跨越往往涉及 Debian 大版本变化,需要先按顺序改 sources.list、执行 apt dist-upgrade、再跑 pve8to9 检查脚本。跳过检查脚本直接升级,最常见的后果是某个虚拟机起不来。
九、小结
在宿主机上做虚拟化,本质上是用「多一层管理」换「更好的隔离和弹性」。对个人站长来说,它的最大价值不是省钱,而是让你可以在同一台机器上安全地做实验,而不用担心把主站搞挂。做好三件事:存储分层要清楚、内存不要硬超卖、备份一定要有一份不在这台机器上的。剩下的细节,跑上半年自然会熟悉。