Aller au contenu principal

Votre Première Infrastructure avec TerraForm

Lorsqu'on combine Terraform avec KVM, on obtient un moyen de se former facilement à Terraform. Cette combinaison va vous permettre de déployer, gérer et mettre à l'échelle des machines virtuelles (VM) de manière efficace et reproductible. Je vous propose donc ici de découvrir comment créer votre première infrastructure informatique Terraform sur votre machine Linux avec KVM / libvirt.

Installation des Dépendances pour KVM

Assurez-vous que KVM soit installé et configuré sur votre machine :

kvm-ok

INFO: /dev/kvm exists
KVM acceleration can be used

Ecriture du code Terraform pour créer un VM KVM

Libvirt est une bibliothèque open-source qui fournit une couche d'abstraction pour gérer diverses technologies de virtualisation dont KVM. Il existe un provider pour Terraform.

Installation du provider libvirt

Commençons par créer un dossier pour notre formation

mkdir test-terraform
cd test-terraform

Pour utiliser Terraform avec KVM, il est nécessaire d'ajouter le provider approprié dans votre fichier de configuration Terraform. Créez un fichier main.tf avec le contenu suivant :

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

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

Ce code initialise Terraform avec le fournisseur libvirt, qui est utilisé pour interagir avec KVM. Lançons l'installation du provider :

terraform init                                                                                                        10:52:37

Initializing the backend...

Initializing provider plugins...
- Finding dmacvicar/libvirt versions matching "0.7.6"...
- Installing dmacvicar/libvirt v0.7.6...
- Installed dmacvicar/libvirt v0.7.6 (self-signed, key ID 0833E38C51E74D26)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Pour que terraform fonctionne avec ce provider, il faut modifier la configuration de libvirtd :

sudo vi /etc/libvirt/qemu.conf

Modifier la ligne : #security_driver = [...] par security_driver = "none". Ne pas oublier d'enlever le # au début de la ligne. On redémarre libvirt pour que cela soit pris en compte.

sudo systemctl restart libvirtd

Création de notre première VM KVM

Voici un exemple permettant de créer une machine Ubuntu en utilisant cloud-init pour sa configuration.

Éditer le fichier main.tf avec ce contenu :

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

terraform {
  required_version = ">= 1.6.6"
}

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

// variables that can be overriden
variable "hostname" { default = "test" }
variable "domain" { default = "example.com" }
variable "ip_type" { default = "dhcp" } # dhcp is other valid type
variable "memoryMB" { default = 1024*1 }
variable "cpu" { default = 1 }

// fetch the latest ubuntu release image from their mirrors
resource "libvirt_volume" "os_image" {
  name = "${var.hostname}-os_image"
  pool = "default"
  source = "jammy-server-cloudimg-amd64.img"
  format = "qcow2"
}

// Use CloudInit ISO to add ssh-key to the instance
resource "libvirt_cloudinit_disk" "commoninit" {
  name = "${var.hostname}-commoninit.iso"
  pool = "default"
  user_data      = data.template_cloudinit_config.config.rendered
  network_config = data.template_file.network_config.rendered
}

data "template_file" "user_data" {
  template = file("${path.module}/cloud_init.cfg")
  vars = {
    hostname = var.hostname
    fqdn = "${var.hostname}.${var.domain}"
    public_key = file("~/.ssh/id_ed25519.pub")
  }
}

data "template_cloudinit_config" "config" {
  gzip = false
  base64_encode = false
  part {
    filename = "init.cfg"
    content_type = "text/cloud-config"
    content = "${data.template_file.user_data.rendered}"
  }
}

data "template_file" "network_config" {
  template = file("${path.module}/network_config_${var.ip_type}.cfg")
}


