Máquinas vituales en KVM mediante Terraform

Introducción

Para que no se alargue hasta el infinito este post voy a dar por supuesto que tienes un entorno GNU/Linux con qemu y kvm instalado, más libvirt correctamente configurado (opcionalmente con virt-manager), que conoces Terraform y que sabes cómo instalarlo todo. Hay mil recursos donde buscar información aunque te dejo los enlaces a la instalación en cada tecnología.

IaC con Terraform y libvirt

Libvirt es una serie de librerías que permiten gestionar máquinas virtuales en entornos GNU/Linux no sólamente sobre qemu y KVM, sino también en VirtualBox y otros sistemas de virtualización.

Terraform es casi un "standar" de facto de la industria para la gestión de Infraestructura como Código (IaC en inglés) que permite gestionar casi todas las nubes públicas y privadas (AWS, GPC, Azure, OCP...) y además tiene ciertos providers aportados por la comunidad. En este artículo vamos a usar el provider libvirt.

Nos creamos el fichero main.tf con la definición de nuestro provider

terraform {
  required_providers {
    libvirt = {
      source = "dmacvicar/libvirt"
    }
  }
}

provider "libvirt" {
  # Configuration options
  uri = "qemu:///system"
}

y a continuación añadimos la definición de la máquina virtual

resource "libvirt_domain" "virtualMachine" {
  name    = "debianMachine"
  memory  = "512"
  vcpu    = 1

  cloudinit = libvirt_cloudinit_disk.commoninit.id

  network_interface {
    network_name = "default"
  }

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

  console {
    type        = "pty"
    target_type = "virtio"
    target_port = "1"
  }

  disk {
    volume_id = libvirt_volume.OSvolume.id
  }

  running = true
}

resource "libvirt_volume" "base_OSvolume" {
  name   = "debian11-base.qcow2"
  pool   = "default"
  source = "https://cloud.debian.org/images/cloud/bullseye/20221108-1193/debian-11-generic-amd64-20221108-1193.qcow2"
  format = "qcow2"
}

resource "libvirt_volume" "OSvolume" {
  name             = "debian11.qcow2"
  pool             = "default"
  size             = 1024 * 1024 * 1024 * 20 #20Gb
  base_volume_id   = libvirt_volume.base_OSvolume.id
  base_volume_pool = "default"
  format           = "qcow2"
}

data "template_file" "user_data" {
  template = file("./templates/cloud_init.tpl")
  vars = {
    ci_user     = "ansible"
    ssh_keys    = "ssh-rsa AAA------------7878 jdrm@host"
    local_admin = "jdrm"
  }
}

data "template_file" "network_data" {
  template = file("./templates/network_config.tpl")
}

resource "libvirt_cloudinit_disk" "commoninit" {
  name           = "debian.iso"
  user_data      = data.template_file.user_data.rendered
  network_config = data.template_file.network_data.rendered
}

En el directorio templates tenemos los siguientes ficheros:

network_config.tpl

network:
  version: 1
  config:
  - type: physical
    name: eth0
    subnets:
      - type: dhcp

cloud_init.tpl

#cloud-config

ssh_pwauth: True

packages:
  - iotop
  - python3
  - qemu-guest-agent

users:
  - name: ${ci_user}
    gecos: CI user
    ssh_authorized_keys:
      - ${ssh_keys}
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    shell: /bin/bash
    groups: wheel
%{ if local_admin != "" }
  - name: ${local_admin}
    ssh_authorized_keys:
      - ${ssh_keys}
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    shell: /bin/bash
    groups: wheel
%{ endif }

write_files:
  - path: /etc/ssh/sshd_config
    content: |
         Port 22
         Protocol 2
         HostKey /etc/ssh/ssh_host_rsa_key
         HostKey /etc/ssh/ssh_host_dsa_key
         HostKey /etc/ssh/ssh_host_ecdsa_key
         HostKey /etc/ssh/ssh_host_ed25519_key
         UsePrivilegeSeparation yes
         KeyRegenerationInterval 3600
         ServerKeyBits 1024
         SyslogFacility AUTH
         LogLevel INFO
         LoginGraceTime 120
         PermitRootLogin no
         StrictModes yes
         RSAAuthentication yes
         PubkeyAuthentication yes
         IgnoreRhosts yes
         RhostsRSAAuthentication no
         HostbasedAuthentication no
         PermitEmptyPasswords no
         ChallengeResponseAuthentication no
         X11Forwarding yes
         X11DisplayOffset 10
         PrintMotd no
         PrintLastLog yes
         TCPKeepAlive yes
         AcceptEnv LANG LC_*
         Subsystem sftp /usr/lib/openssh/sftp-server
         UsePAM yes
         AllowUsers ${ci_user} %{ if local_admin != "" }${local_admin}%{ endif }

Con esta configuración haremos que nos desplilegue una máquina con una instalación de Debian 11 mínima en un disco de 20Gb asignándole 512 Mb de memoria RAM y una CPU.

Es todo cuestión de ejecutar el despliegue mediante:

terraform init
terraform applay