我与 Proxmox VE 9.0 那些不得不说的故事
从23年年初开始,笔者就一直有在运行自己的NAS,平时拿来存点数据,每年停机清灰一次,倒也没啥太大的问题,一直安安稳稳过日子。
这玩意说是说NAS,实际上更加倾向于是『不小心多插了几块硬盘』的Home Server,正好最近又到了年度计划清灰的时候,手头还有一点余钱,于是想着给机器提一下性能,然后尝试一些新东西,也就是今天的主角,Proxmox VE(简称PVE)

1. 前言
这台NAS的前身其实是笔者的前主力机,也就是家里曾经的台式机。在笔者有了自己的笔记本(详见上一篇文章)之后,再加上一直在寄宿制学校求学的原因,曾经的台式机就在父母房间吃灰了很多年。后来在22年因故回家之后,就有了相对较多的时间(和积蓄),来折腾这玩意。
一开始,机器还是用原来的板U和内存,也就是AMD Athlon II + 4GB DDR2:

在去年年度清灰的时候,正好碰上友人出二手板U,于是花点钱买了下来,顺便买了些新内存,于是新配置就变成了:
- QQLS(魔改U,本质上是i9-9980HK)
- 16G DDR4

主板上依然是6个SATA接口,不过这次没那么多PCIe接口了而已,幸而自己也用不上那么多PCIe,因此很乐意装进去用,进一步提升机器的性能,好歹比速龙II要好多了,甚至似乎比之前的笔记本性能还要好不少。
当时给机器装的系统是Debian 12,裸机安装,装在一块128G的SSD上,一开始用着倒是还行,但是随着笔者继续到外面求学,一个问题很快浮上了水面:家里没人懂怎么操作这台机器,换句话说,一旦不小心把系统弄炸了,就只能找一个节假日去抢高铁票坐车回来亲自修,这一来一回又得折腾很多时间。或曰怎么不用IP-KVM之类的设备,道理也很简单,系统炸掉了之后所有服务都死翘翘了,包括跑在上面的SMB/WebDAV,也就意味着我在远端的所有工作流会直接中断(为了保险起见,家里网络是用单独机器跑的),我必须在短时间内把他修好,这样不至于影响日常使用。
于是一来一回之下,目光很自然就看向了虚拟机上:不论干啥都是在虚拟机内操作,基本甚至完全不影响宿主机,即使玩炸了,也可以很轻松地通过VNC进去修,或者先放着以后再修,或者直接销机重来一次。与此同时其他服务不受影响,我可以一边继续听着硬盘上82年的盗版音乐一边修机器。甚至还可以在机器没坏的时候,直接备份整台虚拟机,玩炸了就把备份恢复起来,继续工作。
2. 为什么是PVE
基于上面的想法,其实老早之前,笔者就已经有『给NAS安装Hypervisor』的想法了,因为机器上跑的是Linux,一个很自然的想法就是使用KVM,这样可以在保持不换系统不动太多底层的情况下,让机器具有开虚拟机的能力。但是KVM原生的管理方式是命令行,很多情况下显得不太方便(确实有Virt-manager这种东西的存在,但是NAS为了减少资源占用,并没有安装GUI,而X11转发之类的方案天生不适合在高延迟网络上使用),因此笔者比较倾向于使用WebUI类的方案。
最开始,笔者选择的是WebVirtCloud,一开始确实觉得不错,但是在开了几个虚拟机之后,还是发现了这个方案的两个问题:
- KVM是手动安装的,和WebVirtCloud配合并不算太好,不是说不能用,而是在细节问题上总是存在一些磕磕绊绊的地方
- KVM开虚拟机是很好,但对于某些『轻量』任务来说,却显得杀鸡用牛刀。比如装Debian 12,如果一开始就分配512MiB内存,安装程序会直接进入低内存模式,说明系统也觉得这个程度的内存有些太少了——但是从另一个角度来看,512MiB就是0.5GiB,多开几个VM就能把宿主机内存吃差不多了,怎么看怎么不太适合
机缘巧合之下,笔者了解到了PVE所支持的另一种虚拟化技术,LXC。关于LXC是什么,网上资料多,这里不再赘述,但总之就是,这种轻量化,开机快,占用低的虚拟环境,对于笔者这种不需要太高隔离度且Linux-only的应用场景来说,简直是完美适配——而且这就是笔者选择PVE而不是学校里面教的ESXi的主要原因:ESXi不直接支持LXC(当然另一个原因是ESXi授权费的问题,虽然有开心版,以及后来面向个人用户的免费版,但总觉得不太适应,PVE就挺好)
3. 硬件升级
前两天,正好到了今年的年度停机清灰,于是趁着这个机会,提前买了要添加的零件,给机器升级了一番

