DevOps Elastic Hayway
Document

SUBSCRIBE TO GET FULL ACCESS TO THE E-BOOKS FOR FREE 🎁SUBSCRIBE NOW

Professional Dropdown with Icon

SUBSCRIBE NOW TO GET FREE ACCESS TO EBOOKS

TerraformLesson 14 / 2411 min readUpdated September 13, 2026

Terraform for_each vs count vs for

À la fin de ce tutoriel, vous saurez pourquoi boucler des ressources similaires, utiliser count (index et pièges de réordonnancement), préférer for_each sur map/set à clés stables, distinguer l’expression for (locals / outputs) du meta-argument, migrer count → for_each sans tout détruire (moved), et valider un lab léger en Region ca-central-1 avec destroy systématique.

Niveau : Débutant → Intermédiaire · Temps estimé : 40–50 min · Versions testées : Terraform 1.16.2 (série ≥ 1.9), AWS provider ~> 5.0 · Dernière vérification : 2026-09-10

Slug proposé : terraform-boucles-for-each · Série : Terraform · Remplace / fusionne : #1650 (P6 bonus #1 : count vs for_each vs for)

Prérequis

  • Avoir suivi Data sources (terraform-data-sources) : lire l’existant, timing plan
  • Avoir suivi Variables (terraform-variables) et Locals (terraform-locals) : maps, sets, valeurs dérivées
  • Avoir suivi Providers et ressources (terraform-providers-ressources) : adresse TYPE.NAME
  • Terraform ≥ 1.9, AWS CLI v2, profil de lab (aws sts get-caller-identity OK)

Coût estimé : quasi 0 € si vous créez seulement des buckets S3 vides tagués (quelques octets) ou des utilisateurs IAM de lab sans clés d’accès. Destroy obligatoire en fin de lab. Aucun secret dans le HCL ni les tfvars commités.

Auth sans secrets. Utilisez AWS_PROFILE / SSO. Jamais d’access_key / secret_key dans le provider. Pour ce lab, une politique limitée (S3 + IAM users de test) suffit.

Ce que nous allons construire

Lab boucles Terraform (ca-central-1)
  ├── versions.tf / providers.tf → TF ≥ 1.9, aws ~> 5.0, region ca-central-1
  ├── variables.tf               → map de buckets (ou users IAM) + tags
  ├── main.tf                    → aws_s3_bucket / tags via for_each
  ├── outputs.tf                 → map id → bucket (expression for)
  ├── (option) moved.tf          → aperçu migration count → for_each
  └── init → plan → apply → destroy

(Alt : « Terraform for_each vs count vs for : boucler des ressources AWS sans réordonnancement destructeur ».)

Ce guide remplace le post WordPress #1650 (« Terraform Loops ») et aligne le parcours P2 sur le sujet count vs for_each vs for (bonus P6 #1 du plan), actualisé 2026 (TF ≥ 1.9, provider ~> 5.0, Region ca-central-1).

Étape 1 — Pourquoi boucler (N ressources similaires)

En IaC, vous créez souvent plusieurs objets du même type : buckets de logs par environnement, utilisateurs IAM d’équipe, sous-réseaux par AZ. Dupliquer le bloc resource N fois est fragile (copier-coller, dérive de tags, oubli d’un output).

Terraform propose trois outils proches mais distincts :

Outil Rôle Où
Meta-argument count Créer N instances indexées 0…N-1 Sur resource / module
Meta-argument for_each Créer une instance par clé (map ou set) Sur resource / module
Expression for Transformer listes / maps en mémoire Dans locals, output, arguments

Objectif : une déclaration, N objets dans le state — avec des adresses stables pour éviter les destroy/create inutiles.

Étape 2 — count : index et limites (réordonnancement destructeur)

count prend un entier. Chaque instance s’adresse TYPE.NAME[i] :

variable "bucket_suffixes" {
  type    = list(string)
  default = ["alpha", "beta", "gamma"]
}

resource "aws_s3_bucket" "lab_count" {
  count  = length(var.bucket_suffixes)
  bucket = "lab-tf-count-${var.bucket_suffixes[count.index]}-${data.aws_caller_identity.current.account_id}"

  tags = {
    Name  = var.bucket_suffixes[count.index]
    Index = tostring(count.index)
  }
}

Avantage : simple pour « exactement N copies identiques ». Limite critique : l’identité repose sur l’index. Si vous retirez "beta" au milieu de la liste, Terraform voit :

  • [1] (ex-beta) → devient gamma → remplacement
  • [2] → disparaît → destroy

Le cloud peut donc détruire et recréer des buckets (ou instances) pourtant « encore voulus », uniquement parce que les indices ont glissé. count reste utile pour count = var.enable ? 1 : 0 (présence/absence) — sujet du tuto Conditionnels.

Étape 3 — for_each : map/set et clés stables (préféré)

for_each itère une map ou un set. L’adresse devient TYPE.NAME["clé"] : retirer une clé n’affecte pas les autres.