// Create the machine
resource "libvirt_domain" "domain-ubuntu" {
  # domain name in libvirt, not hostname
  name = "${var.hostname}"
  memory = var.memoryMB
  vcpu = var.cpu

  disk {
    volume_id = libvirt_volume.os_image.id
  }
  network_interface {
    network_name = "default"
  }

  cloudinit = libvirt_cloudinit_disk.commoninit.id

  # IMPORTANT
  # Ubuntu can hang is a isa-serial is not present at boot time.
  # If you find your CPU 100% and never is available this is why
  console {
    type        = "pty"
    target_port = "0"
    target_type = "serial"
  }

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

output "ips" {
  value = libvirt_domain.domain-ubuntu.*.network_interface.0.addresses
}

Créer les deux fichiers cloud_init.cfg et network_config_dhcp.cfg

hostname: ${hostname}
fqdn: ${fqdn}
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:
      - ${public_key}
ssh_pwauth: true
disable_root: false
chpasswd:
  list: |
    ubuntu:linux
  expire: False
packages:
    - qemu-guest-agent
    - python3
bootcmd:
    - [ sh, -c, 'echo $(date) | sudo tee -a /root/bootcmd.log' ]
runcmd:
    - [ sh, -c, 'echo $(date) | sudo tee -a /root/runcmd.log' ]
final_message: "The system is finall up, after $UPTIME seconds"
power_state:
  delay: "+30"
  mode: reboot
  message: Bye Bye
  timeout: 30
  condition: True

le second :

version: 2
ethernets:
  ens3:
    dhcp4: true

Préparation de l'image

Avant de se lancer je vous propose de récupérer l'image et de changer le mot de passe de l'utilisateur root. Cela sera bien pratique pour debugger et surtout d'éviter de télécharger l'image à chaque lancement de la commande terraform apply.

sudo apt install -y libguestfs-tools genisoimage

wget http://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
sudo virt-customize -a jammy-server-cloudimg-amd64.img --root-password password:stephane

[   0.0] Examining the guest ...
[  12.4] Setting a random seed
virt-customize: warning: random seed could not be set for this type of
guest
[  12.4] Setting the machine ID in /etc/machine-id
[  12.4] Setting passwords
[  13.5] Finishing off

Création de la VM

C'est bon, on peut y aller, lancer les commandes suivantes :

terraform init

...

On peut vérifier que le code est correct avec la commande plan :

terraform plan                                                                                                        11:09:52

data.template_file.user_data: Reading...
data.template_file.network_config: Reading...
data.template_file.network_config: Read complete after 0s [id=bcfb7206d4010e07ad6c96e98f4bc3a12b6b3cc1eb45e32b7da63bc0a848c529]
data.template_file.user_data: Read complete after 0s [id=1f6866eba5468c0743185eefa17f36831ee008629d6de4566bf2070754084c9b]
data.template_cloudinit_config.config: Reading...
data.template_cloudinit_config.config: Read complete after 0s [id=3936730158]
terraform apply

...

  # libvirt_volume.os_image will be created
  + resource "libvirt_volume" "os_image" {
      + format = "qcow2"
      + id     = (known after apply)
      + name   = "test-os_image"
      + pool   = "default"
      + size   = (known after apply)
      + source = "jammy-server-cloudimg-amd64.img"
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + ips = [
      + (known after apply),
    ]

Tout est correct, nous pouvons lancer le provisionnement de la VM en répondant yes :

terraform apply

...

Plan: 3 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + ips = [
      + (known after apply),
    ]

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

libvirt_cloudinit_disk.commoninit: Creating...
libvirt_volume.os_image: Creating...
libvirt_volume.os_image: Creation complete after 1s [id=/var/lib/libvirt/images/test-os_image]
libvirt_cloudinit_disk.commoninit: Creation complete after 1s [id=/var/lib/libvirt/images/test-commoninit.iso;8dced368-1cf9-42a5-ac84-1989eb0cc5f6]
libvirt_domain.domain-ubuntu: Creating...
libvirt_domain.domain-ubuntu: Creation complete after 0s [id=3fb29a5e-a025-4d18-8629-a8230006e330]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

ips = [
  tolist([]),
]

Contrôlons si la VM a bien été provisionné :

sudo virsh list --all
 Id   Name                                        State
------------------------------------------------------------
 2    test   running

Récupérons son adresse IP :

terraform refresh && terraform output ips                                                                             11:17:24
data.template_file.network_config: Reading...
data.template_file.user_data: Reading...
data.template_file.network_config: Read complete after 0s [id=bcfb7206d4010e07ad6c96e98f4bc3a12b6b3cc1eb45e32b7da63bc0a848c529]
libvirt_volume.os_image: Refreshing state... [id=/var/lib/libvirt/images/test-os_image]
data.template_file.user_data: Read complete after 0s [id=1f6866eba5468c0743185eefa17f36831ee008629d6de4566bf2070754084c9b]
data.template_cloudinit_config.config: Reading...
data.template_cloudinit_config.config: Read complete after 0s [id=3936730158]
libvirt_cloudinit_disk.commoninit: Refreshing state... [id=/var/lib/libvirt/images/test-commoninit.iso;8dced368-1cf9-42a5-ac84-1989eb0cc5f6]
libvirt_domain.domain-ubuntu: Refreshing state... [id=3fb29a5e-a025-4d18-8629-a8230006e330]

Outputs:

ips = [
  tolist([
    "192.168.122.24",
  ]),
]
[
  tolist([
    "192.168.122.24",
  ]),
]
bob@internal ~/Projects/perso/terraform-kvm

sudo virsh net-dhcp-leases default
 Expiry Time           MAC address         Protocol   IP address           Hostname   Client ID or DUID
------------------------------------------------------------------------------------------------------------------------------------------------
 2022-08-17 12:52:41   52:54:00:e2:65:01   ipv4       192.168.122.66/24   ubuntu     ff:b5:5e:67:ff:00:02:00:00:ab:11:77:6b:c8:0b:77:8a:3e:de

Oui ! Elle bien up ! Maintenant, il suffit de lancer se connecter à la vm

ssh ubuntu@192.168.122.24

The authenticity of host '192.168.122.24 (192.168.122.24)' can't be established.
ED25519 key fingerprint is SHA256:0Jj4oramSFLqMALxFFS23G4cGZ6Ydgce6gXXJAI5CGk.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.122.24' (ED25519) to the list of known hosts.
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-91-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sat Dec 16 10:22:20 UTC 2023

  System load:  0.0029296875      Processes:             94
  Usage of /:   82.7% of 1.96GB   Users logged in:       1
  Memory usage: 21%               IPv4 address for ens3: 192.168.122.24
  Swap usage:   0%


Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status



The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

-bash: warning: setlocale: LC_ALL: cannot change locale (fr_FR.UTF-8)
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@test:~$

On va contrôler le résultat de la commande cloud_init :

sudo cat /var/log/cloud-init-output.log
Cloud-init v. 23.3.3-0ubuntu0~22.04.1 running 'init-local' at Sat, 16 Dec 2023 10:17:30 +0000. Up 5.06 seconds.
Cloud-init v. 23.3.3-0ubuntu0~22.04.1 running 'init' at Sat, 16 Dec 2023 10:17:34 +0000. Up 8.66 seconds.
ci-info: ++++++++++++++++++++++++++++++++++++++Net device info++++++++++++++++++++++++++++++++++++++
ci-info: +--------+------+----------------------------+---------------+--------+-------------------+
ci-info: | Device |  Up  |          Address           |      Mask     | Scope  |     Hw-Address    |
ci-info: +--------+------+----------------------------+---------------+--------+-------------------+
ci-info: |  ens3  | True |       192.168.122.24       | 255.255.255.0 | global | 52:54:00:2e:5d:b3 |
ci-info: |  ens3  | True | fe80::5054:ff:fe2e:5db3/64 |       .       |  link  | 52:54:00:2e:5d:b3 |
ci-info: |   lo   | True |         127.0.0.1          |   255.0.0.0   |  host  |         .         |
ci-info: |   lo   | True |          ::1/128           |       .       |  host  |         .         |
ci-info: +--------+------+----------------------------+---------------+--------+-------------------+
....
Setting up qemu-guest-agent (1:6.2+dfsg-2ubuntu6.15) ...
qemu-guest-agent.service is a disabled or a static unit, not starting it.
Processing triggers for libc-bin (2.35-0ubuntu3.5) ...
Processing triggers for man-db (2.10.2-1) ...
NEEDRESTART-VER: 3.5
NEEDRESTART-KCUR: 5.15.0-91-generic
NEEDRESTART-KEXP: 5.15.0-91-generic
NEEDRESTART-KSTA: 1
Sat Dec 16 10:17:49 UTC 2023
The system is finall up, after 23.86 seconds

Génial !!!

Voilà, maintenant détruisez la :

Décomissionnement de notre VM

terraform destroy -auto-approve

Debug

important

Attention le cloud-init ne se lance que lors du premier lancement et il faut détruire et reconstruire la VM si vous devez le rejouer.

Pour debugger cloud-init, il suffit d'activer cockpit :

sudo dnf -y install cockpit
sudo systemctl enable --now cockpit.socket
sudo firewall-cmd --add-service=cockpit --permanent
sudo firewall-cmd --reload

Ensuite, allez sur votre navigateur, pour accéder à cockpit qui sera disponible via l'url : https://(serverip or hostname):9090/

Il vous suffira ensuite de vous connecter à la machine via la console série avec le compte root. Ensuite les logs de cloud-init se trouve dans le répertoire /var/log.

Conclusion

En résumé, Terraform et KVM représentent ensemble une solution pour se former à la gestion des infrastructures virtualisées. Cette synergie entre l'Infrastructure as Code et la virtualisation vous ouvre des possibilités étendues pour construire et gérer des environnements virtualisés de manière plus efficace, sécurisée et conforme aux exigences actuelles. Je vous encourage à expérimenter et à explorer davantage les capacités de Terraform en consultant le billet sur "comment lancer le provisioning avec Ansible".