Table of Contents

Infraestrutura KVM usando Terraform para Laboratório com Ansible

Este tutorial fornece as etapas para criar uma infraestrutura de laboratório em KVM usando Terraform e preparar as VMs para serem gerenciadas via Ansible. As máquinas virtuais terão diferentes sistemas operacionais, incluindo distribuições Debian/Ubuntu e RHEL, e serão configuradas automaticamente usando Cloud-init.

Requisitos

  1. KVM instalado no host.
  2. Terraform para provisionamento de infraestrutura.
  3. Templates de imagens Ubuntu 22.04, Debian 12, e Oracle Linux 9.

Arquivos principais

main.tf

Este arquivo contém a configuração principal para criar a infraestrutura KVM, definindo a rede, volumes e as VMs.

main.tf
terraform {
  required_providers {
    libvirt = {
      source = "dmacvicar/libvirt"
    }
  }
}
 
provider "libvirt" {
  uri = "qemu:///system"
}
 
variable "vms" {
  type = list(map(any))
}
 
resource "libvirt_network" "ansible" {
  name      = "ansible"
  mode      = "nat"
  addresses = ["10.3.4.0/24"]
  autostart = true
  dhcp {
    enabled = false
  }
  dns {
    enabled = true
  }
}
 
resource "libvirt_volume" "os_image" {
  for_each = { for vm in var.vms : vm.name => vm }
 
  name   = each.value.os_image_name
  pool   = each.value.storage_pool
  source = each.value.os_image_url
  format = "qcow2"
}
 
resource "libvirt_volume" "os_datas" {
  for_each = { for vm in var.vms : vm.name => vm }
 
  name           = each.value.os_datas_name
  base_volume_id = libvirt_volume.os_image[each.key].id
  pool           = each.value.storage_pool
  size           = each.value.disksize * 1024 * 1024 * 1024 // GB para bytes
}
 
data "template_file" "user_data_deb" {
  for_each = { for vm in var.vms : vm.name => vm if lookup(vm, "user_data_deb", null) != null }
 
  template = file("${path.module}/${each.value.user_data_deb}")
  vars = {
    hostname = each.value.hostname
  }
}
 
data "template_file" "user_data_rhel" {
  for_each = { for vm in var.vms : vm.name => vm if lookup(vm, "user_data_rhel", null) != null }
 
  template = file("${path.module}/${each.value.user_data_rhel}")
  vars = {
    hostname = each.value.hostname
  }
}
 
data "template_file" "network_config_deb" {
  for_each = { for vm in var.vms : vm.name => vm if lookup(vm, "network_config_deb", null) != null }
 
  template = file("${path.module}/${each.value.network_config_deb}")
  vars = {
    network_ip = each.value.network_ip
  }
}
 
data "template_file" "network_config_rhel" {
  for_each = { for vm in var.vms : vm.name => vm if lookup(vm, "network_config_rhel", null) != null }
 
  template = file("${path.module}/${each.value.network_config_rhel}")
  vars = {
    network_ip = each.value.network_ip
  }
}
 
resource "libvirt_cloudinit_disk" "cloudinit_deb" {
  for_each = { for vm in var.vms : vm.name => vm if lookup(vm, "user_data_deb", null) != null }
 
  name           = "${each.key}_cloudinit.iso"
  user_data      = data.template_file.user_data_deb[each.key].rendered
  network_config = data.template_file.network_config_deb[each.key].rendered
  pool           = each.value.storage_pool
}
 
resource "libvirt_cloudinit_disk" "cloudinit_rhel" {
  for_each = { for vm in var.vms : vm.name => vm if lookup(vm, "user_data_rhel", null) != null }
 
  name           = "${each.key}_cloudinit.iso"
  user_data      = data.template_file.user_data_rhel[each.key].rendered
  network_config = data.template_file.network_config_rhel[each.key].rendered
  pool           = each.value.storage_pool
}
 
locals {
  cloudinit_disks = {
    for vm in var.vms : vm.name => (
      lookup(vm, "user_data_deb", null) != null ?
      libvirt_cloudinit_disk.cloudinit_deb[vm.name].id :
      libvirt_cloudinit_disk.cloudinit_rhel[vm.name].id
    )
  }
}
 
resource "libvirt_domain" "domain" {
  for_each = { for vm in var.vms : vm.name => vm }
 
  name   = each.value.name
  memory = each.value.memory
  vcpu   = each.value.cpu
 
  cpu {
    mode = "host-passthrough"
  }
 
  cloudinit = local.cloudinit_disks[each.key]
 
  network_interface {
    network_name = each.value.network_name
  }
 
  disk {
    volume_id = libvirt_volume.os_datas[each.key].id
  }
 
  console {
    type        = "pty"
    target_type = "virtio"
    target_port = "1"
  }
 
  graphics {
    type        = "spice"
    listen_type = "address"
    autoport    = true
  }
}

terraform.tfvars

Configura as variáveis para definir as VMs no ambiente.

