Proxmoxでcloud-initイメージからVMをバルク作成する
最近のポストでも触れたとおり、おうちラボにProxmoxが仲間入りしたのでいろいろなLinuxディストロを試しています。
サーバリストをcsvファイルに用意し、そこから複数のVMを作るスクリプト、またcloud-initイメージを使ってVMを作る方法をまとめたので、今回はそれをポストします。
準備
準備するものは次の通りです:
- テンプレート化したcloud-initイメージ
- cloud-initコンフィグとして設定するユーザ名、ssh公開鍵、nameserversとsearch suffix
なお今回の作業を実行したのはこちらのバージョンのProxmoxです。
# pveversion
pve-manager/8.3.4/65224a0f9cd294a3 (running kernel: 6.8.12-8-pve)
Proxmox上のVMのCPUTYPEについて
https://pve.proxmox.com/pve-docs/pve-admin-guide.html#_qemu_cpu_types
https://www.yinfor.com/2023/06/how-i-choose-vm-cpu-type-in-proxmox-ve.html
これらの有用なURLを参照しました。
私のProxmoxマシンの場合は、CPUTYPEは"x86-64-v3"となりました。
テンプレート化されたcloud-initイメージ
基本的に使いたいディストロのcloud-initイメージをウェブ検索して見つけ、ダウンロードし、公式で紹介されている手順を追ってテンプレート化されたcloud-initイメージを用意することになります。
https://pve.proxmox.com/wiki/Cloud-Init_Support
Debian 12 Bookwormを例にとって実際の作業ステップを紹介します。
https://cdimage.debian.org/images/cloud/
いろいろな種類が用意されていますが、今回はgenericcloud
を利用します。
Plain VM (amd64), suitable for use with QEMU
genericcloud: Similar to generic. Should run in any virtualised environment. Is smaller than
generic
by excluding drivers for physical hardware.
以下がProxmox上で実行したコマンドです。
イメージをダウンロードする場所、テンプレートのProxmox上のID、使用するProxmox上のディスクおよびVMのCPUTYPEなど、自身の環境に合わせ自由に設定してください。
# directory to store downloaded cloud-init images
PVE_IMAGE_DIR=/opt/pve/dnld
mkdir -p $PVE_IMAGE_DIR
cd $PVE_IMAGE_DIR
# download qcow2 image and checksum file
wget https://cdimage.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
wget https://cdimage.debian.org/images/cloud/bookworm/latest/SHA512SUMS
# verify
sha512sum -c SHA512SUMS
# view the image details
qemu-img info debian-12-genericcloud-amd64.qcow2
# set the variable for the image
CIIMAGE=$PVE_IMAGE_DIR/debian-12-genericcloud-amd64.qcow2
# ID of the cloud-init template image
TEMPLATE_ID=9006
# create a VM template using the downloaded cloud-init image
# CPUTYPE is x86-64-v3 in my case
# the disk I use for my VMs is "local-zfs"
PVE_CPUTYPE=x86-64-v3
PVE_DISK=local-zfs
qm create $TEMPLATE_ID --memory 2048 --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-single --cpu cputype=$PVE_CPUTYPE --ostype l26
qm set $TEMPLATE_ID --scsi0 $PVE_DISK:0,import-from=$CIIMAGE
qm set $TEMPLATE_ID --ide2 $PVE_DISK:cloudinit
qm set $TEMPLATE_ID --serial0 socket --vga serial0
qm set $TEMPLATE_ID --boot order=scsi0
qm template $TEMPLATE_ID
# check the resulting image
qm config $TEMPLATE_ID
作成するサーバリストの準備
ビルドスクリプトやサーバリストのファイル、それらを置くディレクトリを用意しましょう。
ディレクトリは/opt/pve/builds
、VMリストはvm_list.csv
、VM作成スクリプトはbulk_build.sh
とします。
mkdir -p /opt/pve/builds
cd /opt/pve/builds
# create csv file here
# and name it vm_list.csv
今回のサーバリストファイルはこのような形になりました。今回作るVMのOSはDebianだけですが、テンプレート化されたイメージが用意されていれば多様なOSを混ぜてリストにできます。
なお、192.0.2.0/24サブネットとexample.netというドキュメント用のサブネットとドメインを用いる体でこのポストは進めていきます。ここもご自身の実際の環境に合わせて用意しましょう。
VMIDも同様です。Proxmox上で割り当てたいVMIDを決めてリストに記載してください。
# hostname, vmid, os, cpu, memory(mb), disk(gb), ipaddr/subnetmask, gw
# vmhost1, 8999, debian, 4, 8192, 128, 192.0.2.30/24, 192.0.2.1
etcd2,1303,debian,2,2048,32,192.0.2.11/24,192.0.2.62
etcd3,1304,debian,2,2048,32,192.0.2.12/24,192.0.2.62
バルクVM作成スクリプト
次に.env
ファイル内にcloud-initイメージテンプレート情報やcloud-initコンフィグ情報を用意します。ここで設定するコンフィグ情報はユーザ名、SSH公開鍵、ネームサーバ、そしてサーチドメイン名です。
以下のファイルではDebian以外のディストロの情報も記載されていることが確認できます。これは、私はすでにVMID 9003にRHELのcloud-initイメージのテンプレートを用意しているので、VM作成スクリプトが参照できるようにここに情報を含めています。
# .env file
# cloud-init
CI_USERNAME=ansible-hlv3
CI_USER_SSH_PUB=/opt/pve/ssh_pub/id_ed25519_ansible.pub
CI_NAMESERVERS=192.0.2.16 192.0.2.17
CI_SEARCHDOMAIN=example.net
# VM TEMPLATE ID
DEBIAN_TEMPLATE_ID=9006
RHEL_TEMPLATE_ID=9003
UBUNTU_TEMPLATE_ID=9004
ROCKY_TEMPLATE_ID=9000
ORACLE_TEMPLATE_ID=9001
そして次に実際にVMを作るスクリプトです。いくつかの変数は上の.env
ファイルから取得しています。
"debian"といった名前とProxmox上のテンプレートのVMIDをマッチさせるために、get_template_id()
にマッチさせるための条件を記載しています。別のIDを参照させる、あるいは他のディストロ・テンプレートVMIDを追加するといった際にはここと.env
を編集しましょう。
スクリプトのVM作成作業の内容としては次の通りです:
- cloud-initテンプレートイメージのIDを参照し、そのクローンVMを作る
- 先述のcloud-initコンフィグを作成したクローンVMに設定する
- CPU数やメモリサイズをサーバリストのcsvファイルにある通り設定する
- ディスク容量も同様に、csvファイルに従ってリサイズする
-
onboot=1
をセットし、Proxmoxが再起動などした際にVMが自動的に起動するようにする - "init"という名称でスナップショットを作る
- このスナップショットのdescriptionに作成日時を記載しておく
#!/bin/bash
# files to use
ENV_FILENAME=.env
VM_LIST_CSV=vm_list.csv
# function to load variables from .env file
load_dotenv() {
while IFS= read -r line; do
if [[ -z "$line" || "$line" =~ ^# ]]; then
continue
fi
export "$line"
done <$ENV_FILENAME
}
# function to get template VM ID per OS
function get_template_id() {
case $OS in
debian)
TEMPLATE_ID=$DEBIAN_TEMPLATE_ID
;;
rhel)
TEMPLATE_ID=$RHEL_TEMPLATE_ID
;;
ubuntu)
TEMPLATE_ID=$UBUNTU_TEMPLATE_ID
;;
rocky)
TEMPLATE_ID=$ROCKY_TEMPLATE_ID
;;
oracle)
TEMPLATE_ID=$ORACLE_TEMPLATE_ID
;;
*)
echo "Invalid OS"
exit 1
;;
esac
}
# function for VM bulk build
function bulk_build_vm() {
local IFS=','
while read -r line; do
if [[ -z "$line" || "$line" =~ ^# ]]; then
continue
fi
set -- $line
HOSTNAME=$1
VMID=$2
OS=$3
CPU=$4
MEMORY=$5
DISK="${6}G"
NIC=$7
GW=$8
get_template_id
qm clone $TEMPLATE_ID $VMID --name $HOSTNAME
qm set $VMID --sshkey $CI_USER_SSH_PUB
qm set $VMID --ciuser $CI_USERNAME
qm set $VMID --ipconfig0 ip=$NIC,gw=$GW
qm set $VMID --nameserver "${CI_NAMESERVERS}"
qm set $VMID --searchdomain $CI_SEARCHDOMAIN
qm set $VMID --cores $CPU --memory $MEMORY
qm set $VMID --onboot 1
qm disk resize $VMID scsi0 $DISK
qm snapshot $VMID init --description "Created at $(date --utc --iso-8601=seconds)"
done <$VM_LIST_CSV
}
# load variables from .env file
load_dotenv
# bulk build VMs
bulk_build_vm
スクリプトを実行すると、次のようなログが確認できると思います。
# ./bulk_build.sh
create full clone of drive ide2 (local-zfs:vm-9006-cloudinit)
create linked clone of drive scsi0 (local-zfs:base-9006-disk-0)
update VM 1303: -sshkeys xxx
update VM 1303: -ciuser ansible-hlv3
update VM 1303: -ipconfig0 ip=192.0.2.11/24,gw=192.0.2.62
update VM 1303: -nameserver 192.0.2.16 192.0.2.17
update VM 1303: -searchdomain example.net
update VM 1303: -cores 2 -memory 2048
update VM 1303: -onboot 1
snapshotting 'drive-scsi0' (local-zfs:base-9006-disk-0/vm-1303-disk-0)
create full clone of drive ide2 (local-zfs:vm-9006-cloudinit)
create linked clone of drive scsi0 (local-zfs:base-9006-disk-0)
update VM 1304: -sshkeys xxx
update VM 1304: -ciuser ansible-hlv3
update VM 1304: -ipconfig0 ip=192.0.2.12/24,gw=192.0.2.62
update VM 1304: -nameserver 192.0.2.16 192.0.2.17
update VM 1304: -searchdomain example.net
update VM 1304: -cores 2 -memory 2048
update VM 1304: -onboot 1
snapshotting 'drive-scsi0' (local-zfs:base-9006-disk-0/vm-1304-disk-0)
# qm config 1303
boot: order=scsi0
ciuser: ansible-hlv3
cores: 2
cpu: cputype=x86-64-v3
ide2: local-zfs:vm-1303-cloudinit,media=cdrom,size=4M
ipconfig0: ip=192.0.2.11/24,gw=192.0.2.62
memory: 2048
meta: creation-qemu=9.0.2,ctime=1741480019
name: etcd2
nameserver: 192.0.2.16 192.0.2.17
net0: virtio=BC:24:11:16:50:A2,bridge=vmbr0
onboot: 1
ostype: l26
parent: init
scsi0: local-zfs:base-9006-disk-0/vm-1303-disk-0,size=32G
scsihw: virtio-scsi-single
searchdomain: example.net
serial0: socket
smbios1: uuid=73b42a6c-b9c8-4c83-9b26-47d86821df39
sshkeys: ssh-ed25519%20AAAAC3NzaC1lZDI1NTE5AAAAICL2ZwI2LR%2BVb%2BqFL6wgEwlhLRVD1CIO71bEmrlAGVj1%20ansible%0A
vga: serial0
vmgenid: f26df15a-f2d8-48e3-9382-255ccd998c50
# qm listsnapshot 1303
`-> init 2025-03-09 00:57:10 Created at 2025-03-09T00:57:10+00:00
`-> current You are here!
無事VMが作成されたらqm start 1303
など実行してVMを起動しましょう。
ログオンして確認してみる
ログオンしてみましょう。ユーザ名は.env
内でも指定した通りのもの、そしてSSH秘密鍵はcloud-initコンフィグ用に指定した公開鍵のペアとなるものを用いましょう。
以下のように、VMは最新のDebian 12 Bookworm (12.9)で動いており、CPUは2つ、メモリは2GB、ディスクサイズは32GBであることが確認できると思います。
sudo
も最初から使えるようになっています。
ansible-hlv3@etcd2:~$ ls -la .ssh
total 12
drwx------ 2 ansible-hlv3 ansible-hlv3 4096 Mar 9 01:01 .
drwxr-xr-x 3 ansible-hlv3 ansible-hlv3 4096 Mar 9 01:01 ..
-rw------- 1 ansible-hlv3 ansible-hlv3 89 Mar 9 01:01 authorized_keys
ansible-hlv3@etcd2:~$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
ansible-hlv3@etcd2:~$ cat /etc/debian_version
12.9
ansible-hlv3@etcd2:~$ sudo echo hi
hi
ansible-hlv3@etcd2:~$ grep -c processor /proc/cpuinfo
2
ansible-hlv3@etcd2:~$ grep MemTotal /proc/meminfo
MemTotal: 2027032 kB
ansible-hlv3@etcd2:~$ sudo fdisk -l
Disk /dev/sda: 32 GiB, 34359738368 bytes, 67108864 sectors
Disk model: QEMU HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: E87670DB-F40E-7D44-B763-861A19E45656
Device Start End Sectors Size Type
/dev/sda1 262144 67108830 66846687 31.9G Linux root (x86-64)
/dev/sda14 2048 8191 6144 3M BIOS boot
/dev/sda15 8192 262143 253952 124M EFI System
Partition table entries are not in disk order.
完成
以上です。
ハイパーバイザーがあるといろいろなディストロを簡単に試すことができます。そして手作業でやるのもたいした手間ではないです。
ただ私の場合、いろいろ試し終えた後、全部クリーンアップしていざ全部VM立ち上げるところからおうちラボ構築をやり直そうといった時に、異なるセットアップの複数のVMをいっぺんに作成できるスクリプトが使えるというのは便利でした。
Discussion