home

Using task to create libvirt virtual guests

2024/05/12

Creating virtual guests with libvirt can be conveniently automated with task and a few other dependencies. Below I describe how to handcraft a Taskfile.yml that provides 2 tasks (create & delete) which can be used to create and delete individual guests using libvirt.

Apart from task, the other dependencies needed are:

$ sudo apt install wget libvirt-clients apg gopass guestfs-tools virtinst 

When creating the libvirt network, I make reference to br0 which is the bridge interface on my host system running libvirtd. br0 can be setup using a network interface definition like this:

auto br0
iface br0 inet static
        address 192.168.1.10
        broadcast 192.168.1.255
        netmask 255.255.255.0
        gateway 192.168.1.1
        dns-nameservers 192.168.1.1
        bridge_ports eth0
        bridge_stp off
        bridge_waitport 0
        bridge_fd 0

First, create a default Taskfile.yml file:

$ task --init

Within Taskfile.yml create vars and env:

version: '3'

env:
  LIBVIRT_DEFAULT_URI: qemu:///system

vars:
  IMAGE_URL: https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2
  IMAGE_FILENAME: 
    sh: echo {{ base .IMAGE_URL }}
  SSH_PUBKEY: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM+eJVmeHO1fclWlTGwV6TZHKEkGg7ZGVrIHro5jGBvb
  HOSTNAME: "debian-test-system"
  LIBVIRT_POOL_NAME: "default"
  DISK: 10240M
  MEMORY: 2048
  VCPUS: 2
  ROOTPASSWD:
    sh: apg -m 8 -a0 -n1

Create a bridge.xml file which defines a libvirt network:

<network>
    <name>debian-test-system</name>
    <forward mode="bridge"/>
    <bridge name="br0"/>
</network>

Next create the create task:

tasks:
  create:
    summary: "create {{.HOSTNAME}}"
    cmds:
      - wget -c {{.IMAGE_URL}}
      - cmd: echo {{.ROOTPASSWD}} | gopass insert --force {{.HOSTNAME}}-root-password
        silent: true
      - |
        virt-customize -a {{.IMAGE_FILENAME}} \
          --root-password password:{{.ROOTPASSWD}} \
          --edit '/etc/default/grub: s/^GRUB_CMDLINE_LINUX=".*"$/GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200 consoleblank=0 systemd.unified_cgroup_hierarchy=0"/' \
          --run-command 'mkdir -p /boot/grub2 && update-grub' \
          --ssh-inject root:string:"{{.SSH_PUBKEY}}" \
          --hostname {{.HOSTNAME}}
      - virsh vol-create-as --pool {{.LIBVIRT_POOL_NAME}} --name {{.HOSTNAME}} --capacity {{.DISK}} --format qcow2
      - virsh vol-upload --vol {{.HOSTNAME}} --pool {{.LIBVIRT_POOL_NAME}} {{.IMAGE_FILENAME}}
      - virsh net-define bridge.xml
      - virsh net-autostart {{.HOSTNAME}}
      - virsh net-start {{.HOSTNAME}}
      - |
        virt-install \
          --virt-type kvm \
          --name {{.HOSTNAME}} \
          --ram {{.MEMORY}} \
          --vcpus={{.VCPUS}} \
          --import \
          --video virtio \
          --disk vol={{.LIBVIRT_POOL_NAME}}/{{.HOSTNAME}},format=qcow2,bus=virtio \
          --memballoon virtio \
          --boot hd \
          --network network:{{.HOSTNAME}},model=virtio \
          --noautoconsole \
          --cpu host \
          --qemu-commandline='-smbios type=1,serial=ds=nocloud' \
          --osinfo debian11

And then create the delete task:

  delete:
    desc: delete {{.HOSTNAME}}
    cmds:
      - virsh vol-delete --pool {{.LIBVIRT_POOL_NAME}} {{.HOSTNAME}}
      - virsh destroy {{.HOSTNAME}}
      - virsh undefine {{.HOSTNAME}}
      - virsh net-undefine {{.HOSTNAME}}
      - virsh net-destroy {{.HOSTNAME}}

