PVE 9.0 定制 Debian 13 镜像 支持 Cloud-Init 快速部署虚拟机【模板篇】
前言

经常买 VPS 的肯定都有这种体会:在商家的面板里装系统往往只需要几分钟,而且商家提供的系统模板中还预装了很多常用的工具。
但在本地安装系统时,先不说安装完成后还得手动更换软件源、调整时区、安装常用工具,就连安装过程各种设置选下来,整个过程起码也得花费几十分钟。
实际上,在 PVE 中安装 Debian 并不需要下载 3.7G 的 ISO 镜像,在官方的下载页面就可以看到,完整安装镜像包含了所有桌面环境的套件,但 PVE 中的虚拟机并不需要这些套件,右下角的“云镜像”反而更加合适,直接将下载好的 qcow2 镜像导入,就可以开机,没有复杂的安装过程。

定制镜像
使用 virt-customize 自动化定制参考了 Sukka 大佬的文章,如果想要深入了解的可以去看看原文
在“云镜像”的介绍界面,可以看到云镜像分为了 azure、ec2、generic、genericcloud、nocloud,这么多镜像我们到底应该选择哪个呢?
首先排除前两个 Amazon EC2 和 Microsoft Azure 的镜像,然后就是最后的 nocloud 不支持 Cloud-Init 也可以排除了。
那么最后就剩下 generic 和 genericcloud,在 Sukka 大佬的文章中推荐的是更加适合虚拟机的 genericcloud 镜像,但我实际测试下来发现这个镜像有些过于精简了,不包含 USB 相关内核模块,如果你有直通 USB 连接打印机、移动硬盘的需求,建议使用 generic 镜像,更加适合家庭用户。

选择好了合适的镜像后,我们接着用 libguestfs-tools 来自动化定制镜像,这一过程建议使用国外的 VPS 来操作,避免网络原因造成定制失败,内存的话最好在 4G 以上。
apt install libguestfs-tools下载官方原版的 qcow2 镜像
wget -c https://cdimage.debian.org/images/cloud/trixie/latest/debian-13-generic-amd64.qcow2下面是我定制 Debian 13 镜像用到的命令,主要是为了让这个镜像更加适合国内宝宝体质。
危险
安装软件包时要注意避免冲突。我一开始不小心装了 ifupdown,结果和 systemd-networkd 冲突,导致虚拟机开机一直获取不到 DNS。
通过 resolvectl status 可以查看当前实际生效的 DNS,正常情况下如果 IPv4 是 DHCP 的会获取到 DHCP 分配的 DNS,如果 IPv4 是静态 IP,则可以在 Cloud-Init 中设置 DNS。
提示
在PVE 中使用的虚拟机,强烈建议安装上 qemu-guest-agent
virt-customize -a debian-13-generic-amd64.qcow2 \
--smp 2 --verbose \
--timezone "Asia/Shanghai" \
--append-line "/etc/default/grub:# disables OS prober to avoid loopback detection which breaks booting" \
--append-line "/etc/default/grub:GRUB_DISABLE_OS_PROBER=true" \
--run-command "update-grub" \
--run-command "sed -i 's|Types: deb deb-src|Types: deb|g' /etc/apt/sources.list.d/debian.sources" \
--run-command "sed -i 's|generate_mirrorlists: true|generate_mirrorlists: false|g' /etc/cloud/cloud.cfg.d/01_debian_cloud.cfg" \
--update --install "wget,curl,nano,vim,sudo,unzip,mtr-tiny,iputils-ping,bind9-host,dnsutils,net-tools,lsb-release,ca-certificates,bash-completion,fail2ban,dialog,netbase,iproute2,whois,ssh,dbus,systemd,systemd-sysv,locales,apt-utils,gnupg2,apt-transport-https,rsyslog,logrotate,less,rsync,qemu-guest-agent,haveged,systemd-timesyncd" \
--run-command "apt-get -y autoremove --purge && apt-get -y clean" \
--run-command "sed -i 's/^#\?\s*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config && sed -i 's/^#\?\s*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config" \
--append-line "/etc/systemd/timesyncd.conf:NTP=ntp.aliyun.com" \
--delete "/var/log/*.log" \
--delete "/var/lib/apt/lists/*" \
--delete "/var/cache/apt/*" \
--truncate "/etc/apt/mirrors/debian.list" \
--append-line "/etc/apt/mirrors/debian.list:https://mirrors.tuna.tsinghua.edu.cn/debian" \
--truncate "/etc/apt/mirrors/debian-security.list" \
--append-line "/etc/apt/mirrors/debian-security.list:https://mirrors.tuna.tsinghua.edu.cn/debian-security" \
--truncate "/etc/machine-id"具体做了这些操作:
| 类别 | 功能 |
|---|---|
| 基础 | 指定镜像、多核执行 |
| 时区 | 设置为上海 |
| 引导 | 禁止 OS prober |
| 系统 | 更新包、安装常用工具 |
| SSH | 启用 root 登录和密码认证 |
| 网络 | 设置阿里云 NTP |
| APT | 改用清华源、禁用源码仓库 |
| Cloud-Init | 禁止自动生成镜像源 |
| 清理 | 删除日志和缓存 |
| 唯一性 | 清空 machine-id 以防冲突 |
最后再把刚才定制好的镜像压缩一下就大功告成了。
virt-sparsify --compress debian-13-generic-amd64.qcow2 debian-13-generic-amd64-zc.qcow2制作模板
这一步是将刚才定制好的 qcow2 导入 PVE 中并创建成虚拟机模板,其中大部分操作都可以在 PVE 的网页中完成
创建虚拟机
由于创建的虚拟机后面是转换为模板使用,因此 VM ID 可以填的大一些,与正在运行的虚拟机区分开。

