Aller au contenu
Infrastructure as Code medium
🔐 Alerte sécurité — Incident supply chain Trivy : lire mon analyse de l'attaque

Créer une VM avec Terraform et libvirt

11 min de lecture

logo terraform

Vous avez créé une première VM Terraform. Mais les paramètres exacts du bloc libvirt_domainos.type_machine, devices.disks, virtio, q35 — sont restés un peu obscurs. Que se passe-t-il si vous oubliez le champ type_machine ? Pourquoi utiliser virtio et pas ide ?

Ce guide démonte chaque composant d’une VM libvirt déclarée en Terraform, vous montre comment vérifier la VM créée depuis la CLI libvirt, et vous prépare à personnaliser vos VMs pour des scénarios réels.

  • Anatomie complète du bloc libvirt_volume : pool, format, image source
  • Anatomie complète du bloc libvirt_domain : type, OS, CPU, RAM, disque, réseau
  • Vérifier depuis virsh : virsh list, virsh dominfo, virsh dumpxml
  • Comprendre virtio et pourquoi ce bus est recommandé
  • cloud-init : configurer la VM au premier démarrage sans SSH manuel
  • Première infrastructure créée (guide précédent)
  • Provider libvirt fonctionnel (terraform init réussi)

Analogie : créer une VM avec libvirt, c’est comme assembler un ordinateur. Le libvirt_volume est le disque dur — il stocke l’OS. Le libvirt_domain, c’est le boîtier qui connecte tout : processeur, RAM, disque, carte réseau. On crée d’abord le disque, puis on assemble la machine.

libvirt_volume → le disque dur (fichier qcow2)
libvirt_domain → la VM complète (CPU, RAM, disques, interfaces)
resource "libvirt_volume" "disk" {
name = "lab-vm.qcow2" # (1) Nom du fichier dans le pool libvirt
pool = "default" # (2) Pool de stockage cible
target = {
format = {
type = "qcow2" # (3) Format disque : qcow2 (copy-on-write, recommandé)
}
}
create = {
content = {
url = pathexpand("~/images/ubuntu-24.04-cloudimg.img") # (4) Image source
}
}
}
ChampRôleValeurs courantes
nameNom du fichier dans le poolvm-name.qcow2
poolPool libvirt stockant le volumedefault
format.typeFormat disqueqcow2 (recommandé), raw
create.content.urlImage source (chemin local ou URL)Chemin absolu local

Après l’apply, vérifiez le volume :

Fenêtre de terminal
virsh vol-list default
Name Path
--------------------------------------------------------
lab-vm.qcow2 /var/lib/libvirt/images/lab-vm.qcow2
resource "libvirt_domain" "vm" {
name = "lab-vm" # (1) Nom affiché dans virsh list
type = "kvm" # (2) Hyperviseur : kvm (virtualisation matérielle)
memory = 512 # (3) RAM allouée
memory_unit = "MiB" # (4) Unité : MiB (toujours expliciter)
vcpu = 1 # (5) Nombre de CPUs virtuels
os = {
type = "hvm" # (6) Hardware Virtual Machine (mode KVM complet)
type_arch = "x86_64" # (7) Architecture du guest
type_machine = "q35" # (8) Chipset virtuel : q35 (moderne, PCIe)
}
devices = {
disks = [
{
source = {
file = {
file = libvirt_volume.disk.path # (9) Chemin du volume créé
}
}
target = {
dev = "vda" # (10) Nom du device dans le guest (/dev/vda)
bus = "virtio" # (11) Bus virtio (performances maximales)
}
}
]
interfaces = [
{
model = { type = "virtio" } # (12) Carte réseau virtio
source = { network = { network = "default" } } # (13) Réseau libvirt
}
]
}
}

type = "kvm" — Utilise la virtualisation matérielle (KVM). L’alternative qemu utilise l’émulation pure, beaucoup plus lente. Toujours préférer kvm si le CPU le supporte.

