Ansible KVM Libvirt — Create and Manage Virtual Machines
Introduction
KVM (Kernel-based Virtual Machine) with libvirt is the standard Linux virtualization stack. With the community.libvirt Ansible collection, you can automate VM creation, network configuration, storage pools, snapshots, and cloud-init provisioning — treating your hypervisors as cattle, not pets.
Prerequisites
# Install collection
ansible-galaxy collection install community.libvirt
# On the hypervisor host
sudo apt install qemu-kvm libvirt-daemon-system virtinst python3-libvirt
# or RHEL/CentOS:
sudo dnf install qemu-kvm libvirt virt-install python3-libvirt
# inventory.yml
all:
children:
hypervisors:
hosts:
kvm01:
ansible_host: 192.168.1.50
ansible_user: root
Create a Virtual Machine
---
- name: Create KVM virtual machines
hosts: hypervisors
become: true
vars:
vm_name: webserver01
vm_vcpus: 2
vm_memory_mb: 2048
vm_disk_gb: 20
vm_os_variant: ubuntu24.04
vm_image: /var/lib/libvirt/images/ubuntu-24.04-server.qcow2
vm_network: default
tasks:
- name: Ensure libvirtd is running
ansible.builtin.systemd:
name: libvirtd
state: started
enabled: true
- name: Check if VM already exists
community.libvirt.virt:
command: list_vms
register: existing_vms
- name: Create VM disk from base image
ansible.builtin.command:
cmd: >
qemu-img create -f qcow2 -F qcow2
-b {{ vm_image }}
/var/lib/libvirt/images/{{ vm_name }}.qcow2
{{ vm_disk_gb }}G
creates: "/var/lib/libvirt/images/{{ vm_name }}.qcow2"
when: vm_name not in existing_vms.list_vms
- name: Define VM
community.libvirt.virt:
command: define
xml: "{{ lookup('template', 'vm.xml.j2') }}"
when: vm_name not in existing_vms.list_vms
- name: Start VM
community.libvirt.virt:
name: "{{ vm_name }}"
state: running
- name: Set VM to autostart
community.libvirt.virt:
name: "{{ vm_name }}"
autostart: true
VM XML Template
<!-- templates/vm.xml.j2 -->
<domain type='kvm'>
<name>{{ vm_name }}</name>
<memory unit='MiB'>{{ vm_memory_mb }}</memory>
<vcpu>{{ vm_vcpus }}</vcpu>
<os>
<type arch='x86_64'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/><apic/>
</features>
<cpu mode='host-passthrough'/>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='/var/lib/libvirt/images/{{ vm_name }}.qcow2'/>
<target dev='vda' bus='virtio'/>
</disk>
<interface type='network'>
<source network='{{ vm_network }}'/>
<model type='virtio'/>
</interface>
<channel type='unix'>
<target type='virtio' name='org.qemu.guest_agent.0'/>
</channel>
<graphics type='vnc' port='-1' autoport='yes'/>
<console type='pty'/>
</devices>
</domain>
Cloud-Init Provisioning
- name: Create cloud-init ISO
ansible.builtin.command:
cmd: >
cloud-localds /var/lib/libvirt/images/{{ vm_name }}-cidata.iso
/tmp/{{ vm_name }}-userdata.yml
/tmp/{{ vm_name }}-metadata.yml
creates: "/var/lib/libvirt/images/{{ vm_name }}-cidata.iso"
- name: Generate user-data
ansible.builtin.copy:
content: |
#cloud-config
hostname: {{ vm_name }}
users:
- name: ansible
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- {{ lookup('file', '~/.ssh/id_rsa.pub') }}
package_update: true
packages:
- qemu-guest-agent
- python3
dest: "/tmp/{{ vm_name }}-userdata.yml"
Manage VM State
- name: Start VM
community.libvirt.virt:
name: "{{ vm_name }}"
state: running
- name: Shutdown VM gracefully
community.libvirt.virt:
name: "{{ vm_name }}"
state: shutdown
- name: Force stop VM
community.libvirt.virt:
name: "{{ vm_name }}"
state: destroyed
- name: Pause VM
community.libvirt.virt:
name: "{{ vm_name }}"
state: paused
- name: Delete VM and disk
block:
- name: Destroy VM
community.libvirt.virt:
name: "{{ vm_name }}"
state: destroyed
ignore_errors: true
- name: Undefine VM
community.libvirt.virt:
command: undefine
name: "{{ vm_name }}"
- name: Remove disk
ansible.builtin.file:
path: "/var/lib/libvirt/images/{{ vm_name }}.qcow2"
state: absent
Storage Pools
- name: Define storage pool
community.libvirt.virt_pool:
command: define
name: vm-storage
xml: |
<pool type='dir'>
<name>vm-storage</name>
<target>
<path>/var/lib/libvirt/images</path>
</target>
</pool>
- name: Start and autostart pool
community.libvirt.virt_pool:
name: vm-storage
state: active
autostart: true
- name: List pools
community.libvirt.virt_pool:
command: list_pools
register: pools
Network Management
- name: Define isolated network
community.libvirt.virt_net:
command: define
name: app-network
xml: |
<network>
<name>app-network</name>
<bridge name='virbr1'/>
<ip address='10.10.10.1' netmask='255.255.255.0'>
<dhcp>
<range start='10.10.10.100' end='10.10.10.200'/>
</dhcp>
</ip>
</network>
- name: Start network
community.libvirt.virt_net:
name: app-network
state: active
autostart: true
Batch VM Creation
---
- name: Create multiple VMs
hosts: hypervisors
become: true
vars:
vms:
- name: web01
vcpus: 2
memory: 2048
disk: 20
- name: web02
vcpus: 2
memory: 2048
disk: 20
- name: db01
vcpus: 4
memory: 4096
disk: 50
tasks:
- name: Get existing VMs
community.libvirt.virt:
command: list_vms
register: existing
- name: Create disks
ansible.builtin.command:
cmd: >
qemu-img create -f qcow2
/var/lib/libvirt/images/{{ item.name }}.qcow2
{{ item.disk }}G
creates: "/var/lib/libvirt/images/{{ item.name }}.qcow2"
loop: "{{ vms }}"
when: item.name not in existing.list_vms
- name: Get VM info
community.libvirt.virt:
command: info
register: vm_info
- name: Show running VMs
ansible.builtin.debug:
msg: "{{ vm_info.keys() | list }}"
Troubleshooting
| Issue | Solution |
|---|---|
| Permission denied | Add user to libvirt group or use become: true |
| VM won't start | Check virsh domblklist <vm> for missing disks |
| No network | Verify default network: virsh net-list --all |
| Python module missing | Install python3-libvirt on target host |
| QEMU/KVM not available | Check `lsmod |
Best Practices
- Use cloud-init for VM provisioning — no manual setup
- qcow2 backing files for thin provisioning — save disk space
- VirtIO drivers for best performance — disk and network
- Autostart critical VMs — survive host reboots
- Snapshot before changes — easy rollback
- QEMU guest agent — enables graceful shutdown and IP reporting
Conclusion
The community.libvirt collection gives you full control over KVM virtual machines, networks, and storage pools through Ansible. Combined with cloud-init and qcow2 backing files, you can spin up entire lab environments in minutes — all defined as code.