Aller au contenu
Infrastructure as Code medium

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 tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn