Automate libvirt Virtual Machine Provisioning with Ansible
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#