
Vous avez créé 3 VMs avec count, et vous supprimez celle du milieu.
Terraform réindexe : vm[2] devient vm[1], ce qui force une
recréation de toutes les suivantes. Ce comportement détruit et recrée des
ressources qui n’ont pas changé.
for_each résout ce problème en adressant chaque instance par une
clé nommée plutôt qu’un index numérique. Supprimer la VM "db"
ne déplace pas les VM "web" ou "cache" — chacune a une identité
stable dans le state. C’est l’approche recommandée pour toutes les
ressources ayant un rôle distinct, par opposition à count réservé
aux instances vraiment interchangeables. for_each s’alimente d’une
map ou d’un set — et les expressions for permettent de
construire ces collections depuis des données brutes.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »for_eachavec une map :each.keyeteach.valuefor_eachavec un set : itérer sur une liste de chaînes- Adressage :
ressource.nom["clé"] - Expressions
fordans les outputs pour transformer les résultats - Quand
for_eachplutôt quecount
Prérequis
Section intitulée « Prérequis »countcompris (count Terraform)- Variables de type
mapetobjectconnues (variables Terraform)
L’idée derrière for_each
Section intitulée « L’idée derrière for_each »Pensez à une boucle for classique iterant sur un dictionnaire Python :
vms = { "web": {"memory": 512, "vcpu": 1}, "db": {"memory": 1024, "vcpu": 2}, "cache": {"memory": 512, "vcpu": 1}}
for name, config in vms.items(): create_vm(f"vm-{name}", config["memory"], config["vcpu"]) # vm-web, vm-db, vm-cache — avec une identité stablefor_each fait exactement cela en Terraform : au lieu de copier un bloc pour chaque VM, vous dites « appliquez ce bloc à chaque entrée de cette map ». Chaque instance reste identifiée par sa clé, pas par un index.
Syntaxe minimale
Section intitulée « Syntaxe minimale »La logique : « pour chaque entrée (clé, valeur) de cette map, créer une ressource personnalisée selon la clé et la valeur ».
resource "libvirt_domain" "vm" { for_each = var.vms # ← Pour chaque entrée dans var.vms
name = "vm-${each.key}" # ← each.key = clé de la map ("web", "db", "cache") memory = each.value.memory # ← each.value = l'objet sous la clé vcpu = each.value.vcpu}En HCL :
for_each = map_ou_set: itère sur une collectioneach.key: la clé récupéréeeach.value: la valeur associée à cette clé
Exemple : VMs web, db, cache
Section intitulée « Exemple : VMs web, db, cache »variable "vms" { type = map(object({ memory = number vcpu = number })) default = { "web" = { memory = 512, vcpu = 1 } "db" = { memory = 1024, vcpu = 2 } "cache" = { memory = 512, vcpu = 1 } }}
# main.tfresource "libvirt_volume" "disk" { for_each = var.vms
name = "lab09-${each.key}.qcow2" pool = var.pool target = { format = { type = "qcow2" } } create = { content = { url = var.image_path } }}
resource "libvirt_domain" "vm" { for_each = var.vms
name = "lab09-${each.key}" memory = each.value.memory memory_unit = "MiB" vcpu = each.value.vcpu
devices = { disks = [ { # Référence croisée par clé — pas par index ! source = { file = { file = libvirt_volume.disk[each.key].path } } target = { dev = "vda", bus = "virtio" } } ] }}Résultat de l’apply :
libvirt_domain.vm["web"]: Creation complete [name=lab09-web]libvirt_domain.vm["db"]: Creation complete [name=lab09-db]libvirt_domain.vm["cache"]: Creation complete [name=lab09-cache]
Apply complete! Resources: 6 added, 0 changed, 0 destroyed.
Outputs:
vm_memory = { "cache" = 512 "db" = 1024 "web" = 512}vm_names = { "cache" = "lab09-cache" "db" = "lab09-db" "web" = "lab09-web"}Adressage par clé dans le state
Section intitulée « Adressage par clé dans le state »Quand vous utilisez for_each, chaque ressource est identifiée par sa clé — jamais réindexée. Voici comment accéder à une instance spécifique :
Addressage direct : ressource.nom["clé"]
# Référencer le disque de la VM "db" depuis la VM "db"libvirt_volume.disk["db"].path
# Utiliser la clé courante dans le bloc for_each lui-mêmelibvirt_volume.disk[each.key].pathDans terraform state list, les instances apparaissent avec leur clé nommée — pas d’index :
libvirt_domain.vm["cache"]libvirt_domain.vm["db"]libvirt_domain.vm["web"]Pour inspecter ou détruire une instance spécifique sans toucher aux autres :
terraform state show 'libvirt_domain.vm["db"]' # Voir la configuration de la VM "db"terraform destroy -target 'libvirt_domain.vm["cache"]' # Détruire UNIQUEMENT "cache"for_each avec un set de chaînes
Section intitulée « for_each avec un set de chaînes »Quand les instances n’ont pas de configuration différente — juste un nom simple :
variable "hostnames" { type = set(string) default = ["web", "db", "cache"]}
resource "libvirt_volume" "disk" { for_each = var.hostnames # ← set de 3 chaînes
name = "lab09-${each.key}.qcow2" # ← each.key = la valeur ("web", "db", "cache") # Pour un set, each.key == each.value — tous deux sont la chaîne}Expressions for dans les outputs
Section intitulée « Expressions for dans les outputs »Pour transformer une map de ressources en map d’attributs, utilisez une expression for :
output "vm_names" { value = { for k, v in libvirt_domain.vm : k => v.name } # Résultat : { "cache" = "lab09-cache", "db" = "lab09-db", "web" = "lab09-web" }}Lisez cette expression : « pour chaque clé k et valeur v dans la map libvirt_domain.vm, créer une paire k => v.name ».
Les expressions for sont couvertes en détail dans le guide dédié (boucles for).
Ajouter une instance sans toucher aux autres
Section intitulée « Ajouter une instance sans toucher aux autres »Un avantage clé de for_each : ajouter une nouvelle instance revient à ajouter une ligne en map. Les anciennes instances ne bougent pas.
Ajoutons "proxy" à la variable :
default = { "web" = { memory = 512, vcpu = 1 } "db" = { memory = 1024, vcpu = 2 } "cache" = { memory = 512, vcpu = 1 } "proxy" = { memory = 256, vcpu = 1 } # ← ajoutée}terraform applylibvirt_volume.disk["proxy"]: Creating...libvirt_domain.vm["proxy"]: Creating...
Plan: 2 to add, 0 to change, 0 to destroy.Seuls les deux objets "proxy" sont créés. Les VM web, db, cache ne
sont pas touchées — c’est la différence fondamentale avec count.
count vs for_each : quand choisir ?
Section intitulée « count vs for_each : quand choisir ? »| Critère | count | for_each |
|---|---|---|
| Instances identiques | ✅ Adapté | Possible mais verbeux |
| Instances avec config différente | ❌ Difficile | ✅ Adapté |
| Suppression d’une instance du milieu | Réindexe les suivantes | ✅ Stable, seule la clé supprimée est détruite |
| Ajout d’une instance | Peut recréer les suivantes | ✅ Seule la nouvelle est créée |
| Ressource optionnelle (0 ou 1) | count = condition ? 1 : 0 | Possible mais verbeux |
| Adressage dans le state | ressource.nom[0] | ressource.nom["clé"] |
Règle simple : si les instances ont des noms ou des configurations
différentes, utilisez for_each. Si elles sont vraiment interchangeables
(N workers identiques), count suffit.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
for_each does not allow lists | Passer une liste directement | Convertir avec toset(liste) |
each.key non disponible | Bloc sans for_each déclaré | Vérifier que for_each est bien défini |
| Ressource détruite et recrée à un changement de map | Clé modifiée | Ne pas renommer une clé — utiliser terraform state mv |
| Output en map au lieu de liste | Comportement normal avec for_each | Utiliser values(ressource.nom[*].attr) pour une liste |
À retenir
Section intitulée « À retenir »for_eachaccepte une map ou un set — pas une liste directeeach.key= clé de la map / valeur du set ;each.value= valeur de la map- Les instances sont adressées par clé :
ressource.nom["clé"] - Ajouter/supprimer une entrée de la map ne touche que l’instance correspondante
{ for k, v in ressource.nom : k => v.attr }— transformer les résultats en output- Préférer
for_eachàcountquand les instances ont une identité distincte