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

for_each Terraform : instances nommées avec une map

12 min de lecture

logo terraform

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.

  • for_each avec une map : each.key et each.value
  • for_each avec un set : itérer sur une liste de chaînes
  • Adressage : ressource.nom["clé"]
  • Expressions for dans les outputs pour transformer les résultats
  • Quand for_each plutôt que count

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é stable

for_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.

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 collection
  • each.key : la clé récupérée
  • each.value : la valeur associée à cette clé
variables.tf
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.tf
resource "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"
}

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ême
libvirt_volume.disk[each.key].path

Dans 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 :

Fenêtre de terminal
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"

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
}

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).

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
}
Fenêtre de terminal
terraform apply
libvirt_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.

Critèrecountfor_each
Instances identiques✅ AdaptéPossible mais verbeux
Instances avec config différente❌ Difficile✅ Adapté
Suppression d’une instance du milieuRéindexe les suivantes✅ Stable, seule la clé supprimée est détruite
Ajout d’une instancePeut recréer les suivantes✅ Seule la nouvelle est créée
Ressource optionnelle (0 ou 1)count = condition ? 1 : 0Possible mais verbeux
Adressage dans le stateressource.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.

SymptômeCause probableSolution
for_each does not allow listsPasser une liste directementConvertir avec toset(liste)
each.key non disponibleBloc sans for_each déclaréVérifier que for_each est bien défini
Ressource détruite et recrée à un changement de mapClé modifiéeNe pas renommer une clé — utiliser terraform state mv
Output en map au lieu de listeComportement normal avec for_eachUtiliser values(ressource.nom[*].attr) pour une liste
  1. for_each accepte une map ou un set — pas une liste directe
  2. each.key = clé de la map / valeur du set ; each.value = valeur de la map
  3. Les instances sont adressées par clé : ressource.nom["clé"]
  4. Ajouter/supprimer une entrée de la map ne touche que l’instance correspondante
  5. { for k, v in ressource.nom : k => v.attr } — transformer les résultats en output
  6. Préférer for_each à count quand les instances ont une identité distincte

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