variable "lab_buckets" {
  type = map(object({
    purpose = string
  }))
  description = "Buckets de lab indexés par clé stable."
  default = {
    alpha = { purpose = "logs" }
    beta  = { purpose = "artifacts" }
    gamma = { purpose = "backup" }
  }
}

data "aws_caller_identity" "current" {}

resource "aws_s3_bucket" "lab" {
  for_each = var.lab_buckets

  # Préfixe unique au compte — évite les collisions de nom S3 global
  bucket = "lab-tf-fe-${each.key}-${data.aws_caller_identity.current.account_id}"

  tags = {
    Name      = each.key
    Purpose   = each.value.purpose
    ManagedBy = "terraform"
    Lab       = "for-each"
  }
}

Dans le bloc : each.key (clé) et each.value (valeur). Avec un toset(["a","b"]), each.value vaut aussi la clé. Préférez une map dès que vous avez des attributs par élément (tags, taille, rôle).

Règle pratique P2 : for_each par défaut pour N objets nommés ; count pour le toggle 0/1 ou les copies strictement anonymes.

Étape 4 — Expression for ≠ meta-argument

L’expression for ne crée pas de ressources. Elle construit listes ou maps dans locals / outputs / arguments :

locals {
  bucket_arns = {
    for k, b in aws_s3_bucket.lab : k => b.arn
  }
}

output "bucket_ids" {
  description = "Map clé → id des buckets for_each."
  value = {
    for k, b in aws_s3_bucket.lab : k => b.id
  }
}

output "bucket_names_list" {
  description = "Liste des noms (expression for → list)."
  value       = [for b in aws_s3_bucket.lab : b.bucket]
}

Syntaxe typique : { for k, v in map : k => v.attr } ou [for x in list : x.name if x.enabled]. Confusion fréquente : écrire for_each = [for …] correctement, mais croire que for remplace for_each sur le resource — non : sans meta-argument, une seule ressource.

Étape 5 — Migrer count → for_each sans tout détruire

Passer de aws_s3_bucket.lab[0] à aws_s3_bucket.lab["alpha"] sans moved = Terraform prévoit destroy + create. Depuis Terraform 1.1+, les blocs moved réécrivent l’adresse dans le state sans toucher au cloud :

# Après avoir remplacé count par for_each sur les mêmes objets AWS
moved {
  from = aws_s3_bucket.lab_count[0]
  to   = aws_s3_bucket.lab["alpha"]
}

moved {
  from = aws_s3_bucket.lab_count[1]
  to   = aws_s3_bucket.lab["beta"]
}

moved {
  from = aws_s3_bucket.lab_count[2]
  to   = aws_s3_bucket.lab["gamma"]
}

Workflow : (1) inventaire terraform state list, (2) bascule HCL vers for_each + clés stables, (3) un moved par instance, (4) terraform plan → 0 destroy attendu sur ces objets, (5) apply, puis retirez les moved au commit suivant si vous le souhaitez (ils restent inoffensifs). Alternative historique : terraform state mv — moins traçable en Git.

Étape 6 — Lab ca-central-1 (buckets via for_each + destroy)

Lab léger : trois buckets S3 tagués (ou, variante, des aws_iam_user sans aws_iam_access_key). Region ca-central-1.

