上一篇把旧笔记本装成了 Proxmox VE,之后一个多月陆陆续续建了不少东西,删了又建,建了又删。刚开始我的原则很简单:能用 LXC 就用 LXC,毕竟省资源。后来踩了几次坑,才慢慢形成自己的判断标准,记录一下。
本质区别
- LXC 容器和宿主机共用同一个内核,本质上是一组被隔离起来的进程。启动只要一两秒,内存按实际用量算,一个跑 Debian 的空容器只占几十 MB。
- 虚拟机(KVM)有自己完整的内核和虚拟硬件,隔离更彻底,可以装任何系统,代价是要预留内存,启动也慢一些。
由此带来的直接后果是:容器里不能加载内核模块,很多内核参数也改不了,宿主机升级了内核,容器里的“内核”也跟着变;而虚拟机里怎么折腾都影响不到宿主机。
用命令行创建
网页上点几下就能建,不过我更喜欢用命令行,方便记录,也方便以后重复。先下载容器模板,再创建一个非特权容器:
pveam update
pveam available --section system | grep debian-12
# 模板的版本号以上一条命令的输出为准
pveam download local debian-12-standard_12.7-1_amd64.tar.zst
pct create 101 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname test-ct \
--cores 1 --memory 512 --swap 512 \
--rootfs local-lvm:8 \
--net0 name=eth0,bridge=vmbr0,ip=192.168.10.101/24,gw=192.168.10.1 \
--unprivileged 1 \
--features nesting=1 \
--timezone host \
--onboot 1
pct start 101
pct enter 101 # 直接进入容器的 shell,先用 passwd 设个密码
nesting=1 建议打开,新版本的 systemd 在容器里需要它,网页上建非特权容器时这一项默认就是勾上的。
虚拟机也一样,下面是我给 Docker 准备的那台:
qm create 200 --name docker \
--ostype l26 --machine q35 --cpu host \
--cores 2 --memory 6144 --balloon 2048 \
--scsihw virtio-scsi-single \
--scsi0 local-lvm:64,iothread=1,discard=on \
--net0 virtio,bridge=vmbr0 \
--ide2 local:iso/debian-12.7.0-amd64-netinst.iso,media=cdrom \
--boot 'order=scsi0;ide2' \
--agent enabled=1
qm start 200
装好系统后记得在虚拟机里 apt install qemu-guest-agent 并启动它,这样 PVE 才能拿到虚拟机的 IP,关机时也能让虚拟机正常关机。--cpu host 把宿主机的 CPU 特性直接暴露给虚拟机,单机用没问题,以后如果要在不同型号的机器之间迁移再另说。
我现在的选择标准
- 单一用途、轻量的服务,比如 DNS、一个小网页、一个同步服务:用非特权 LXC。资源占用小,备份恢复都快。
- Docker:放虚拟机。Proxmox 的官方文档也建议把 Docker 这类应用容器跑在虚拟机里。在 LXC 里跑 Docker 不是不行,但要开 nesting,存储驱动、AppArmor 时不时出问题,宿主机一升级说不定又坏了。我一开始在容器里跑过一阵,最后还是挪进了虚拟机。
- 需要特定内核、内核模块,或者干脆不是 Linux:只能虚拟机。
- 需要整块设备直通,比如一张 PCIe 网卡或者一整个硬盘控制器:虚拟机。
拿不准的时候,我会问自己一个问题:这个服务挂了或者被攻破,会不会波及宿主机?如果担心,就放虚拟机。
踩过的坑
1. 非特权容器的挂载权限。 非特权容器里的 UID 是映射过的:容器里的 root(UID 0)在宿主机上其实是 100000,容器里的 1000 对应宿主机的 101000。把宿主机目录 bind mount 进去之后,容器里看到的文件全属于 nobody,写也写不进去。最简单的办法是在宿主机上把目录的属主改成映射后的 UID:
# 在宿主机上执行
mkdir -p /srv/share
chown -R 101000:101000 /srv/share # 对应容器里 UID 1000 的用户
pct set 101 -mp0 /srv/share,mp=/mnt/share
pct reboot 101
更精细的做法是改 /etc/subuid、/etc/subgid 和容器配置里的 lxc.idmap,把某个 UID 原样映射进去,不过对家用来说上面的办法就够了。
2. 非特权容器里挂不了 NFS/SMB。 想在容器里直接 mount 网络共享是不行的,只能先在宿主机上挂好,再像上面那样 bind mount 进容器。
3. bind mount 不会被备份。 PVE 自带的 vzdump 对容器和虚拟机都能备份,但容器的 bind mount 挂载点是不参与备份的,只有建在 PVE 存储上的卷才会。我一开始以为备份了整个容器就万事大吉,后来看备份文件的大小才发现不对。
4. 容器里的时区。 模板默认是 UTC,日志里的时间总是差八个小时。创建时加上 --timezone host 就会跟宿主机保持一致,已经建好的容器可以用 pct set 101 --timezone host 补上。
小结
LXC 和虚拟机不是二选一的关系,混着用才是常态。我现在的布局是:一台虚拟机专门跑 Docker,承载大部分服务;几个轻量的、跟基础设施有关的东西各自放一个小 LXC。资源最紧张的永远是内存,16G 分下来其实很快就见底了,下一步可能要考虑加条内存,或者干脆再添一台机器。