Phase 1: Host Preparation

1. Install KVM and Libvirt tools (Commands for Ubuntu/Debian)

1
2
sudo apt update
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst libvirt-daemon virt-manager

2. Install ISO generation utility Terraform uses this to generate the Cloud-Init ISO that injects your SSH keys.

1
sudo apt install -y genisoimage

3. Permissions Add your user to the libvirt and kvm groups so Terraform can run without sudo.

1
2
sudo usermod -aG libvirt $USER
sudo usermod -aG kvm $USER

Phase 2: Project Setup

Create a clean directory for the project and download a cloud-native image. We use Cloud Images (qcow2) rather than installer ISOs because they support automatic configuration on boot.

1
2
3
4
5
mkdir tf-libvirt-lab
cd tf-libvirt-lab

# Download Ubuntu 22.04 Cloud Image
wget https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img

Phase 3: The Terraform Configuration

Create a file named main.tf.

Copy and paste the following configuration:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
terraform {
  required_providers {
    libvirt = {
      source = "dmacvicar/libvirt"
      version = "0.7.1"
    }
  }
}

# Connect to the local system QEMU
provider "libvirt" {
  uri = "qemu:///system"
}

resource "libvirt_network" "lab_net" {
  name      = "lab_net"
  mode      = "nat"
  domain    = "lab.local"
  addresses = ["10.10.10.0/24"]

  dhcp {
    enabled = true
  }
  
  dns {
    enabled = true
  }
}

resource "libvirt_pool" "lab_pool" {
  name = "lab_pool"
  type = "dir"
  # Use a path your user has write access to, or standard libvirt path
  path = "/var/lib/libvirt/images/terraform-lab"
}

# The Base Image (The template we downloaded)
resource "libvirt_volume" "os_base" {
  name   = "ubuntu-base.qcow2"
  pool   = libvirt_pool.lab_pool.name
  source = "./ubuntu-22.04-server-cloudimg-amd64.img"
  format = "qcow2"
}

# The VM Disk (Cloned from base, resized to 10GB)
resource "libvirt_volume" "vm_disk" {
  name           = "vm-disk-1.qcow2"
  base_volume_id = libvirt_volume.os_base.id
  pool           = libvirt_pool.lab_pool.name
  size           = 10737418240 # 10GB
}

data "template_file" "user_data" {
  template = <<EOF
#cloud-config
hostname: terraform-vm
fqdn: terraform-vm.lab.local
manage_etc_hosts: true
users:
  - name: ubuntu
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: users, admin
    home: /home/ubuntu
    shell: /bin/bash
    lock_passwd: false
    ssh-authorized-keys:
      - ${file("~/.ssh/id_rsa.pub")}
ssh_pwauth: true
disable_root: false
chpasswd:
  list: |
     ubuntu:password
  expire: False
package_update: true
packages:
 - qemu-guest-agent
runcmd:
 - [ systemctl, start, qemu-guest-agent ]
EOF
}

resource "libvirt_cloudinit_disk" "commoninit" {
  name      = "commoninit.iso"
  pool      = libvirt_pool.lab_pool.name
  user_data = data.template_file.user_data.rendered
}

resource "libvirt_domain" "vm_01" {
  name   = "ubuntu-terraform-01"
  memory = "2048"
  vcpu   = 2

  cloudinit = libvirt_cloudinit_disk.commoninit.id

  network_interface {
    network_id     = libvirt_network.lab_net.id
    wait_for_lease = true
  }

  console {
    type        = "pty"
    target_port = "0"
    target_type = "serial"
  }

  disk {
    volume_id = libvirt_volume.vm_disk.id
  }

  graphics {
    type        = "spice"
    listen_type = "address"
    autoport    = true
  }
}

# Output the IP so we know where to SSH
output "ip" {
  value = libvirt_domain.vm_01.network_interface[0].addresses
}

Phase 4: Execution

1. SSH Key Verification Ensure you have a public key at ~/.ssh/id_rsa.pub. If not, generate one:

1
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""

2. Initialize Terraform This downloads the dmacvicar/libvirt provider.

1
terraform init

3. Review the Plan

1
terraform plan

4. Apply Build the infrastructure.

1
terraform apply

Phase 5: Verification

Once the apply is complete, Terraform will output the IP address.

1. Test Connection

1
2
# Replace X.X.X.X with the Output IP
ssh [email protected]

2. Inspect in KVM Open virt-manager. You will see the VM running, the storage pool created, and the network active.

3. Cleanup To tear down the environment:

1
terraform destroy