# versions.tf
terraform {
  required_version = ">= 1.9.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# providers.tf
provider "aws" {
  region = "ca-central-1"
  # Auth : AWS_PROFILE / SSO — jamais de clés ici
}

# variables.tf — voir map lab_buckets (étape 3)

# main.tf — data + resource for_each (étape 3)
# outputs.tf — expressions for (étape 4)

Commandes :

mkdir -p ~/terraform-foreach-lab && cd ~/terraform-foreach-lab
# collez versions.tf, providers.tf, variables.tf, main.tf, outputs.tf
export AWS_PROFILE=lab
export AWS_REGION=ca-central-1
terraform init
terraform fmt
terraform validate
terraform plan
terraform apply -auto-approve
terraform output
terraform destroy -auto-approve

Vérifiez dans la console S3 (ou aws s3api list-buckets) les noms lab-tf-fe-alpha-…, etc., puis destroy. Variante IAM : resource "aws_iam_user" "lab" { for_each = toset(["alice-lab","bob-lab"]) ; name = each.key } — jamais de clés secrètes dans le code.

Étape 7 — Checklist

  1. Boucler = une déclaration, N objets — éviter le copier-coller.
  2. count → index ; retirer un élément au milieu = risque de réordonnancement destructeur.
  3. for_each → map/set ; clés stables = préféré pour N ressources nommées.
  4. Expression for = transformation ; pas un meta-argument de création.
  5. Migration : blocs moved (ou state mv) avant le plan.
  6. Lab ca-central-1 + destroy systématique.
  7. Aucun secret ; profil SSO.

Nettoyage

cd ~/terraform-foreach-lab
terraform destroy -auto-approve
rm -rf ~/terraform-foreach-lab

Confirmez l’absence des buckets / users de lab dans le compte.

Erreurs fréquentes

Symptôme Cause Correction
for_each refuses list / unknown until apply Liste ordinaire ou valeur inconnue au plan toset(...), map littérale / variable typée ; éviter les IDs cloud encore inconnus comme clés
Destroy/create en cascade après edit de liste count + suppression au milieu Passer à for_each + clés ; moved si state déjà indexé
each.key / each.value hors bloc Référence en dehors de la ressource for_each Utiliser aws_s3_bucket.lab["alpha"].id ou une expression for
Noms S3 déjà pris Namespace global + collision Inclure account_id (data) dans le nom
Confusion for / for_each Attendre N resources d’un seul for Ajouter le meta-argument for_each sur le resource

Quiz (3 questions)

1. Pourquoi for_each est-il en général préférable à count pour N buckets nommés ?

  • A. Il est plus rapide à appliquer
  • B. Les clés stables évitent le réordonnancement destructeur
  • C. count ne fonctionne pas avec le provider AWS 5.x

2. Que fait l’expression for dans un output ?

  • A. Elle crée autant de ressources que d’éléments
  • B. Elle transforme une collection en liste ou map sans meta-argument de ressource
  • C. Elle remplace obligatoirement moved

3. Comment migrer resource.x[0] vers resource.x["alpha"] sans détruire l’objet cloud ?

  • A. Supprimer le state puis re-apply
  • B. Déclarer un bloc moved { from = … to = … } puis plan/apply
  • C. Changer seulement le nom local du resource

Réponses : 1‑B · 2‑B · 3‑B

Pourquoi / quand boucler avec for_each

Dès que vous créez N ressources similaires (buckets, sous-réseaux, files d’attente), dupliquer des blocs resource est une dette. for_each sur une map ou un set donne des adresses stables (aws_s3_bucket.this["logs"]) : ajouter/retirer une clé ne réordonne pas tout, contrairement à count mal maîtrisé.

count vs for_each — guide de choix

  • count : prototypage rapide, N identique, index numérique acceptable. Risque : supprimer l’index 0 décale tout et peut forcer des replaces.
  • for_each : préféré en production pédagogique et réelle dès que chaque instance a une identité (nom, AZ, rôle).
  • Expression for : transforme des collections dans une expression ; ce n’est pas le meta-argument for_each.

Pièges des boucles Terraform

  • Convertir une liste en for_each sans toset() / map : Terraform exige une map ou un set de strings.
  • Clés dépendantes d’une valeur connue seulement après apply (unknown at plan) : cas avancé, à éviter en lab.
  • Migrer count → for_each sans moved / state mv : plan plein de destroys.
  • Nommer les buckets avec un préfixe non unique globalement : choc S3 au apply.
  • Laisser tourner N buckets après le lab : destroy systématique en Region ca-central-1, TF ≥ 1.9, sans secrets dans le HCL.

Quand ne pas boucler

Une seule resource suffit. Ou la variation est mieux modelée par un module appelé plusieurs fois avec des inputs clairs (lisibilité pour les débutants du module).

FAQ for_each & count

Comment inspecter une instance ? terraform state show 'aws_s3_bucket.this["logs"]' (guillemets selon le shell).

Puis-je combiner count et for_each sur la même resource ? Non : un seul de ces meta-arguments par resource.

for_each sur un module ? Oui (Terraform moderne) : très puissant ; voyez Modules bases après ce tuto.

Pourquoi des clés stables ? Le state indexe par clé : une clé stable = lifecycle stable quand la collection change.

Les tags doivent-ils inclure la clé ? Bonne pratique : Name = each.key ou tag Item = each.key pour retrouver la resource dans la console.

Pour aller plus loin

  • Doc : for_each, count, for expressions, moved
  • Sur ce site : Data sources, Conditionnels ; aussi Dynamic blocks, Modules
  • Suite : activer / désactiver une ressource avec count conditionnel et expressions ternaires

Maillage série Terraform (P2)

← Précédent Data sources (terraform-data-sources)
→ Suivant Conditionnels (terraform-conditionnels)
Aussi Dynamic blocks (terraform-dynamic-blocks) · Modules (terraform-modules-bases)
Carte Overview · Install · Workflow · Blocks · Providers · Variables · Locals · Outputs · State · Data sources · Loops · Conditionals · Dynamic blocks · Modules ×2 · Workspaces · Provisioners · EBS · ELB/ALB · IAM · RDS · VPC · Auto Scaling · Route 53

Cas réel — count vs for_each après une suppression au milieu

Vous aviez count = 3 subnets. On retire celui du milieu : les index glissent, Terraform remplace les suivants. Avec for_each sur des AZ nommées, seule la clé supprimée disparaît. C’est la raison pour laquelle ce cours insiste sur for_each dès qu’il y a une identité métier.

Piège : une map dont les clés sont des timestamps ou des UUID régénérés à chaque plan. Le for_each croit à un destroy/create massif. Clés stables (nom d’env, AZ, nom de bucket). En lecture, each.key / each.value doivent apparaître dans les tags pour debugger.

À retenir : la clé est plus importante que la valeur. Une mauvaise clé coûte un replace ; une mauvaise valeur se corrige souvent in-place.

Retour parcours Terraform — hub de la série et leçons sœurs.

Share your love

Leave a Reply

Your email address will not be published. Required fields are marked *