os.type_machine = "q35" — Le chipset virtuel q35 est le standard moderne pour les guests KVM. Il émule un chipset Intel Q35 avec support PCIe. L’ancien chipset pc (i440FX) est encore fonctionnel mais moins capable.

target.bus = "virtio" — Le bus virtio est para-virtuel : le guest sait qu’il tourne dans une VM et utilise des pilotes optimisés. Résultat : performances disque et réseau bien supérieures à ide ou sata.

Après terraform apply, vous disposez de plusieurs commandes virsh pour inspecter :

Fenêtre de terminal
# Lister toutes les VMs
virsh list --all
# Informations détaillées
virsh dominfo lab-vm
# Voir la configuration XML complète
virsh dumpxml lab-vm | head -40
# Voir les interfaces réseau
virsh domiflist lab-vm

Résultat de virsh dominfo :

Id: 1
Name: lab-vm
UUID: a1b2c3d4-...
OS Type: hvm
State: running
CPU(s): 1
Max memory: 524288 KiB
Used memory: 524288 KiB

Une VM créée sans cloud-init démarre mais n’est pas accessible par SSH : aucun utilisateur défini, aucune clé autorisée. cloud-init configure la VM au premier démarrage depuis un fichier de données.

Analogie : cloud-init est comme une clé USB de configuration que vous branchez à la VM au premier démarrage. Elle lit les instructions (créer un utilisateur, injecter une clé SSH) et les applique automatiquement.

La configuration cloud-init passe par un volume de type cdrom (disque ISO) :

# Données cloud-init : utilisateur et clé SSH
resource "libvirt_cloudinit_disk" "init" {
name = "lab-vm-init.iso" # Nom du fichier ISO dans le pool
pool = "default"
user_data = <<-EOF
#cloud-config
users:
- name: ubuntu
groups: sudo
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- ${trimspace(file(pathexpand("~/.ssh/id_ed25519.pub")))}
package_update: true
EOF
}
resource "libvirt_domain" "vm" {
# ... (paramètres précédents)
cloudinit = libvirt_cloudinit_disk.init.id # Attacher le disque cloud-init
}
versions.tf
terraform {
required_version = ">= 1.11.0"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "~> 0.8"
}
}
}
provider "libvirt" {
uri = "qemu:///system"
}
main.tf
locals {
vm_name = "lab-vm"
}
resource "libvirt_volume" "disk" {
name = "${local.vm_name}.qcow2"
pool = "default"
target = { format = { type = "qcow2" } }
create = { content = { url = pathexpand("~/images/ubuntu-24.04-cloudimg.img") } }
}
resource "libvirt_cloudinit_disk" "init" {
name = "${local.vm_name}-init.iso"
pool = "default"
user_data = <<-EOF
#cloud-config
users:
- name: ubuntu
groups: sudo
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- ${trimspace(file(pathexpand("~/.ssh/id_ed25519.pub")))}
EOF
}
resource "libvirt_domain" "vm" {
name = local.vm_name
type = "kvm"
memory = 512
memory_unit = "MiB"
vcpu = 1
cloudinit = libvirt_cloudinit_disk.init.id
os = { type = "hvm", type_arch = "x86_64", type_machine = "q35" }
devices = {
disks = [{
source = { file = { file = libvirt_volume.disk.path } }
target = { dev = "vda", bus = "virtio" }
}]
interfaces = [{
model = { type = "virtio" }
source = { network = { network = "default" } }
}]
}
}
output "vm_name" {
value = libvirt_domain.vm.name
}
  • Une VM libvirt nécessite deux ressources minimum : libvirt_volume + libvirt_domain.
  • Le format qcow2 et le bus virtio sont les choix recommandés pour les performances.
  • Le chipset q35 est le standard moderne pour les guests KVM.
  • cloud-init est indispensable pour rendre la VM accessible via SSH au premier démarrage.
  • virsh list, virsh dominfo, virsh dumpxml permettent d’inspecter une VM après sa création.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn