Da ich die meisten VMs nach dem gleichen Schema haben möchte, also ein Ubuntu 20.04 mit meinem User, meinen SSH-Pubkeys und ein paar Tools wie zsh, vim, tmux, dnsutils und tcpdump installiert, finde ich es am einfachsten, eine Template-VM anzulegen und alles, was irgendwie geht, daraus zu klonen.

Zuerst muss, wie immer, ein neues ZFS Dataset für die Template-VM angelegt werden.

zfs create tank/vms/template_ubuntu_2004

In diesem Dataset wird für die VM ein neues 50GB fassendes thin-provisioned QCOW2 Image angelegt.

qemu-img create -f qcow2 /srv/vms/template_ubuntu_2004/template_ubuntu_2004_disk01.qcow2 50G

In dieses Image wird im nächsten Schritt Ubuntu von einem zuvor herunter geladenen ISO installiert.

virt-install --virt-type kvm \
    --name template_ubuntu_2004 \
    --ram 2048 \
    --disk /srv/vms/template_ubuntu_2004/template_ubuntu_2004_disk01.qcow2,bus=scsi,discard='unmap',format=qcow2 \
    --controller type=scsi,model=virtio-scsi \
    --cpu host \
    --network bridge=vbr100 \
    --graphics vnc,listen=0.0.0.0 \
    --noautoconsole \
    --os-type=linux \
    --os-variant=ubuntu20.04 \
    --cdrom=/srv/images/iso/ubuntu-20.04.1-live-server-amd64.iso

Als Zieldisk wird das QCOW2 von eben verwendet. Als Bus der Virtio-SCSi Controller, der in der nächsten Zeile steht. Die Option discard='unmap' sorgt dafür, dass man später durch ein fstrim im Guest-OS dafür sorgen kann, dass gelöschte Blöcke im Container wieder freigegeben werden und daher auf der Host-Disk keinen unnötigen Platz mehr verbrauchen. Das Netzwerk wird mit einer der Bridges auf dem Host verbunden (siehe letzter Beitrag). Statt VNC könnte man auch die Console konfigurieren.

Mit einem

virsh vncdisplay template_ubuntu_2004

kann man herausfinden, welchen VNC-Port (ab 5900) die VM verwendet. Wenn es die erste VM auf dem Host ist, wird es vermutlich Port 5900 sein. Die weitere Installation der VM läuft dann innerhalb der VNC-Verbindung durch den normalen Ubuntu-Installer ab.
Da ich in allen meinen VMs gerne ein paar Tools wie zsh, vim, tmux, dnsutils und tcpdump habe, installiere ich diese nach dem ersten Boot und einem apt update && apt full-upgrade. Auch meine SSH-Pubkeys lege ich hier schon an die dafür vorgesehene Stelle. Wenn diese Arbeiten abgeschlossen sind, schalte ich die VM aus und kann aus ihr dann die weiteren VMs klonen.

Dieser Schritt beginnt wie vorhin mit dem Anlegen eines neuen ZFS Datasets für die VM.

zfs create tank/vms/test01

Im nächsten Schritt wird die VM geklont. Die verwendeten Optionen des Befehls virt-clone sollten selbsterklärend sein.

virt-clone --original template_ubuntu_2004 --name test01 --file /srv/vms/test01/test01_disk01.qcow2

Damit nicht alle VMs die gleichen SSH-Hostkeys und andere IDs haben, wird der Klon noch ein wenig angepasst. Von den ganzen Steps, die virt-sysprep anbietet, wähle ich nur user-account und ssh-userdir ab, da ich meinen User und die .ssh/authorized_keys behalten möchte.

w=$(virt-sysprep --list-operations | egrep -v 'user-account|ssh-userdir' | awk '{ printf "%s,", $1}' | sed 's/,$//')
virt-sysprep -d test01 --hostname test01 --enable $w --firstboot-command 'dpkg-reconfigure openssh-server; chage -d 0 jochen'

Mit den beiden firstboot-command’s wird dafür gesorgt, dass die SSH-Hostkeys neu generiert werden und ich beim ersten Login das Passwort meines Users neu setzen muss. Anschließend passe ich noch mit

virsh edit test01

die Netzwerk-Bridge an, an der die VM angeschlossen sein soll, und die CPU und RAM-Ausstattung der VM, die damit fertig ist ihre individuellen Dinge installiert zu bekommen.