
Créer une VM KVM à la main, c’est bien pour apprendre. Mais pour un lab de 5, 10 ou 20 machines ? Terraform automatise la création de VMs libvirt avec des configurations reproductibles. Ce guide vous montre comment faire proprement, avec Cloud-Init et les bonnes pratiques 2026.
À la fin de ce guide, vous aurez :
- Un template Terraform réutilisable pour créer des VMs KVM
- Une configuration Cloud-Init robuste (SSH, hostname, packages)
- Les bonnes pratiques sécurité (pas de
security_driver = none) - Des commandes de debug pour dépanner sans interface graphique
Prérequis
Section intitulée « Prérequis »| Élément | Version | Vérification |
|---|---|---|
| KVM/libvirt | Ubuntu 22.04+ ou équivalent | virsh version |
| Terraform ou OpenTofu | ≥ 1.6.0 | terraform version ou tofu version |
| Image cloud | Ubuntu 24.04 QCOW2 | Téléchargement ci-dessous |
| Clé SSH | Ed25519 recommandée | ls ~/.ssh/id_ed25519.pub |
Ce que vous allez construire
Section intitulée « Ce que vous allez construire »terraform-kvm/├── main.tf # Ressources principales├── variables.tf # Variables configurables├── outputs.tf # Sorties (IP, hostname)├── versions.tf # Providers et versions└── cloudinit/ ├── user-data.yaml # Configuration Cloud-Init └── network-config.yamlArchitecture cible :
┌─────────────────────────────────────────────────────────┐│ Hôte KVM ││ ┌───────────────┐ ┌───────────────┐ ││ │ Pool default │ │ Réseau NAT │ ││ │ (images) │ │ (192.168.122.x)│ ││ └───────┬───────┘ └───────┬───────┘ ││ │ │ ││ ▼ ▼ ││ ┌─────────────────────────────────────┐ ││ │ VM créée par Terraform │ ││ │ • Ubuntu 24.04 (cloud image) │ ││ │ • Cloud-Init configuré │ ││ │ • qemu-guest-agent installé │ ││ └─────────────────────────────────────┘ │└─────────────────────────────────────────────────────────┘Partie 1 : Préparer l’environnement
Section intitulée « Partie 1 : Préparer l’environnement »Vérifier libvirt
Section intitulée « Vérifier libvirt »-
Vérifier que libvirtd fonctionne
Fenêtre de terminal systemctl is-active libvirtdSortie attendue :
active -
Vérifier votre appartenance au groupe libvirt
Fenêtre de terminal groups | grep -E "libvirt|kvm"Si absent :
Fenêtre de terminal sudo usermod -aG libvirt,kvm $USER# Puis déconnectez-vous et reconnectez-vous -
Tester l’accès à qemu:///system
Fenêtre de terminal virsh -c qemu:///system list --allCette commande doit s’exécuter sans sudo. Si elle échoue, voir la section Dépannage.
Télécharger l’image cloud Ubuntu
Section intitulée « Télécharger l’image cloud Ubuntu »Les images cloud sont des QCOW2 minimalistes, optimisées pour Cloud-Init :
# Créer un dossier pour les images de basesudo mkdir -p /var/lib/libvirt/images/base
# Télécharger Ubuntu 24.04 cloud imagesudo wget -O /var/lib/libvirt/images/base/ubuntu-24.04-server-cloudimg-amd64.img \ https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
# Vérifier le téléchargementqemu-img info /var/lib/libvirt/images/base/ubuntu-24.04-server-cloudimg-amd64.imgCréer la clé SSH (si absente)
Section intitulée « Créer la clé SSH (si absente) »# Générer une clé Ed25519 (plus sécurisée que RSA)ssh-keygen -t ed25519 -C "terraform-kvm" -f ~/.ssh/id_ed25519 -N ""Partie 2 : Projet Terraform
Section intitulée « Partie 2 : Projet Terraform »Créer la structure du projet
Section intitulée « Créer la structure du projet »mkdir -p ~/terraform-kvm/cloudinitcd ~/terraform-kvmFichier versions.tf — Providers et versions
Section intitulée « Fichier versions.tf — Providers et versions »Créez versions.tf :
terraform { required_version = ">= 1.6.0"
required_providers { libvirt = { source = "dmacvicar/libvirt" version = "~> 0.9.0" # Version 2025+ avec breaking changes } }}
provider "libvirt" { # Connexion au démon libvirt système (pas session utilisateur) uri = "qemu:///system"}Fichier variables.tf — Configuration
Section intitulée « Fichier variables.tf — Configuration »Créez variables.tf :
variable "vm_name" { description = "Nom de la VM" type = string default = "tf-kvm-01"}
variable "memory_mb" { description = "Mémoire RAM en Mo" type = number default = 2048}
variable "vcpu" { description = "Nombre de vCPU" type = number default = 2}
variable "disk_size_gb" { description = "Taille du disque en Go (expansion de l'image cloud)" type = number default = 20}
variable "base_image_path" { description = "Chemin vers l'image cloud de base" type = string default = "/var/lib/libvirt/images/base/ubuntu-24.04-server-cloudimg-amd64.img"}
variable "ssh_public_key_path" { description = "Chemin vers la clé SSH publique" type = string default = "~/.ssh/id_ed25519.pub"}
variable "network_name" { description = "Nom du réseau libvirt" type = string default = "default"}
variable "pool_name" { description = "Nom du pool de stockage libvirt" type = string default = "default"}Fichier main.tf — Ressources
Section intitulée « Fichier main.tf — Ressources »Créez main.tf :
# ============================================================# Provider libvirt 0.9.x - Configuration complète# ============================================================
# 1) Volume de base (image cloud Ubuntu - copie locale)resource "libvirt_volume" "base" { name = "ubuntu-24.04-base.qcow2" pool = var.pool_name
target = { format = { type = "qcow2" } }
create = { content = { url = var.base_image_path } }}
# 2) Volume OS de la VM (overlay CoW sur l'image de base)resource "libvirt_volume" "os_disk" { name = "${var.vm_name}.qcow2" pool = var.pool_name capacity = var.disk_size_gb * 1024 * 1024 * 1024 # Conversion en bytes
target = { format = { type = "qcow2" } }
# Utilise l'image de base comme backing store (Copy-on-Write) backing_store = { path = libvirt_volume.base.path format = { type = "qcow2" } }}
# 3) Cloud-Init : génération de l'ISO de configurationresource "libvirt_cloudinit_disk" "init" { name = "${var.vm_name}-cloudinit"
user_data = templatefile("${path.module}/cloudinit/user-data.yaml", { hostname = var.vm_name public_key = file(pathexpand(var.ssh_public_key_path)) })
# meta_data est OBLIGATOIRE en 0.9.x meta_data = yamlencode({ instance-id = var.vm_name local-hostname = var.vm_name })
network_config = file("${path.module}/cloudinit/network-config.yaml")}
# 4) Volume pour l'ISO Cloud-Init (upload dans le pool)resource "libvirt_volume" "cloudinit" { name = "${var.vm_name}-cloudinit.iso" pool = var.pool_name
create = { content = { url = libvirt_cloudinit_disk.init.path } }}
# 5) Domaine (VM) - Syntaxe 0.9.x avec bloc devicesresource "libvirt_domain" "vm" { name = var.vm_name type = "kvm" # Type obligatoire en 0.9.x memory = var.memory_mb * 1024 # Attention : en KiB, pas MiB ! vcpu = var.vcpu
os = { type = "hvm" }
features = { acpi = true }
# Bloc devices : structure obligatoire en 0.9.x devices = { # Disque OS disks = [ { # Driver explicite obligatoire pour qcow2 ! driver = { name = "qemu" type = "qcow2" } source = { volume = { pool = var.pool_name volume = libvirt_volume.os_disk.name } } target = { dev = "vda" bus = "virtio" } }, # Disque Cloud-Init (CDROM) { device = "cdrom" driver = { name = "qemu" type = "raw" } source = { volume = { pool = var.pool_name volume = libvirt_volume.cloudinit.name } } target = { dev = "sda" bus = "sata" } } ]
# Interface réseau interfaces = [ { type = "network" model = { type = "virtio" } source = { network = { network = var.network_name } } } ]
# Console série (indispensable pour debug) serials = [ { type = "pty" target = { port = 0 type = "isa-serial" } } ]
# Support graphique (optionnel, pour virt-manager) graphics = [ { spice = { autoport = "yes" } } ] }
running = true}Configuration Cloud-Init
Section intitulée « Configuration Cloud-Init »Cloud-Init est le standard pour configurer les VMs cloud au premier démarrage. Il gère : hostname, utilisateurs, clés SSH, packages, commandes.
Créez cloudinit/user-data.yaml :
#cloud-config
# Hostname de la VMhostname: ${hostname}fqdn: ${hostname}.localmanage_etc_hosts: true
# Utilisateur par défautusers: - name: ubuntu groups: [adm, sudo, docker] shell: /bin/bash sudo: ALL=(ALL) NOPASSWD:ALL lock_passwd: true # Désactive le mot de passe (SSH key only) ssh_authorized_keys: - ${public_key}
# Paquets à installerpackages: - qemu-guest-agent # Indispensable pour récupérer l'IP - curl - vim - htop
# Activer l'agent QEMU au démarrageruncmd: - systemctl enable --now qemu-guest-agent
# Autoriser SSH par clé uniquementssh_pwauth: false
# Mise à jour des paquets au premier bootpackage_update: truepackage_upgrade: false # Évite les surprises de temps
# Message finalfinal_message: | Cloud-init terminé après $UPTIME secondes. Hostname: ${hostname} Connectez-vous avec: ssh ubuntu@<IP>Créez cloudinit/network-config.yaml :
version: 2ethernets: default: match: driver: virtio* # Match n'importe quelle interface virtio dhcp4: true dhcp6: falseFichier outputs.tf — Récupérer les informations
Section intitulée « Fichier outputs.tf — Récupérer les informations »En provider 0.9.x, l’IP n’est plus accessible directement via network_interface[0].addresses. On utilise un data source dédié :
Créez outputs.tf :
# Data source pour récupérer les IPs via DHCP leasesdata "libvirt_domain_interface_addresses" "vm" { domain = libvirt_domain.vm.name source = "lease" # Utilise le serveur DHCP de libvirt
depends_on = [libvirt_domain.vm]}
output "vm_name" { description = "Nom de la VM" value = libvirt_domain.vm.name}
output "vm_id" { description = "ID libvirt de la VM" value = libvirt_domain.vm.id}
output "vm_uuid" { description = "UUID de la VM" value = libvirt_domain.vm.uuid}
# Toutes les IPs (format 0.9.x : interfaces[].addrs[].addr)output "ip_addresses" { description = "Adresses IP de la VM" value = [ for iface in data.libvirt_domain_interface_addresses.vm.interfaces : [for a in iface.addrs : a.addr] ]}
# IP principale pour faciliter l'usageoutput "primary_ip" { description = "Adresse IP principale de la VM" value = try( data.libvirt_domain_interface_addresses.vm.interfaces[0].addrs[0].addr, "Non disponible" )}
output "ssh_command" { description = "Commande SSH pour se connecter" value = try( "ssh ubuntu@${data.libvirt_domain_interface_addresses.vm.interfaces[0].addrs[0].addr}", "IP non disponible - attendre cloud-init puis: tofu refresh" )}Partie 3 : Déployer la VM
Section intitulée « Partie 3 : Déployer la VM »Initialiser et appliquer
Section intitulée « Initialiser et appliquer »-
Initialiser Terraform
Fenêtre de terminal cd ~/terraform-kvmterraform initSortie attendue :
Initializing the backend...Initializing provider plugins...- Finding dmacvicar/libvirt versions matching "~> 0.8"...- Installing dmacvicar/libvirt v0.8.1...Terraform has been successfully initialized! -
Valider la configuration
Fenêtre de terminal terraform validateSortie attendue :
Success! The configuration is valid. -
Prévisualiser les changements
Fenêtre de terminal terraform planVérifiez que 3 ressources seront créées :
libvirt_volume.os_disklibvirt_cloudinit_disk.initlibvirt_domain.vm
-
Appliquer
Fenêtre de terminal terraform applyTapez
yespour confirmer. -
Récupérer l’IP
Fenêtre de terminal terraform output ip_addresses
Vérifier la VM
Section intitulée « Vérifier la VM »# État de la VMvirsh list --all
# Détailsvirsh dominfo tf-kvm-01
# IP via l'agent QEMUvirsh domifaddr tf-kvm-01 --source agentSe connecter en SSH
Section intitulée « Se connecter en SSH »# Récupérer la commande SSHterraform output ssh_command
# Ou directement (remplacez l'IP)ssh ubuntu@192.168.122.xxxPartie 4 : Debug et dépannage
Section intitulée « Partie 4 : Debug et dépannage »Console série (sans réseau)
Section intitulée « Console série (sans réseau) »Si la VM ne démarre pas ou n’obtient pas d’IP, la console série est votre meilleur ami :
virsh console tf-kvm-01Pour quitter : Ctrl + ]
Vérifier Cloud-Init
Section intitulée « Vérifier Cloud-Init »Depuis la console ou SSH :
# Statut globalcloud-init status --long
# Logs détailléssudo cat /var/log/cloud-init-output.log
# Configuration appliquéesudo cat /var/lib/cloud/instance/user-data.txt| Statut | Signification |
|---|---|
status: done | Cloud-Init terminé avec succès |
status: running | Encore en cours (patience) |
status: error | Erreur — consultez les logs |
Problèmes courants
Section intitulée « Problèmes courants »| Symptôme | Cause probable | Solution |
|---|---|---|
Error: virsh: command not found | libvirt non installé | Guide installation |
failed to connect to the hypervisor | libvirtd arrêté ou permissions | sudo systemctl start libvirtd + vérifier groupe |
| IP vide même après 1 minute | qemu-guest-agent non installé | Vérifier Cloud-Init user-data |
Permission denied sur le pool | Droits fichier ou AppArmor | Voir section Sécurité ci-dessous |
| Disque corrompu après apply | Image source modifiée | Utiliser une image “golden” en lecture seule |
Logs libvirtd
Section intitulée « Logs libvirtd »# Logs en temps réeljournalctl -u libvirtd -f
# Erreurs récentesjournalctl -u libvirtd --since "10 minutes ago" | grep -i errorPartie 5 : Sécurité — Ne pas désactiver les protections
Section intitulée « Partie 5 : Sécurité — Ne pas désactiver les protections »Pourquoi les tutoriels le font ?
Section intitulée « Pourquoi les tutoriels le font ? »Quand Terraform (ou vous) crée un fichier dans /var/lib/libvirt/images, il peut avoir les mauvais labels de sécurité. libvirt refuse alors de démarrer la VM avec une erreur de permission.
La solution “facile” (et dangereuse) : désactiver le driver de sécurité.
La vraie solution
Section intitulée « La vraie solution »-
Vérifier le profil AppArmor
Fenêtre de terminal sudo aa-status | grep libvirt -
Ajouter le chemin au profil si nécessaire
Si vous utilisez un pool personnalisé (ex:
/data/vms), modifiez :Fenêtre de terminal sudo nano /etc/apparmor.d/local/usr.sbin.libvirtdAjoutez :
/data/vms/** rwk, -
Recharger AppArmor
Fenêtre de terminal sudo systemctl reload apparmorsudo systemctl restart libvirtd
-
Vérifier le contexte des fichiers
Fenêtre de terminal ls -Z /var/lib/libvirt/images/Les fichiers doivent avoir le contexte
svirt_image_t. -
Restaurer le contexte si incorrect
Fenêtre de terminal sudo restorecon -Rv /var/lib/libvirt/images/ -
Pour un chemin personnalisé
Fenêtre de terminal sudo semanage fcontext -a -t svirt_image_t "/data/vms(/.*)?"sudo restorecon -Rv /data/vms/
Permissions du pool par défaut
Section intitulée « Permissions du pool par défaut »# Vérifier le propriétairels -la /var/lib/libvirt/images/
# Doit appartenir à root:root ou libvirt-qemu selon la distro# Avec les permissions 711 sur le dossierMigration 0.8 → 0.9
Section intitulée « Migration 0.8 → 0.9 »Si vous avez des configurations existantes avec le provider 0.7 ou 0.8, voici les changements à appliquer.
Tableau récapitulatif des changements
Section intitulée « Tableau récapitulatif des changements »| Élément | Provider 0.8.x | Provider 0.9.x |
|---|---|---|
| libvirt_volume | source, format | create.content.url, target.format.type |
| backing file | base_volume_id | backing_store = { path, format } |
| libvirt_cloudinit_disk | meta_data optionnel | meta_data obligatoire |
| libvirt_domain | blocs disk {}, network_interface {} | devices = { disks = [...], interfaces = [...] } |
| memory | en MiB | en KiB (multiplier par 1024) |
| driver qcow2 | auto-détecté | explicite obligatoire |
| récupération IP | network_interface[0].addresses | data.libvirt_domain_interface_addresses |
Exemple de migration : libvirt_volume
Section intitulée « Exemple de migration : libvirt_volume »resource "libvirt_volume" "os_disk" { name = "vm.qcow2" pool = "default" source = "/var/lib/libvirt/images/base.img" format = "qcow2" size = 20 * 1024 * 1024 * 1024}# Image de base (nouvelle ressource séparée)resource "libvirt_volume" "base" { name = "base.qcow2" pool = "default"
target = { format = { type = "qcow2" } }
create = { content = { url = "/var/lib/libvirt/images/base.img" } }}
# Disque VM avec backing storeresource "libvirt_volume" "os_disk" { name = "vm.qcow2" pool = "default" capacity = 20 * 1024 * 1024 * 1024
target = { format = { type = "qcow2" } }
backing_store = { path = libvirt_volume.base.path format = { type = "qcow2" } }}Exemple de migration : libvirt_domain
Section intitulée « Exemple de migration : libvirt_domain »resource "libvirt_domain" "vm" { name = "my-vm" memory = 2048 vcpu = 2
disk { volume_id = libvirt_volume.os_disk.id }
network_interface { network_name = "default" wait_for_lease = true }
cloudinit = libvirt_cloudinit_disk.init.id
console { type = "pty" target_port = "0" target_type = "serial" }
qemu_agent = true}resource "libvirt_domain" "vm" { name = "my-vm" type = "kvm" memory = 2048 * 1024 # KiB ! vcpu = 2
os = { type = "hvm" } features = { acpi = true }
devices = { disks = [ { driver = { name = "qemu", type = "qcow2" } source = { volume = { pool = "default" volume = libvirt_volume.os_disk.name } } target = { dev = "vda", bus = "virtio" } }, { device = "cdrom" driver = { name = "qemu", type = "raw" } source = { volume = { pool = "default" volume = libvirt_volume.cloudinit.name } } target = { dev = "sda", bus = "sata" } } ]
interfaces = [ { type = "network" model = { type = "virtio" } source = { network = { network = "default" } } } ]
serials = [ { type = "pty" target = { port = 0, type = "isa-serial" } } ]
graphics = [{ spice = { autoport = "yes" } }] }
running = true}Partie 6 : Créer plusieurs VMs
Section intitulée « Partie 6 : Créer plusieurs VMs »Avec count
Section intitulée « Avec count »Pour créer 3 VMs identiques, modifiez variables.tf :
variable "vm_count" { description = "Nombre de VMs à créer" type = number default = 3}Et adaptez main.tf (syntaxe 0.9.x) :
# Volume de base (partagé par toutes les VMs)resource "libvirt_volume" "base" { name = "ubuntu-24.04-base.qcow2" pool = var.pool_name
target = { format = { type = "qcow2" } }
create = { content = { url = var.base_image_path } }}
# Volumes OS pour chaque VM (overlay CoW)resource "libvirt_volume" "os_disk" { count = var.vm_count name = "${var.vm_name}-${count.index + 1}.qcow2" pool = var.pool_name capacity = var.disk_size_gb * 1024 * 1024 * 1024
target = { format = { type = "qcow2" } }
backing_store = { path = libvirt_volume.base.path format = { type = "qcow2" } }}
# Cloud-init pour chaque VMresource "libvirt_cloudinit_disk" "init" { count = var.vm_count name = "${var.vm_name}-${count.index + 1}-cloudinit"
user_data = templatefile("${path.module}/cloudinit/user-data.yaml", { hostname = "${var.vm_name}-${count.index + 1}" public_key = file(pathexpand(var.ssh_public_key_path)) })
meta_data = yamlencode({ instance-id = "${var.vm_name}-${count.index + 1}" local-hostname = "${var.vm_name}-${count.index + 1}" })
network_config = file("${path.module}/cloudinit/network-config.yaml")}
# Volumes cloud-initresource "libvirt_volume" "cloudinit" { count = var.vm_count name = "${var.vm_name}-${count.index + 1}-cloudinit.iso" pool = var.pool_name
create = { content = { url = libvirt_cloudinit_disk.init[count.index].path } }}
# Domaines (VMs)resource "libvirt_domain" "vm" { count = var.vm_count name = "${var.vm_name}-${count.index + 1}" type = "kvm" memory = var.memory_mb * 1024 vcpu = var.vcpu
os = { type = "hvm" } features = { acpi = true }
devices = { disks = [ { driver = { name = "qemu", type = "qcow2" } source = { volume = { pool = var.pool_name volume = libvirt_volume.os_disk[count.index].name } } target = { dev = "vda", bus = "virtio" } }, { device = "cdrom" driver = { name = "qemu", type = "raw" } source = { volume = { pool = var.pool_name volume = libvirt_volume.cloudinit[count.index].name } } target = { dev = "sda", bus = "sata" } } ] interfaces = [ { type = "network" model = { type = "virtio" } source = { network = { network = var.network_name } } } ] serials = [{ type = "pty", target = { port = 0, type = "isa-serial" } }] graphics = [{ spice = { autoport = "yes" } }] }
running = true}Adaptez outputs.tf :
# Data source pour chaque VMdata "libvirt_domain_interface_addresses" "vm" { count = var.vm_count domain = libvirt_domain.vm[count.index].name source = "lease"
depends_on = [libvirt_domain.vm]}
output "vms" { description = "Informations sur les VMs créées" value = { for i, vm in libvirt_domain.vm : vm.name => { id = vm.id ip = try(data.libvirt_domain_interface_addresses.vm[i].interfaces[0].addrs[0].addr, "Non disponible") } }}Avec for_each (VMs différentes)
Section intitulée « Avec for_each (VMs différentes) »Pour des VMs avec des specs différentes :
variable "vms" { description = "Map des VMs à créer" type = map(object({ memory = number vcpu = number disk = number })) default = { "master" = { memory = 4096, vcpu = 2, disk = 40 } "worker1" = { memory = 2048, vcpu = 2, disk = 20 } "worker2" = { memory = 2048, vcpu = 2, disk = 20 } }}Partie 7 : Lifecycle et bonnes pratiques
Section intitulée « Partie 7 : Lifecycle et bonnes pratiques »Éviter les chaînes QCOW2 incontrôlées
Section intitulée « Éviter les chaînes QCOW2 incontrôlées »Quand vous faites terraform apply plusieurs fois avec des modifications de disque, vous pouvez créer des chaînes de snapshots involontaires.
Règle : si vous voulez une VM “propre”, faites un terraform destroy puis terraform apply, pas des apply successifs.
Re-exécuter Cloud-Init
Section intitulée « Re-exécuter Cloud-Init »Cloud-Init ne s’exécute qu’une fois par instance. Pour le rejouer :
# Option 1 : Supprimer l'état Cloud-Init (VM existante)sudo cloud-init cleansudo rm /etc/machine-idsudo reboot
# Option 2 : Recréer la VM (recommandé avec Terraform)terraform destroyterraform applySauvegarder le state
Section intitulée « Sauvegarder le state »Le fichier terraform.tfstate contient l’état de votre infrastructure. Sauvegardez-le :
# Sauvegarder localementcp terraform.tfstate terraform.tfstate.backup
# Ou utilisez un backend distant (S3, GitLab, etc.)Partie 8 : Variantes
Section intitulée « Partie 8 : Variantes »Utiliser un bridge au lieu de NAT
Section intitulée « Utiliser un bridge au lieu de NAT »Si vous avez configuré un bridge (voir guide réseaux), modifiez la section interfaces dans le bloc devices :
# Syntaxe 0.9.x pour bridgeinterfaces = [ { type = "bridge" model = { type = "virtio" } source = { bridge = { bridge = "br0" } } }]Ajouter un disque de données
Section intitulée « Ajouter un disque de données »# Volume de données (syntaxe 0.9.x)resource "libvirt_volume" "data_disk" { name = "${var.vm_name}-data.qcow2" pool = var.pool_name capacity = 50 * 1024 * 1024 * 1024 # 50 Go
target = { format = { type = "qcow2" } }}
# Dans libvirt_domain, ajouter le disque dans devices.disks :devices = { disks = [ # ... disque OS existant ... { driver = { name = "qemu", type = "qcow2" } source = { volume = { pool = var.pool_name volume = libvirt_volume.data_disk.name } } target = { dev = "vdb" # Second disque bus = "virtio" } } ] # ... reste de la config ...}Utiliser OpenTofu
Section intitulée « Utiliser OpenTofu »OpenTofu est un fork open source de Terraform, compatible avec les configurations existantes.
# Remplacer terraform par tofutofu inittofu plantofu applyNettoyage
Section intitulée « Nettoyage »Pour supprimer toutes les ressources créées :
terraform destroyVérifiez que tout est supprimé :
virsh list --allvirsh vol-list defaultÀ retenir
Section intitulée « À retenir »- Provider 0.9.x : structure complètement différente de 0.8.x — consultez la section Migration
- Driver qcow2 explicite : sans
driver = { name = "qemu", type = "qcow2" }, la VM ne boot pas - Memory en KiB :
memory = 2048 * 1024pour 2 Go (pas MiB) - meta_data obligatoire :
libvirt_cloudinit_diskrequiertmeta_data = yamlencode({...}) - Cloud-Init réseau : utilisez
match: { driver: virtio* }au lieu deeth0 - Récupération IP : data source
libvirt_domain_interface_addressesavecsource = "lease" - Sécurité : ne désactivez jamais
security_driver— corrigez les permissions - Debug :
virsh console+cloud-init status --long+virsh net-dhcp-leases default