Skip to content

MonolithProjects/terraform-libvirt-vm

Repository files navigation

Libvirt VM Terraform module

GitHub Actions License Terraform

Terraform module for KVM/Libvirt Virtual Machine. This module will create a KVM Virtual Machine(s), configure it using Cloud Init and test the ssh connection. This module is using dmacvicar/libvirt Terraform provider.

What it provides

  • creates one or more VMs
  • one NIC per domain, connected to the network using the bridge interface
  • setup network interface using DHCP or static configuration
  • cloud_init VM(s) configuration (Ubuntu+Netplan complient)
  • optionally add multiple extra disks
  • test the ssh connection

Tested on

  • Ubuntu 20.04 TLS Cloud Image
  • Ubuntu 22.04 TLS Cloud Image

Requirements

Name Version
terraform >= 1.0
libvirt >= 0.7.0

Modules

No modules.

Resources

Name Type
libvirt_cloudinit_disk.commoninit resource
libvirt_domain.virt-machine resource
libvirt_volume.base-volume-qcow2 resource
libvirt_volume.volume-qcow2 resource

Inputs

Name Description Type Default Required
additional_disk_ids List of volume ids list(string) [] no
autostart Autostart the domain bool true no
base_pool_name Name of base OS image string null no
base_volume_name Name of base OS image string null no
bridge Bridge interface string "virbr0" no
cpu_mode CPU mode string "host-passthrough" no
dhcp Use DHCP or Static IP settings bool false no
graphics Graphics type (can be 'spice' or 'vnc') string spice no
index_start From where the indexig start number 1 no
ip_address List of IP addresses list(string)
[
"192.168.123.101"
]
no
ip_gateway IP addresses of a gateway string "192.168.123.1" no
ip_nameserver IP addresses of a nameserver string "192.168.123.1" no
local_admin Admin user without ssh access string "" no
local_admin_passwd Local admin user password string "password_example" no
memory RAM in MB string "1024" no
os_img_url URL to the OS image string "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img" no
pool Storage pool name string "default" no
runcmd Extra commands to be run with cloud init list(string)
[
"[ systemctl, daemon-reload ]",
"[ systemctl, enable, qemu-guest-agent ]",
"[ systemctl, start, qemu-guest-agent ]",
"[ systemctl, restart, systemd-networkd ]"
]
no
share_filesystem n/a
object({
source = string
target = string
readonly = bool
mode = string
})
{
"mode": null,
"readonly": false,
"source": null,
"target": null
}
no
ssh_admin Admin user with ssh access string "ssh-admin" no
ssh_keys List of public ssh keys list(string) [] no
ssh_private_key Private key for SSH connection test (either path to file or key content) string null no
system_volume System Volume size (GB) number 10 no
time_zone Time Zone string "UTC" no
vcpu Number of vCPUs number 1 no
vm_count Number of VMs number 1 no
vm_hostname_prefix VM hostname prefix string "vm" no
xml_override With these variables you can: Enable hugepages; Set USB controllers; Attach USB devices
object({
hugepages = bool
usb_controllers = list(object({
model = string
}))
usb_devices = list(object({
vendor = string
product = string
}))
pci_devices_passthrough = list(object({
src_domain = string
src_bus = string
src_slot = string
src_func = string
dst_domain = string
dst_bus = string
dst_slot = string
dst_func = string
}))
})
{
"hugepages": false,
"usb_controllers": [
{
"model": "piix3-uhci"
}
],
"usb_devices": []
"pci_devices_passthrough": []
}
no
bastion_host ssh bastion host string null no
bastion_user ssh user on bastion host string null no
bastion_ssh_private_key ssh private key for bastion host (either path to file or key content) string null no

Outputs

Name Description
ip_address n/a
name n/a

Example

Example with enable HugePages, Attached USB device, changed USB controller model... :

terraform {
  required_version = ">= 0.13"
    required_providers {
      libvirt = {
        source  = "dmacvicar/libvirt"
      }
    }
}

resource "tls_private_key" "ecdsa-p384-bastion" {
  algorithm   = "ECDSA"
  ecdsa_curve = "P384"
}

provider "libvirt" {
  uri = "qemu+ssh://hero@192.168.165.100/system"
}

module "vm" {
  source  = "MonolithProjects/vm/libvirt"
  version = "1.8.0"