terraform.tfvars
vms = [
  {
    name               = "ansible"
    cpu                = 2
    memory             = 4096
    disksize           = 32
    storage_pool       = "default"
    hostname           = "ansible"
    os_image_name      = "ansible_image.qcow2"
    os_datas_name      = "ansible_datas.qcow2"
    network_name       = "ansible"
    user_data_deb      = "cloud-init-deb.yml"
    network_config_deb = "network-config-deb.yml"
    network_ip         = "10.3.4.10"
    os_image_url       = "/home/gean/kvm/templates/ubuntu-22.04-server-cloudimg-amd64.img"
  },
  {
    name                = "balancer"
    cpu                 = 2
    memory              = 2048
    disksize            = 64
    storage_pool        = "default"
    hostname            = "balancer"
    os_image_name       = "balancer_image.qcow2"
    os_datas_name       = "balancer_datas.qcow2"
    network_name        = "ansible"
    user_data_rhel      = "cloud-init-rhel.yml"
    network_config_rhel = "network-config-rhel.yml"
    network_ip          = "10.3.4.11"
    os_image_url        = "/home/gean/kvm/templates/OL9U3_x86_64-kvm-b220.qcow2"
  },
  {
    name               = "webserver1"
    cpu                = 2
    memory             = 2048
    disksize           = 32
    storage_pool       = "default"
    hostname           = "webserver1"
    os_image_name      = "webserver1_image.qcow2"
    os_datas_name      = "webserver1_datas.qcow2"
    network_name       = "ansible"
    user_data_deb      = "cloud-init-deb.yml"
    network_config_deb = "network-config-deb.yml"
    network_ip         = "10.3.4.12"
    os_image_url       = "/home/gean/kvm/templates/ubuntu-22.04-server-cloudimg-amd64.img"
  },
  {
    name                = "webserver2"
    cpu                 = 2
    memory              = 2048
    disksize            = 64
    storage_pool        = "default"
    hostname            = "webserver2"
    os_image_name       = "webserver2_image.qcow2"
    os_datas_name       = "webserver2_datas.qcow2"
    network_name        = "ansible"
    user_data_rhel      = "cloud-init-rhel.yml"
    network_config_rhel = "network-config-rhel.yml"
    network_ip          = "10.3.4.13"
    os_image_url        = "/home/gean/kvm/templates/OL9U3_x86_64-kvm-b220.qcow2"
  },
  {
    name               = "dbserver"
    cpu                = 2
    memory             = 2048
    disksize           = 32
    storage_pool       = "default"
    hostname           = "dbserver"
    os_image_name      = "dbserver_image.qcow2"
    os_datas_name      = "dbserver_datas.qcow2"
    network_name       = "ansible"
    user_data_deb      = "cloud-init-deb.yml"
    network_config_deb = "network-config-deb.yml"
    network_ip         = "10.3.4.14"
    os_image_url       = "/home/gean/kvm/templates/debian-12-generic-amd64.qcow2"
  }
]

cloud-init-deb.yml

Configuração de inicialização para as VMs baseadas em Debian/Ubuntu.

cloud-init-deb.yml
#cloud-config

hostname: ${hostname}

ssh_pwauth: yes  

users:
  - name: gean
    sudo: ALL=(ALL) NOPASSWD:ALL  
    groups: users, sudo
    shell: /bin/bash
    lock_passwd: false 
    passwd: $6$uGQol.HnKU0nvpEy$YJl94Y7/p1cWZVlu0gsZIeebssh4oHCIQ9VNX721T1/Lx0UbQVbjfbzS2.9.2EGz4Hdxi0ICNKAua8lo/AsuT1 

chpasswd:
  list: |
    root:root
  expire: False 

packages:
  - qemu-guest-agent
  - bash-completion

package_update: true
package_upgrade: true

cloud-init-rhel.yml

Configuração de inicialização para as VMs baseadas em RHEL.

cloud-init-rhel.yml
#cloud-config

ssh_pwauth: yes  

users:
  - name: gean
    sudo: ALL=(ALL) NOPASSWD:ALL  
    groups: users, whell
    shell: /bin/bash
    lock_passwd: false  
    passwd: $6$uGQol.HnKU0nvpEy$YJl94Y7/p1cWZVlu0gsZIeebssh4oHCIQ9VNX721T1/Lx0UbQVbjfbzS2.9.2EGz4Hdxi0ICNKAua8lo/AsuT1  

chpasswd:
  list: |
    root:root
  expire: False  

packages:
  - qemu-guest-agent  
  - bash-completion  

package_update: true  
package_upgrade: true  

runcmd:
  - systemctl enable qemu-guest-agent
  - systemctl start qemu-guest-agent
  - hostnamectl set-hostname ${hostname}

network-config-deb.yml

Configuração de rede para VMs baseadas em Debian/Ubuntu.

network-config-deb.yml
#cloud-config

network:
  version: 2
  ethernets:
    ens3:
      addresses:
        - ${network_ip}/24
      nameservers:
        addresses: 
          - 10.3.4.1
      routes:
        - to: default
          via: 10.3.4.1

network-config-rhel.yml

Configuração de rede para VMs baseadas em RHEL.

network-config-rhel.yml
#cloud-config
network:
  version: 1
  config:
    - type: physical
      name: eth0
      subnets:
        - type: static
          address: ${network_ip}/24
          gateway: 10.3.4.1

Passos para execução

  1. Instale o KVM e Terraform em seu ambiente. Instalação e Configuração: KVM e Terraform
  2. Copie todos os arquivos para um diretório de trabalho.
  3. Execute os seguintes comandos para provisionar a infraestrutura:
terraform init
terraform fmt
terraform validate
terraform plan
terraform apply

4. O Terraform criará as VMs com base na configuração e aplicará as configurações de rede e de sistema.

Considerações Finais

  1. Ajuste os arquivos conforme suas necessidades específicas.
  2. Verifique se as imagens das VMs estão corretamente localizadas e acessíveis.