目前这台NAS的主要配置变成了:
- CPU不变,还是QQLS
- 内存:2x16G DDR4
- 宿主机系统盘:128GB SSD
- 虚拟机系统盘:512GB SSD
- NAS数据盘:3+3+4+4 = 14TB(无RAID)
除此之外就没有什么太大变动了,清完灰,擦拭干净,把机器复原,然后把机器扛回去房间,开始装系统。
4. 磕磕绊绊的PVE之路
这里开始就是正文,如果不出意外的话,就应该出意外了。
本次安装的PVE版本是9.0,基于Debian 13,算是最新的一版了。
4.1 小心Ventoy
Ventoy是个好东西,只需要把ISO放进去,就能直接引导起来。笔者手头有预先做好的Ventoy盘,于是把PVE镜像拷进去盘里面,开装。
这里只能拍屏了,没办法安装过程倒是没什么幺蛾子,选好启动盘,划分一定空间的SWAP(32G内存,笔者开了16G的SWAP),然后就是设置密码什么的,就坐和放宽等他安装就行。
装好后拔掉启动盘,正式进系统。只见屏幕上闪过
Loading Linux 6.14.8-2-pve ...
Loading inital ramdisk ...然后就卡住了,再也没有动静。
拿着故障原因到谷歌上去找办法,结果找到了一堆老哥碰到类似问题,反正大意是说说是Ventoy会修改一部分设置,导致选不对ramdisk,无法启动(后来知道了如何修改)。在不知道具体搞哪里才能解决问题的情况下,既然知道是Ventoy的锅,笔者只好翻箱倒柜,在之前上学时候背的包里面翻出来一个8GB的USB 2.0 U盘,用Rufus的DD模式重新写一遍镜像进盘了。
写完,插上,引导,进安装,设置启动盘,SWAP,密码....一套折腾下来,终于用纯净的安装盘把系统装上了。
装好后拔掉启动盘,正式进系统,屏幕上依然只是闪过上面的提示,依然卡在了一模一样的位置,依然一动不动。
这就问题大了,因为卡在这个地方,网上资料还提到,可能是因为视频控制器(说白了就是显卡)不兼容引起的,如果是这样的话,那就难以解决了,因为这块主板用的是核显,而且当时还是晚上,短时间内要加装显卡或者更换显卡都不是容易事。
4.2 魔改CPU的痛
一筹莫展之际,发现了Grub菜单里面有高级选项,点进去之后里面有个Recovery Mode的选项,本着死马当活马医的态度,点进去了这个选项。这次,屏幕上Loading inital ramdisk ...也就持续了两三秒的时间,随后就开始大量滚动 OK 开头的启动日志,一看就知道有戏。果然,也就十几秒时间,屏幕再次清空,上面弹出来了PVE关于本机IP的提示,用手机访问了一下上面的IP地址,能正常弹出来PVE的登录界面,这就说明系统已经正常启动。
这就给问题带来了新的突破口:已知Grub菜单的选项是由Grub配置文件决定的,既然Recovery Mode能进系统,说明必然有某些参数影响了加载过程。
于是先把系统重启,在进入Grub界面之后,按e键调出正常启动的配置文件,拍照,返回之后进入高级选项,按e调出恢复模式的配置文件,再次拍照,两张照片一对比,马上发现了端倪:
# 这是正常启动的配置
echo 'Loading Linux 6.14.8-2-pve ...'
linux /boot/vmlinuz-6.14.8-2-pve root=/dev/mapper/pve-root ro quite
# 这是恢复模式的配置
echo 'Loading Linux 6.14.8-2-pve ...'
linux /boot/vmlinuz-6.14.8-2-pve root=/dev/mapper/pve-root ro single dis_ucode_ldrsingle好说,就是用来进恢复模式的,但是这个dis_ucode_ldr是什么呢?上网一查,原来是禁止Linux加载CPU微码,看到这个参数的释义的一瞬间,真相就大白了。
还记得上面提过,这个CPU是魔改CPU吗?事实上,这颗CPU,不论是在BIOS中,还是进系统之后执行lscpu,都是没办法正确读出CPU型号的:

合理怀疑,系统根据CPU ID识别类型的时候,要么误识别并误加载了错误型号的微码,要么是加载的微码把原来BIOS固定注入的(确保魔改U可以正常运行的)特殊微码给冲掉了,导致CPU无法正常工作,自然也就进不去系统。加上dis_ucode_ldr之后,Linux不再尝试注入微码,CPU正常运行。
知道问题所在就很简单了,只需要先在正常启动的配置文件中,添加dis_ucode_ldr选项,即可正常引导。引导后以root身份登录系统,执行以下操作:
nano /etc/default/grub
# 找到 GRUB_CMDLINE_LINUX_DEFAULT= 一行,在后面的引号中加入dis_ucode_ldr
# 并保存文件
# 然后更新Grub,问题即可解决
update-grub自此,费了九牛二虎之力,系统终于开起来了,内牛满面....
开起来之后,就是根据网上的其他教程,设置软件安装源(我是没有换源,只是在APT配置文件中设置了代理,让他走代理下载更新),容器模板源(换了清大源),以及可以执行以下命令屏蔽掉PVE的未订阅提示:
# 开始之前先备份,总没错
cp /usr/share/pve-manager/js/pvemanagerlib.js /usr/share/pve-manager/js/pvemanagerlib.js.bak
cp /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.bak
sed -i_orig "s/data.status === 'Active'/true/g" /usr/share/pve-manager/js/pvemanagerlib.js
sed -i_orig "s/if (res === null || res === undefined || \!res || res/if(/g" /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
sed -i_orig "s/.data.status.toLowerCase() !== 'active'/false/g" /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
systemctl restart pveproxy4.3 正确封装LXC
既然这玩意是NAS,首当其冲的自然就是要建立SMB/WebDAV服务,笔者打算通过LXC解决,轻便快捷。进入PVE的WebUI之后一顿摸索,大致搞明白了如何新建容器,赶紧开个机器尝尝鲜。
因为笔者之前购买过LXC VPS(类似以前的OpenVZ VPS,所以我们都叫他草履虫VPS),知道这玩意内存和硬盘占用都很低,因此下下来模板之后,开容器,输密码填公钥,给容器分配了内网空闲IP,以及128M内存和5GB硬盘(事后发现,配置还是给太高了,有个64M内存和1GB磁盘都可以完美运行),点击创建。果然,日志窗口里面也就弹出来几行日志,大致是说正在解压和生成SSH公钥什么的,一些必要操作,十秒钟不到,容器已经跑起来了——这个效率可比开VM高太多了。
开好容器,用PuTTY去连接,在已经预先配置好用户名和公钥的情况下,只需输入地址就可以直接连接,非常舒服。但是也就舒服了几秒钟,突然发现一个问题:开容器很方便,但是这是一个纯净安装的容器,和实际生产环境有些区别,主要是缺少一些必备工具,比如设置代理,终端字符编码,以及curl、unzip之类的工具。换句话说,每次想要部署什么,都需要手动执行一次完整的安装流程,很麻烦。于是很自然而然想到一个问题:既然开容器是可以用模板开的,那么我能不能打包一个属于自己的模板?
说干就干,查阅了一些资料的之后,在这个网站找到了具体操作流程,根据自己的PVE版本稍作修改之后,完美实现了功能。
4.3.1 获取原模板
在具有『容器模板』属性的存储设备页面中,打开『CT模板』页面,点击模板按钮,然后找一个自己喜欢的发行版,下载下来。
这里以Debian 12为例下载好之后,用这个模板开启一个LXC容器,配置任填,能运行起来就行。开起来之后记住容器ID,假设这里是100。
4.3.2 做你要做的事
进入容器内系统的Shell,执行必要的操作流程。这里就根据个人需求来进行了,笔者所做的事情如下:
# 设置系统代理
echo "export http_proxy=http://代理服务器地址:端口" >> /etc/profile
echo "export https_proxy=http://代理服务器地址:端口" >> /etc/profile
source /etc/profile
# 安装必备软件包
apt update
apt install curl unzip lsof psmisc
# 安装可信公钥,实现免密登录
# 如果追求安全性,也可以不安装
# 在新建容器的时候手动输入别的公约就行
echo "你的公钥" >> ~/.ssh/authorized_keys
# 调整中文编码
dpkg-reconfigure locales
# 然后在里面滚到底,选择zh_CN.UTF-8 UTF-8
# 启用ls的颜色,以及ll等命令
nano ~/.bashrc
# 在里面取消注释掉对应选项
# 修改系统时区为东八区
timedatectl set-timezone Asia/Shanghai
# 关掉Crontab的邮件发送功能
nano /etc/crontab
# 在SHELL=下面添加:MAILTO=""
crontab -e
# 进入用户cron配置文件后,在顶上也添加MAILTO=""
# 清除命令历史记录
history -c输完最后一条命令之后,直接关掉终端窗口。
4.3.3 停机,整理,打包
进入宿主机的Shell,依次执行以下命令:
# 停止容器运行,并删除网络接口,避免模板冲突
pct stop <容器ID>
pct set <容器ID> --delete net0
# 执行打包命令
# 将当前容器打包为模板
vzdump <容器ID> --mode stop --compress zstd --dumpdir /var/lib/vz/template/cache/
# 进入存放模板的地方
cd /var/lib/vz/template/cache/
# 按需删除生成的日志
rm vzdump*.log
# 并把模板重命名为合适的名字(不能有非ASCII字符)
mv vzdump-xxxxxx.tar.zst Your_favourite_name.tar.zst
这样操作完之后,你就可以把刚刚打包用的容器删掉了,原来下载的原版模板也可以删掉了,以后开容器只需要用新准备的模板开,就可以每次都能开到趁手的容器。
4.4 非特权容器
所谓非特权容器,一般指的是内部的ROOT用户映射到外部的非特权高位UID用户,自然是要比特权容器(直接用宿主机ROOT用户)要安全很多,PVE在WebUI上默认选择的也是开非特权容器。
4.4.1 SSH登录慢
开容器的时候,看到有个写着『嵌套』字样的选择框,很好奇取消选择之后会发生什么。开好容器去登录,发现SSH通过鉴权之后就失去了响应,一直等了很久,才成功登入系统。去网上查了一下,原来此故障还真的和嵌套有关,根据PVE论坛的这个帖子,打开嵌套(Nesting)之后,速度就恢复了正常。
进一步查阅资料发现,这个『嵌套』控制的其实是嵌套容器化,打开此功能就允许在容器内继续开容器(例如LXC里面开Docker),否则就不行。其本质上是因为,开启嵌套后,主机的/proc和/sys在容器内挂载为rw权限,容器被授予更多 cgroup 操作权限,可以自行创建子cgroup(当然同时也就引入了一些安全风险)。而对于SSH来说,认证之后的卡顿,背后其实是systemd尝试在/sys/fs/cgroup/user.slice/中新建 session,关掉嵌套,这一步跑不下去,只能反复尝试到超时/最大失败计数,然后才能跑下一步。
虽然就安全性来说,关闭嵌套并通过修改容器内systemd(使其工作在 legacy 模式)的方式可以实现提高安全性的同时登录不卡顿,但注意到,PVE官方的默认操作是,同时勾选『非特权容器』和『嵌套』选项,所以多多少少可以推断,这个搭配是官方推荐(至少也是了解并默认设置)的,对于家庭用户来说,应该不会引入太多问题,所以保持默认就好。
4.4.2 挂载外部磁盘
非特权容器无法直接操作硬件,挂载磁盘就成了一个问题。
上面提到,笔者这个本质上还是一个NAS,所以自然会有装了不少数据的Ext4格式的没有RAID的磁盘。在不是很想把这些磁盘加入PVE存储池的情况下,想要往非特权容器挂载,一个很重要的概念就是让容器内的某个用户有权读写宿主机的某个目录,而这个目录可以是挂载了磁盘的目录。
根据官方Wiki以及笔者找到的第三方资料,进行这一步操作实际上是不难的,但是这些操作暂未在WebUI下实现,因此只能全程命令行。
注意,第三方资料里用的是ZFS存储池,但本质上还是挂载为目录,因此下面就按目录的路径去走了。
4.4.2.1 宿主机磁盘挂载
新建好挂载目录之后,打开/etc/fstab,在里面添加硬盘的挂载信息。
笔者有四块磁盘,为了防止盘符错乱,预先用blkid命令获取了目标分区的UUID,因此下面都是针对UUID进行挂载
UUID=xxxxxx /mnt/HDD ext4 defaults,nofail 0 2
UUID=xxxxxx /mnt/HDD.2 ext4 defaults,nofail 0 2
UUID=xxxxxx /mnt/HDD.3 ext4 defaults,nofail 0 2
UUID=xxxxxx /mnt/HDD.movie ntfs-3g defaults,nofail 0 0保存,然后执行systemctl daemon-reload与mount -a挂载磁盘。
4.4.2.2 宿主机新建用户
要让新建的用户和组有权限读写这些磁盘。注意这里UID都大于10万,这是我们待会要用到的妙妙工具。
# 新建组和用户
# 可能会警告outside of the UID_MIN 1000 and UID_MAX 60000
# 不用管他,不影响
groupadd -g 110000 disk_shares
useradd disk_user -u 101000 -g 110000 -m -s /bin/bash
# 给上面的磁盘目录修改所属组
chown -R disk_user:disk_shares /mnt/HDD
chown -R disk_user:disk_shares /mnt/HDD.2
chown -R disk_user:disk_shares /mnt/HDD.3
chown -R disk_user:disk_shares /mnt/HDD.movie4.4.2.3 容器内新建用户
这里是关键:PVE的LXC会把内部用户的UID加上100000来映射为宿主机用户(所以你执行ps aux可以看到一大堆10万以上的用户进程),所以我们在容器内新建用户时,要故意把UID设置为:加上100000后能和宿主机里刚刚设置的有权用户UID相匹配
打开你要挂载磁盘的容器,执行以下命令:
# 这里的名字随便起,不要求和宿主机的一样
# 也不必和其他容器的一样
# 当然设置为一样也没事
groupadd -g 10000 disk_shares
useradd disk_user -u 1000 -g 10000 -m -s /bin/bash然后把容器关机,并记住容器ID。
4.4.2.4 绑定挂载点
你可能已经在WebUI上容器的管理界面里看到了相关选项,但是那个UI无法实现我们的功能,因此必须继续命令行操作。
打开宿主机Shell,执行:
# 格式为:-mpX /host/dir,mp=/lxc/dir
pct set 容器ID -mp0 /mnt/HDD,mp=/mnt/HDD
pct set 容器ID -mp1 /mnt/HDD.2,mp=/mnt/HDD.2
pct set 容器ID -mp2 /mnt/HDD.3,mp=/mnt/HDD.3
pct set 容器ID -mp3 /mnt/HDD.movie,mp=/mnt/HDD.movie4.4.2.5 检查成果
执行完毕后,启动容器,应该能在容器的对应目录下发现挂载好的磁盘,并且可以正常读写了。
4.5 让CPU慢下来
成功在非特权容器里安装了SMB和WebDAV,存储功能恢复正常,剩下的事情就可以从长计议了。但由于这块高性能的Intel CPU只跑了一些轻量级容器,一直维持睿频状态就有些耗电了,因此可以适当调整CPU调度,让机器在空闲时慢下来。
首先可以先查看一下当前的调度模式:
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor此命令会打印出当前的工作状态,如果看到了一大堆的performance,就说明目前CPU工作在性能模式,睿频会被持续触发。如果你想降低CPU耗电量,不妨把CPU设置为schedutil模式:
for c in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo schedutil | tee $c
done当然了,落到笔者的例子里,哪有这么简单呢?执行命令后系统提示Invalid argument,询问了一下ChatGPT之后才知道,这块CPU使用的是Intel P-state驱动,只支持performance和powersave模式。而后者,和一般CPU的不同,不是锁最低频率,而是动态调整,相当于一般CPU的schedutil,所以解决办法也就很简单了,只需把上面的命令改为echo powersave,问题也就解决了
5. 写在最后
这几天断断续续在折腾这台PVE机器,总算是调教得能正常使用了。
至于别的?以后慢慢研究吧。
(完)