  vm_hostname_prefix = "server"
  vm_count    = 3
  memory      = "2048"
  vcpu        = 1
  pool        = "terra_pool"
  system_volume = 20
  dhcp        = true
  local_admin = "local-admin"
  ssh_admin   = "ci-user"
  ssh_private_key = "~/.ssh/id_ed25519"
  local_admin_passwd = "$6$rounds=4096$xxxxxxxxHASHEDxxxPASSWORD"
  ssh_keys    = [
    "ssh-ed25519 AAAAxxxxxxxxxxxxSSHxxxKEY example",
    ]
  bastion_host = "10.0.0.1"
  bastion_user = "admin"
  bastion_ssh_private_key = tls_private_key.ecdsa-p384-bastion.private_key_pem
  time_zone   = "CET"
  os_img_url  = "file:///home/myuser/ubuntu-20.04-server-cloudimg-amd64.img"
  xml_override = {
      hugepages = true,
      usb_controllers = [
        {
          model = "qemu-xhci"
        }
      ],
      usb_devices = [
        {
          vendor = "0x0bc2",
          product = "0xab28"
        }
      ]
      pci_devices_passthrough = [
        {
          src_domain = "0x0000",
          src_bus    = "0xc1",
          src_slot   = "0x00",
          src_func   = "0x0",
          dst_domain = "0x0000",
          dst_bus    = "0x00",
          dst_slot   = "0x08"
          dst_func   = "0x0"
        },
        {
          src_domain = "0x0000",
          src_bus    = "0xc1",
          src_slot   = "0x00",
          src_func   = "0x1",
          dst_domain = "0x0000",
          dst_bus    = "0x00",
          dst_slot   = "0x09"
          dst_func   = "0x0"
        }
      ]      
    }
}

output "ip_addresses" {
  value = module.nodes
}

Static IP settings... :

terraform {
  required_version = ">= 0.13"
    required_providers {
      libvirt = {
        source  = "dmacvicar/libvirt"
        version = "0.6.3"
      }
    }
}

provider "libvirt" {
  uri = "qemu+ssh://hero@192.168.165.100/system"
}

module "vm" {
  source  = "MonolithProjects/vm/libvirt"
  version = "1.8.0"

  vm_hostname_prefix = "server"
  vm_count    = 3
  memory      = "2048"
  vcpu        = 1
  pool        = "terra_pool"
  system_volume = 20
  share_filesystem = {
    source = "/tmp"
    target = "tmp"
    readonly = false
  }

  dhcp        = false
  ip_address  = [
                  "192.168.165.151",
                  "192.168.165.152",
                  "192.168.165.153"
                ]
  ip_gateway  = "192.168.165.254"
  ip_nameserver = "192.168.165.104"

  local_admin = "local-admin"
  ssh_admin   = "ci-user"
  ssh_private_key = "~/.ssh/id_ed25519"
  local_admin_passwd = "$6$rounds=4096$xxxxxxxxHASHEDxxxPASSWORD"
  ssh_keys    = [
    "ssh-ed25519 AAAAxxxxxxxxxxxxSSHxxxKEY example",
    ]
  time_zone   = "CET"
  os_img_url  = "file:///home/myuser/ubuntu-20.04-server-cloudimg-amd64.img"
}

output "outputs" {
  value = module.nodes
}

The shared directory from the example can be mounted inside the VM with command sudo mount -t 9p -o trans=virtio,version=9p2000.L,rw tmp /host/tmp

Create a VM with an extra disk

# Creates a 50GB extra-data-disk within vms pool
resource "libvirt_volume" "data_volume" {
  pool = "vms"
  name  = "extra-data-disk.qcow2"
  format = "qcow2"
  size = 1024*1024*1024*50
}

module "vm" {
  source  = "MonolithProjects/vm/libvirt"
  version = "1.8.0"

  vm_hostname_prefix = "data-server"
  base_volume_name = "debian-11-base.qcow2"
  base_pool_name = "linked-images"
  vm_count    = 1
  bridge =        "bridge-dmz"
  memory      = "4096"
  vcpu        = 4
  pool        = "vms"
  system_volume = 25
  additional_disk_ids = [ libvirt_volume.data_volume.id ]
  dhcp        = true
  ssh_admin   = "admin"
  ssh_keys    = [
    chomp(file("~/.ssh/id_rsa.pub"))
    ]
  time_zone   = "America/Argentina/Buenos_Aires"
}

output "ip_addresses" {
  value = module.vm
}

Module output example

output_data = {
  "ip_address" = [
    "192.168.165.151",
    "192.168.165.152",
    "192.168.165.153",
  ]
  "name" = [
    "server01",
    "server02",
    "server03",
  ]
}

License

MIT

Author Information

Created in 2020 by Michal Muransky