If everything is wired up correctly you should be able to create a virtual guest:

$ task create
task: [create] wget -c https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2
--2024-05-12 11:28:30--  https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2
Resolving cloud.debian.org (cloud.debian.org)... 194.71.11.165, 194.71.11.163, 2001:6b0:19::163, ...
Connecting to cloud.debian.org (cloud.debian.org)|194.71.11.165|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://laotzu.ftp.acc.umu.se/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2 [following]
--2024-05-12 11:28:33--  https://laotzu.ftp.acc.umu.se/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2
Resolving laotzu.ftp.acc.umu.se (laotzu.ftp.acc.umu.se)... 194.71.11.166, 2001:6b0:19::166
Connecting to laotzu.ftp.acc.umu.se (laotzu.ftp.acc.umu.se)|194.71.11.166|:443... connected.
HTTP request sent, awaiting response... 416 Requested Range Not Satisfiable

    The file is already fully retrieved; nothing to do.

task: [create] echo Twybajni | gopass insert --force debian-test-system-root-password
task: [create] virt-customize -a debian-12-generic-amd64.qcow2 \
  --root-password password:Twybajni \
  --edit '/etc/default/grub: s/^GRUB_CMDLINE_LINUX=".*"$/GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200 consoleblank=0 systemd.unified_cgroup_hierarchy=0"/' \
  --run-command 'mkdir -p /boot/grub2 && update-grub' \
  --ssh-inject root:string:"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM+eJVmeHO1fclWlTGwV6TZHKEkGg7ZGVrIHro5jGBvb" \
  --hostname debian-test-system

[   0.0] Examining the guest ...
[   3.8] Setting a random seed
virt-customize: warning: random seed could not be set for this type of 
guest
[   3.9] Editing: /etc/default/grub
[   4.0] Running: mkdir -p /boot/grub2 && update-grub
[   4.9] SSH key inject: root
[   5.7] Setting the hostname: debian-test-system
[   6.5] Setting passwords
[   7.2] Finishing off
task: [create] virsh vol-create-as --pool default --name debian-test-system --capacity 10240M --format qcow2
Vol debian-test-system created

task: [create] virsh vol-upload --vol debian-test-system --pool default debian-12-generic-amd64.qcow2

task: [create] virsh net-define bridge.xml
Network debian-test-system defined from bridge.xml

task: [create] virsh net-autostart debian-test-system
Network debian-test-system marked as autostarted

task: [create] virsh net-start debian-test-system
Network debian-test-system started

task: [create] virt-install \
  --virt-type kvm \
  --name debian-test-system \
  --ram 2048 \
  --vcpus=2 \
  --import \
  --video virtio \
  --disk vol=default/debian-test-system,format=qcow2,bus=virtio \
  --memballoon virtio \
  --boot hd \
  --network network:debian-test-system,model=virtio \
  --noautoconsole \
  --cpu host \
  --osinfo debian11


Starting install...
Creating domain...                                                                      |    0 B  00:00:00

Grab the root password from gopass:

$ gopass show -o debian-test-system-root-password

And you should be able to connect to the serial console:

$ virsh console debian-test-system
Connected to domain 'debian-test-system'
Escape character is ^] (Ctrl + ])

debian-test-system login: root
Password: 
Linux debian-test-system 6.1.0-21-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.90-1 (2024-05-03) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
root@debian-test-system:~# 

To delete the guest and it’s libvirt resources use the delete task:

$ task delete
task: [delete] virsh vol-delete --pool default debian-test-system
Vol debian-test-system deleted

task: [delete] virsh destroy debian-test-system
Domain 'debian-test-system' destroyed

task: [delete] virsh undefine debian-test-system
Domain 'debian-test-system' has been undefined

task: [delete] virsh net-undefine debian-test-system
Network debian-test-system has been undefined

task: [delete] virsh net-destroy debian-test-system
Network debian-test-system destroyed

The Taskfile.yml in it’s entirety can be found here.