0%

在 PVE 中定制和使用模板虚拟机

在之前的 Proxmox VE + Ceph 集群搭建 一文中,完成了 PVE 集群的搭建,在虚拟化平台的日常使用中,经常需要创建新的虚拟机。
通常的交付流程是:确定所需配置,创建虚拟机,安装操作系统,配置网络,安装必要的软件,最后交付使用。
如果每次都按照这个流程来,既耗时又容易出错。为了提高效率,可以使用模板虚拟机来快速创建新的虚拟机实例。
本文将介绍一种基于 Cloud-init 的虚拟机模板的创建和使用方法。

获取支持 Cloud-init 镜像

Cloud-init 是一个用于自动化配置云实例的工具,许多 Linux 发行版都提供了支持 Cloud-init 的镜像。
虽然可以使用普通的 ISO 镜像安装系统,然后安装 cloud-init 软件包,但直接使用官方提供的 cloud-init 的镜像会更干净。
收集到的一些常用发行版的 cloud-init 镜像下载地址如下:

使用 Cloud-init 镜像创建虚拟机

下载到的 cloud-init 镜像通常是 QCOW2 格式的磁盘镜像文件,是 qcow2 或 img 后缀,可以在 PVE 的 shell 中使用命令创建虚拟机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
qm create 200 \
--ostype l26 \
--agent 1,fstrim_cloned_disks=1 \
--machine q35 \
--bios ovmf \
--cpu host \
--cores 2 \
--sockets 2 \
--memory 8192 \
--hotplug disk,network \
--net0 virtio,bridge=vmbr0,mtu=1 \
--ciupgrade 0 \
--scsihw virtio-scsi-pci \
--boot order=scsi0 \
--sata0 data:cloudinit \
--efidisk0 data:0 \
--scsi0 data:0,import-from=/root/debian-13-genericcloud-amd64.qcow2,discard=on,ssd=1 \
--name "debian-13"

在 scsi0 参数中,import-from 指定了下载好的 cloud-init 镜像文件路径,data:0 表示使用存储池 data 创建虚拟机磁盘,需要根据实际情况修改。
创建完成的虚拟机:
虚拟机配置信息

刚创建完成的虚拟机还不能直接使用,可以看到虚拟机磁盘大小只有 3G,需要扩大磁盘大小:
调整磁盘大小

调整完成后,在 Cloud-init 选项卡中,设置用户名密码网络等信息:
Cloud-init 配置

调整完成后,开机登录进入系统,可以确认到以上设置已经生效,并且 hostname 也对应在 PVE 设置的虚拟机名字:
系统启动情况
但现在只是使用了官方提供的原始系统,如果需要更加方便快捷的创建交付,还需要对虚拟机进行定制。

定制 Cloud-init 模板虚拟机

登录 PVE 的 shell,后续命令都需要在 PVE shell 中执行。

安装定制工具:

1
apt install libguestfs-tools qemu-utils -y

以刚才使用的 Debian 13 为例,首先检查下载的镜像文件分区情况:

1
virt-df -h debian-13-genericcloud-amd64.qcow2

debian 13 镜像磁盘情况
可以看到分区情况,/dev/sda1 只有 2.8G,需要扩大分区以完成之后的定制步骤。

1
2
3
4
# 首先创建一个新的 32G 的 qcow2 空白磁盘文件
qemu-img create -f qcow2 debian-13-32g-custom.img 32G
# 将原始镜像拷贝到新的磁盘文件中,并扩展 sda1 分区
virt-resize --expand /dev/sda1 debian-13-genericcloud-amd64.qcow2 debian-13-32g-custom.img

扩展分区情况

确保分区空间充足后,可以开始接下来的定制步骤,创建定制脚本 custom-commands.txt,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 在调整磁盘大小后,需要重新安装 grub
run-command grub-install /dev/sda
# 更新 grub 设置
run-command update-grub

# 修改 apt 镜像源,换成 deb822 格式
delete /etc/apt/sources.list
delete /etc/apt/sources.list.d/debian.sources
append-line /etc/apt/sources.list.d/debian.sources:Types: deb
append-line /etc/apt/sources.list.d/debian.sources:URIs: https://mirrors.cernet.edu.cn/debian
append-line /etc/apt/sources.list.d/debian.sources:Suites: stable stable-updates stable-backports
append-line /etc/apt/sources.list.d/debian.sources:Components: main contrib non-free non-free-firmware
append-line /etc/apt/sources.list.d/debian.sources:Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
append-line /etc/apt/sources.list.d/debian.sources:
append-line /etc/apt/sources.list.d/debian.sources:Types: deb
append-line /etc/apt/sources.list.d/debian.sources:URIs: https://mirrors.cernet.edu.cn/debian-security
append-line /etc/apt/sources.list.d/debian.sources:Suites: stable-security
append-line /etc/apt/sources.list.d/debian.sources:Components: main contrib non-free non-free-firmware
append-line /etc/apt/sources.list.d/debian.sources:Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg

# 配置 tailscale 源
run-command wget -q https://pkgs.tailscale.com/stable/debian/trixie.noarmor.gpg -O /usr/share/keyrings/tailscale-archive-keyring.gpg
run-command wget -q https://pkgs.tailscale.com/stable/debian/trixie.tailscale-keyring.list -O /etc/apt/sources.list.d/tailscale.list
run-command sed -i 's,pkgs.tailscale.com/stable,mirrors.cernet.edu.cn/tailscale,g' /etc/apt/sources.list.d/tailscale.list

# 更新 apt 数据
update
# 更新现有包
run-command apt full-upgrade -y
# 安装基础软件包
install sudo,qemu-guest-agent,wget,curl,net-tools,nano,vim,tmux,htop,btop,nethogs,iftop,ncdu,axel,fish,arp-scan,nmap,unar

# 安装 tailscale
install tailscale,iptables

# 禁止 root 用户通过 ssh 登录
append-line /etc/ssh/sshd_config.d/00-user-init.conf:PermitRootLogin no
# 禁止 ssh 使用密码登录
append-line /etc/ssh/sshd_config.d/00-user-init.conf:PasswordAuthentication no
# 设置 ssh 的端口为 3333
append-line /etc/ssh/sshd_config.d/00-user-init.conf:Port 3333

# 开启 ipv4 转发
append-line /etc/sysctl.d/00-tailscale.conf:net.ipv4.ip_forward=1
append-line /etc/sysctl.d/00-tailscale.conf:net.ipv6.conf.all.forwarding=1

# 缓存清理步骤
run-command apt clean
run-command fstrim -a

delete /var/log/*.log
delete /var/lib/apt/lists/*
delete /var/cache/apt/*

truncate /etc/machine-id

这个定制脚本里并不是 shell 语法,而是 virt-customize 的选项,可以参考官方文档了解更多用法。
脚本创建完成后,执行以下命令进行定制:

1
virt-customize -a debian-13-32g-custom.img --commands-from-file custom-commands.txt

执行过程:
定制过程
virt-customize 会使用 qemu 挂载 debian-13-32g-custom.img 镜像,并执行脚本中的命令完成定制。
定制完成后,可以使用定制完成的 debian-13-32g-custom.img 文件,按照之前创建虚拟机的步骤,创建新的虚拟机即可。

自动化

上面的定制步骤比较繁杂,可以编写一个自动化脚本来完成这些步骤,适配不同操作系统。
自动化脚本示例:pve-img-maker,使用方法:

1
2
3
4
5
6
7
# 安装依赖
apt install libguestfs-tools qemu-utils python3 axel git -y
# 拉取代码
git clone https://github.com/nameless-name/pve-img-maker.git
cd pve-img-maker
# 运行脚本
python3 build.py

脚本会提示选择操作系统和定制脚本,确定后会自动完成下载镜像、扩展分区、定制镜像等步骤,最后在 output 目录生成定制完成的 qcow2 镜像文件。

如果需要添加其他系统支持,可以在 script 目录下创建对应操作系统的文件夹和配置脚本,base 和 clean 是默认启用的初始化和清理脚本,可以参考现有脚本编写。
还需要在 build.py 里 BASE_IMAGE_URLS 添加对应的镜像下载连接。

在集群环境中,可配合 pvesh 命令完成虚拟机的批量创建和交付,进一步提高效率:

# 将 vmid 200 的虚拟机转换成模板
qm template 200
# 从 200 模板批量创建虚拟机 201-210
for vmid in {201..210}; do
    qm clone 200 $vmid --name "vm-$vmid"
    # 设置 Cloud-init 配置,不设置密码只使用 ssh key 登录
    qm set $vmid \
        --ipconfig0 ip=192.168.233.$vmid/24,gw=192.168.233.1 \
        --net0 virtio,bridge=vmbr233,mtu=1 \
        --cores 16 --sockets 2 --memory 16384 \
        --ciuser username --sshkeys /root/.ssh/ssh-key.pub
    # 调整磁盘大小
    qm resize $vmid scsi0 +32G
    # 迁移到指定节点(如果需要)
    # qm migrate $vmid pve2
    # qm 只能操作本机已有的虚拟机,集群内的其他虚拟机需要使用 pvesh 指定节点操作
    # 使用 pvesh 启动虚拟机
    pvesh create /nodes/pve2/qemu/$vmid/status/start
    # 使用 pvesh 设置虚拟机 DNS
    pvesh set /nodes/pve2/qemu/$vmid/config --nameserver 223.5.5.5,114.114.114.114
done