操作系统后面会直接导入 qcow2,所以这里选择“不使用任何介质”

机型可以选择 “q35”,BIOS 选择 “OVMF (UEFI)”,最重要的是勾选 “Qemu 代理”,可以和虚拟机内安装的 qemu-guest-agent 交互。

刚才定制的 qcow2 本质上就是一个磁盘文件,后面直接导入就是系统盘,因此这里不需要单独添加磁盘,将其删除就好。

创建的这个虚拟机会转换为模板使用,接下来的 CPU 和内存设置选择低配即可,从模板克隆虚拟机的时候再根据实际需求修改。


在内网使用 PVE,可以去掉勾选“防火墙”,方便操作。

这样就创建好了虚拟机,但此时还无法使用,还得导入镜像并且添加上必要的硬件。
导入镜像
网页上不支持导入镜像,因此这一步必须使用命令行操作。
将 qcow2 镜像上传到 PVE 中,通过以下命令导入:
9000是刚才创建的虚拟机 VM ID/tmp/debian-13-generic-amd64-zc.qcow2是镜像存放的路径local-lvm是存储--format=qcow2将其指定为 qcow2 格式
qm importdisk 9000 /tmp/debian-13-generic-amd64-zc.qcow2 local-lvm --format=qcow2返回网页,会看到“未使用的硬盘 0”,双击将其添加进来。

接着来到“选项”,找到引导顺序,勾选刚才添加的系统盘,并把它移动到第一位。

添加硬件
除了系统盘以外,我们还需要添加 CloudInit 设备以及串行端口。
其中 CloudInit 设备可以用于配置虚拟机的用户名、密码、DNS、SSH 公钥以及 IP,而串行端口可以让我们使用控制台中的 xterm.js。
CloudInit
添加 CloudInit 设备,添加时建议选择 SCSI 设备。

接着来到 Cloud-Init 选项卡中,添加完 CloudInit 设备之后里面的选项就能修改了,可以设置一下用户、密码以及 IP。

串行端口
默认情况下,控制台只能选择 noVNC,但它使用起来非常的不方便,清晰度不够也不支持复制粘贴,因此我们需要添加一个“串行端口”来使用其他终端。

此时,控制台就多了个 xterm.js 可以选择了,使用起来更加的方便。

转换为模板
提示
为了保持系统的纯净度,如果你导入的是定制后的镜像,那么转换成模板前不要启动这台虚拟机。
如果你没有条件自动化定制镜像,那么可以在导入镜像之后,直接启动虚拟机,按照你自己的需求换源、安装常用的软件包等等。
不过这一系列操作会留下一堆日志以及缓存,好在除了 machine-id 外基本上都不会影响克隆后的虚拟机正常使用。
但不移除 machine-id 会使得后续克隆的虚拟机 machine-id 也相同,从而导致各种冲突,因此建议在所有操作完成后、关机前,执行以下命令将 machine-id 移除,最后再关闭虚拟机。
truncate -s /etc/machine-id /var/lib/dbus/machine-id完成以上所有步骤后,就可以将其转换成模板了,后续的创建虚拟机都以它为基础,几秒钟就可以新开一台虚拟机。

克隆虚拟机
创建一台新的虚拟机,只需要在刚才的模板上右键,选择“克隆”,模式建议选择“完整克隆”,稍等片刻左边的列表中就会有一台新的虚拟机出现了。

由于我们创建的模板选择配置都是比较低的,这时候可以适当调整一下配置,分配更大的内存、硬盘以及 CPU 核心数。
其中硬盘的扩容稍复杂些,选中硬盘后,找到上方的调整大小,初始硬盘为 3G,输入的数字为增量,例如扩容到 10G,则需要在弹出的窗口中填写 7G。








