Automating KVM VM Creation with Terraform - A flexible infrastructure-as-code solution for provisioning and managing KVM virtual machines with support for multiple operating systems.
- Multi-OS Support: Provision VMs with CentOS, RHEL, or Ubuntu from golden images
- Flexible VM Configuration: Define VM names and assign OS types per VM
- Scalable Infrastructure: Easily create multiple VMs with consistent or custom configurations
- Infrastructure as Code: Define your entire VM cluster in Terraform configuration
- Modular Design: Reusable module structure for different environments (dev, prod, etc.)
- Per-VM Customization: Configure memory, vCPU, disk size, and OS independently for each VM
terraform-kvm/
├── README.md # This file
├── modules/
│ └── vms_cluster/ # Main module for VM provisioning
│ ├── main.tf # Resource definitions (volumes, domains)
│ ├── variables.tf # Input variables
│ ├── outputs.tf # Module outputs
│ └── versions.tf # Provider requirements
└── prod/
├── main.tf # Production environment configuration
└── terraform.tf # Provider configuration
The module handles the core VM provisioning logic:
-
Local Values (
locals):vm_os: Maps each VM name to its OS type (fromvm_os_mappingor usesdefault_os)vm_image_paths: Resolves the correct golden image path for each VM based on its OS
-
Resources:
libvirt_volume: Creates VM disk images based on golden image templateslibvirt_domain: Creates and configures the actual KVM virtual machines
The solution uses three pre-built golden images located at /Users/diablinux/libvirt/images/:
| OS | Image Path | Size |
|---|---|---|
| CentOS | centos-10.qcow2 |
Pre-configured |
| RHEL | rhel10.1-base.qcow2 |
Pre-configured |
| Ubuntu | ubuntu-base.qcow2 |
Pre-configured |
Key variables allow customization:
vm_names(list): Names of VMs to create (e.g.,["web-01", "db-01"])
-
vm_os_mapping(map): Maps VM names to OS types- Example:
{ "web-01" = "ubuntu", "db-01" = "centos" } - VMs without an entry use
default_os
- Example:
-
default_os(string): Default OS for unmapped VMs (default:"ubuntu")
memory_mb(number): RAM per VM in MB (default:2048)vcpu(number): Virtual CPUs per VM (default:2)disk_size(number): Disk size in bytes (default:10737418240= 10GB)
os_images(map): Override golden image paths if needed- Keys:
"centos","rhel","ubuntu" - Default points to
/Users/diablinux/libvirt/images/
- Keys:
Edit prod/main.tf to define your VM cluster:
module "vms_cluster" {
source = "../modules/vms_cluster"
# Define VM names
vm_names = [
"ubuntu-lab-001",
"centos-lab-001",
"rhel-lab-001",
]
# Map each VM to its OS type
vm_os_mapping = {
"ubuntu-lab-001" = "ubuntu"
"centos-lab-001" = "centos"
"rhel-lab-001" = "rhel"
}
# Default OS for unmapped VMs
default_os = "ubuntu"
# Resource specifications
memory_mb = 2048 # 2GB RAM per VM
vcpu = 2 # 2 vCPUs per VM
disk_size = 10737418240 # 10GB disk per VM
}cd prod
terraform initterraform planterraform applyAfter applying, view your VM details:
terraform outputExpected outputs:
prod_vm_names: List of created VM namesprod_vm_ids: Libvirt VM IDsprod_vm_os_mapping: OS type for each VMprod_vm_image_paths: Golden image path used by each VM
vm_names = ["app-01", "app-02", "app-03"]
default_os = "ubuntu"
# No vm_os_mapping needed - all use defaultvm_names = ["web-01", "web-02", "db-01", "cache-01"]
vm_os_mapping = {
"web-01" = "ubuntu"
"web-02" = "ubuntu"
"db-01" = "centos"
"cache-01" = "rhel"
}
default_os = "ubuntu"module "vms_cluster" {
source = "../modules/vms_cluster"
vm_names = ["compute-01", "compute-02"]
# High-resource VMs
memory_mb = 8192 # 8GB RAM
vcpu = 4 # 4 vCPUs
disk_size = 53687091200 # 50GB disk
}The module provides the following outputs:
vm_names: List of created VM namesvm_ids: Libvirt internal IDs for each VMvm_disks: Disk file paths for each VMvm_os_mapping: OS type assigned to each VMvm_image_paths: Golden image path used for each VM
- Terraform >= 1.0
- Libvirt provider configured (
dmacvicar/libvirtv0.9.2+) - KVM/QEMU hypervisor with libvirt daemon running
- Pre-configured libvirt default storage pool
- Golden image files at
/Users/diablinux/libvirt/images/
The provider is configured in prod/terraform.tf. Update the URI as needed:
provider "libvirt" {
uri = "qemu+sshcmd://acabrera@server.local/session"
}For local connection, use: uri = "qemu:///system"
All VMs connect to the nm-bridge bridge network. Ensure this network exists in libvirt:
virsh net-list
virsh net-start nm-bridge- Verify golden images exist at the configured paths
- Check libvirt storage pool permissions
- Ensure
nm-bridgenetwork is active
- Run
terraform initin the prod directory - Verify provider connectivity to libvirt daemon
- Check Terraform state file for conflicts
- Verify VM name is correctly spelled in
vm_os_mapping - Check
os_imagespaths point to valid files - Ensure
default_osvalue is one of:centos,rhel, orubuntu
sudo parted -l
sudo growpart /dev/vda 1
sudo resize2fs /dev/vda1
df -h[Specify your license here]