Automate libvirt Virtual Machine Provisioning with Ansible

Info
This is part 2 of this post.

Table of Contents

Let’s just automate the process what is discussed in part 1 using ansible. This ansible playbook can also loop multiple VM to create.

Environment

Docker/Podman

I’ve dockerized ansible - tailored to the specific task. Check Dockerfile if you want to run it locally.

First clone the repo.

1git clone https://github.com/mcbtaguiad/libvirt-cloudinit-ansible.git

Build the package or just pull the image from gihub registry.

1cd libvirt-cloudinit-ansible    
2docker compose build 
3docker compose up -d
4
5# or
6docker compose up -d

Image

For this example we will use ubuntu noble cloud image.

1cd /srv/nvme/libvirt # my custom pool
2wget https://cloud-images.ubuntu.com/noble/20260225/noble-server-cloudimg-amd64.img

Hypervisor

Edit inventory file and add your hypervisor.

1nvim intentory/host.ini 
1[hypervisor]
2kvm01 ansible_host=192.168.254.191 ansible_user=mcbtaguiad

Create VM

Planning

Now let’s create VMs, in this example we will create 2 VMs. First edit create-vm.yml playbook.

create-vm.yml

 1---
 2- name: Create multiple VMs
 3  hosts: hypervisor
 4  # become: true          # use sudo for all tasks
 5  # become_method: sudo
 6  # ansible_become_pass: "YOUR_SUDO_PASSWORD"
 7  roles:
 8    - role: virt_vm
 9      vars:
10        vm_name: worker01
11        vm_ip: 192.168.254.202
12        vm_vcpus: 2
13        vm_memory: 4096
14        vm_disk_size: 50G
15        disk_path: /srv/nvme/libvirt #/var/lib/libvirt/images
16        base_image: noble-server-cloudimg-amd64.img
17        os_variant: ubuntu24.04
18
19    - role: virt_vm
20      vars:
21        vm_name: worker02
22        vm_dhcp: true

In here worker01 is set to static IP address and worker03 is set to DHCP.

Variable

For more variable check roles/virt_vm/defaults/main.yaml

main.yaml

 1vm_name: srvmnldebvm001
 2
 3vm_user: mcbtaguiad
 4vm_memory: 4096
 5vm_vcpus: 2
 6vm_disk_size: 50G
 7
 8ssh_public_key: 
 9  - "sh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDtf3e9lQR1uAypz4nrq2nDj0DvZZGONku5wO+M87wUVTistrY8REsWO2W1N"
10
11#ssh_public_key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
12
13vm_interface: enp1s0
14
15# Networking
16vm_dhcp: false
17vm_ip: 192.168.254.201
18vm_prefix: 24
19vm_gateway: 192.168.254.254
20vm_dns:
21  - 8.8.8.8
22vm_network_bridge: br0
23
24disk_path: /srv/nvme/libvirt #/var/lib/libvirt/images
25base_image: noble-server-cloudimg-amd64.img
26os_variant: ubuntu24.04

Run Playbook

Exec into the container.

1docker exec -it ansible-libvirt bash

IP of the VM is printed at the end of the loop. I added this in case you set DHCP to true.

1ansible-playbook create-vm.yml -i inventory/hosts.ini
 1PLAY [Create multiple VMs] ******************************************************************************************************************************************************************************************************************
 2
 3TASK [Gathering Facts] **********************************************************************************************************************************************************************************************************************
 4[WARNING]: Host 'kvm01' is using the discovered Python interpreter at '/run/current-system/sw/bin/python3.13', but future installation of another Python interpreter could cause a different interpreter to be discovered. See https://docs.ansible.com/ansible-core/2.20/reference_appendices/interpreter_discovery.html for more information.
 5ok: [kvm01]
 6
 7TASK [virt_vm : Create QCOW2 disk from backing image] ***************************************************************************************************************************************************************************************
 8changed: [kvm01]
 9
10TASK [virt_vm : Render user-data] ***********************************************************************************************************************************************************************************************************
11changed: [kvm01]
12
13TASK [virt_vm : Render meta-data] ***********************************************************************************************************************************************************************************************************
14changed: [kvm01]
15
16TASK [virt_vm : Render network-config] ******************************************************************************************************************************************************************************************************
17changed: [kvm01]
18
19TASK [virt_vm : Generate cloud-init seed ISO] ***********************************************************************************************************************************************************************************************
20changed: [kvm01]
21
22TASK [virt_vm : Install VM using virt-install] **********************************************************************************************************************************************************************************************
23changed: [kvm01]
24
25TASK [virt_vm : Get VM IP - wait for vm and qemu guest to start] ****************************************************************************************************************************************************************************
26FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (20 retries left).
27FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (19 retries left).
28FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (18 retries left).
29FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (17 retries left).
30FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (16 retries left).
31FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (15 retries left).
32FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (14 retries left).
33FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (13 retries left).
34FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (12 retries left).
35changed: [kvm01]
36
37TASK [virt_vm : Show VM IP] *****************************************************************************************************************************************************************************************************************
38ok: [kvm01] => {
39    "msg": "VM worker01 IP is 192.168.254.202"
40}
41
42TASK [virt_vm : Create QCOW2 disk from backing image] ***************************************************************************************************************************************************************************************
43changed: [kvm01]
44
45TASK [virt_vm : Render user-data] ***********************************************************************************************************************************************************************************************************
46changed: [kvm01]
47
48TASK [virt_vm : Render meta-data] ***********************************************************************************************************************************************************************************************************
49changed: [kvm01]
50
51TASK [virt_vm : Render network-config] ******************************************************************************************************************************************************************************************************
52changed: [kvm01]
53
54TASK [virt_vm : Generate cloud-init seed ISO] ***********************************************************************************************************************************************************************************************
55changed: [kvm01]
56
57TASK [virt_vm : Install VM using virt-install] **********************************************************************************************************************************************************************************************
58changed: [kvm01]
59
60TASK [virt_vm : Get VM IP - wait for vm and qemu guest to start] ****************************************************************************************************************************************************************************
61FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (20 retries left).
62FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (19 retries left).
63FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (18 retries left).
64FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (17 retries left).
65FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (16 retries left).
66FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (15 retries left).
67FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (14 retries left).
68FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (13 retries left).
69FAILED - RETRYING: [kvm01]: Get VM IP - wait for vm and qemu guest to start (12 retries left).
70changed: [kvm01]
71
72TASK [virt_vm : Show VM IP] *****************************************************************************************************************************************************************************************************************
73ok: [kvm01] => {
74    "msg": "VM worker02 IP is 192.168.254.201"
75}
76
77PLAY RECAP **********************************************************************************************************************************************************************************************************************************
78kvm01                      : ok=17   changed=14   unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Verify

1$ virsh list      
2 Id   Name       State
3--------------------------
4 20   worker01   running
5 21   worker02   running

Delete VM

Edit the delete-vm.yml file and add the VM name to the list.

delete-vm.yml

 1---
 2- name: Delete multiple VMs
 3  hosts: hypervisor
 4  # become: true
 5  vars:
 6    vm_list:
 7      - worker01
 8      - worker02
 9    disk_path: "/srv/nvme/libvirt" #/var/lib/libvirt/images
10  tasks:
11    - name: Delete multiple VMs
12      with_items: "{{ vm_list }}"
13      include_role:
14        name: virt_vm_delete
15      vars:
16        vm_name: "{{ item }}"
17      loop: "{{ vm_list }}"

Run the playbook.

1ansible-playbook delete-vm.yml -i inventory/hosts.ini
 1PLAY [Delete multiple VMs] ******************************************************************************************************************************************************************************************************************
 2
 3TASK [Gathering Facts] **********************************************************************************************************************************************************************************************************************
 4[WARNING]: Host 'kvm01' is using the discovered Python interpreter at '/run/current-system/sw/bin/python3.13', but future installation of another Python interpreter could cause a different interpreter to be discovered. See https://docs.ansible.com/ansible-core/2.20/reference_appendices/interpreter_discovery.html for more information.
 5ok: [kvm01]
 6
 7TASK [Delete multiple VMs] ******************************************************************************************************************************************************************************************************************
 8included: virt_vm_delete for kvm01 => (item=worker01)
 9included: virt_vm_delete for kvm01 => (item=worker02)
10
11TASK [virt_vm_delete : Fail if vm_name is not defined] **************************************************************************************************************************************************************************************
12skipping: [kvm01]
13
14TASK [virt_vm_delete : Check if VM exists] **************************************************************************************************************************************************************************************************
15changed: [kvm01]
16
17TASK [virt_vm_delete : Shut down VM if running] *********************************************************************************************************************************************************************************************
18changed: [kvm01]
19
20TASK [virt_vm_delete : Force destroy VM if still running] ***********************************************************************************************************************************************************************************
21changed: [kvm01]
22
23TASK [virt_vm_delete : Undefine VM] *********************************************************************************************************************************************************************************************************
24changed: [kvm01]
25
26TASK [virt_vm_delete : Delete VM disk] ******************************************************************************************************************************************************************************************************
27changed: [kvm01]
28
29TASK [virt_vm_delete : Delete temporary cloud-init user-data] *******************************************************************************************************************************************************************************
30changed: [kvm01]
31
32TASK [virt_vm_delete : Delete temporary cloud-init meta-data] *******************************************************************************************************************************************************************************
33changed: [kvm01]
34
35TASK [virt_vm_delete : Delete temporary cloud-init network-config] **************************************************************************************************************************************************************************
36changed: [kvm01]
37
38TASK [virt_vm_delete : Delete Seed ISO] *****************************************************************************************************************************************************************************************************
39changed: [kvm01]
40
41TASK [virt_vm_delete : Fail if vm_name is not defined] **************************************************************************************************************************************************************************************
42skipping: [kvm01]
43
44TASK [virt_vm_delete : Check if VM exists] **************************************************************************************************************************************************************************************************
45changed: [kvm01]
46
47TASK [virt_vm_delete : Shut down VM if running] *********************************************************************************************************************************************************************************************
48changed: [kvm01]
49
50TASK [virt_vm_delete : Force destroy VM if still running] ***********************************************************************************************************************************************************************************
51changed: [kvm01]
52
53TASK [virt_vm_delete : Undefine VM] *********************************************************************************************************************************************************************************************************
54changed: [kvm01]
55
56TASK [virt_vm_delete : Delete VM disk] ******************************************************************************************************************************************************************************************************
57changed: [kvm01]
58
59TASK [virt_vm_delete : Delete temporary cloud-init user-data] *******************************************************************************************************************************************************************************
60changed: [kvm01]
61
62TASK [virt_vm_delete : Delete temporary cloud-init meta-data] *******************************************************************************************************************************************************************************
63changed: [kvm01]
64
65TASK [virt_vm_delete : Delete temporary cloud-init network-config] **************************************************************************************************************************************************************************
66changed: [kvm01]
67
68TASK [virt_vm_delete : Delete Seed ISO] *****************************************************************************************************************************************************************************************************
69changed: [kvm01]
70
71PLAY RECAP **********************************************************************************************************************************************************************************************************************************
72kvm01                      : ok=21   changed=18   unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
73
74root@930ca2694fba:/ansible#