Terraform — Boucles et méta-arguments

De wiki.nexiat.fr
Aller à la navigation Aller à la recherche
Fiche express
Type Méta-arguments de répétition et de contrôle du cycle de vie
Répétition count, for_each, bloc dynamic
Dépendance explicite depends_on
Cycle de vie lifecycle { create_before_destroy, prevent_destroy, ignore_changes }
Voir aussi Terraform — Concepts fondamentaux · Terraform — Expressions et fonctions · Terraform — Modules et variables

Les méta-arguments sont des arguments spéciaux, valables sur tout bloc resource ou module, qui ne configurent pas la ressource elle-même mais la manière dont Terraform la gère : répétition (count, for_each), dépendance (depends_on) et cycle de vie (lifecycle). Cette page détaille chacun, avec une attention particulière au piège classique de count sur des listes instables.

count vs for_each

Les deux permettent de créer plusieurs instances d'un même bloc à partir d'une seule déclaration. Ils ne sont pas interchangeables sans conséquence sur la façon dont Terraform identifie chaque instance dans le state.

count

count prend un entier ; chaque instance est indexée 0, 1, 2, ... et adressée dans le state sous la forme resource_type.name[0].

resource "vsphere_virtual_machine" "node" {
  count = 3
  name  = "rke2-node-${count.index}"
  # ...
}

for_each

for_each prend un map ou un set(string) ; chaque instance est adressée par sa clé, pas par un index : resource_type.name["cle"].

resource "vsphere_virtual_machine" "node" {
  for_each = var.nodes   # map(object({...})), clé = nom logique

  name     = each.key
  num_cpus = each.value.cpu
  memory   = each.value.memory
}

Dans le bloc, each.key et each.value référencent l'entrée courante (pour un set(string), each.key == each.value).

Le piège du count avec une liste qui change d'ordre

Avec count, l'identité d'une instance dans le state est son index, pas son contenu. Si la liste source change d'ordre (tri différent, insertion au milieu, suppression d'un élément autre que le dernier), les index se décalent : Terraform associe le state existant au mauvais élément et planifie potentiellement une destruction/recréation en cascade de ressources qui n'ont, en réalité, pas changé.

variable "zones" {
  default = ["a", "b", "c"]
}

resource "aws_subnet" "this" {
  count = length(var.zones)
  # instance [0] = "a", [1] = "b", [2] = "c"
}

# Si var.zones devient ["b", "c"] (suppression de "a" en tête) :
# [0] était "a", devient "b" -> Terraform veut "modifier" [0] vers b
# [1] était "b", devient "c" -> idem en cascade
# alors qu'on voulait juste supprimer une ressource, pas toucher les autres

for_each n'a pas ce défaut : chaque instance est adressée par clé stable, donc supprimer un élément d'un map ne touche que l'instance correspondante. C'est pourquoi la recommandation générale est : utiliser for_each dès que les éléments ont une identité naturelle (nom, clé) et que l'ensemble peut évoluer ; réserver count aux cas où l'on veut vraiment N instances strictement identiques et interchangeables (ou une ressource optionnelle via count = condition ? 1 : 0, voir Terraform — Expressions et fonctions).

count for_each
Entrée nombre entier map ou set(string)
Identité de l'instance index numérique clé
Référence dans le bloc count.index each.key / each.value
Adressage state [0], [1] ["cle"]
Robustesse au réordonnancement fragile stable

Bloc dynamic

Un bloc dynamic génère N sous-blocs répétés à l'intérieur d'une ressource, pour des arguments qui sont eux-mêmes des blocs imbriqués (et non de simples attributs) — par exemple des règles de sécurité, des interfaces réseau, des tags.

variable "ingress_rules" {
  type = list(object({
    port        = number
    cidr_blocks = list(string)
  }))
}

resource "aws_security_group" "this" {
  name = "app"

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = "tcp"
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}

Le nom donné après dynamic (ici ingress) fixe le nom de la variable d'itération utilisée dans content (ingress.value), à ne pas confondre avec le type de bloc généré (qui est le même nom ici, mais peut différer via iterator). À réserver aux cas où le nombre de sous-blocs est réellement variable : un bloc dynamic sur une liste statique et connue nuit à la lisibilité par rapport à des blocs écrits explicitement.

depends_on

Terraform déduit automatiquement la plupart des dépendances via les références entre expressions (var.x, resource.attr). depends_on sert à forcer une dépendance non visible dans les expressions — par exemple une dépendance sur un effet de bord d'une API (permissions IAM propagées, ressource nécessaire à l'exécution mais non référencée en attribut).

resource "aws_iam_role_policy" "this" {
  # ...
}

resource "aws_instance" "app" {
  # aucune référence directe à la policy dans les attributs,
  # mais l'instance a besoin que la policy soit déjà appliquée
  depends_on = [aws_iam_role_policy.this]
}

À utiliser avec parcimonie : un depends_on explicite est souvent le symptôme qu'une dépendance devrait être exprimée via une vraie référence d'attribut (plus robuste et plus lisible que la liste de dépendances).

lifecycle

Le bloc lifecycle, imbriqué dans une ressource, modifie la façon dont Terraform planifie les changements sur 'cette ressource.

resource "vsphere_virtual_machine" "node" {
  # ...

  lifecycle {
    create_before_destroy = true
    prevent_destroy        = true

    ignore_changes = [
      annotation,
      clone[0].customize,
    ]
  }
}
  • create_before_destroy — inverse l'ordre par défaut (destroy puis create) lors d'un remplacement : la nouvelle ressource est créée avant que l'ancienne soit détruite. Indispensable quand une interruption de service est inacceptable (ex. remplacer un nœud derrière un load-balancer) ou quand une contrainte d'unicité empêcherait sinon la coexistence transitoire (à combiner alors avec des noms générés dynamiquement pour éviter les collisions).
  • prevent_destroy — bloque toute opération qui détruirait la ressource (y compris un remplacement) : Terraform refuse le plan/apply avec une erreur explicite. Protection utile sur des ressources critiques (base de données, state backend lui-même) contre une suppression accidentelle. Ne protège pas contre terraform destroy ciblé après suppression de ce bloc — c'est un garde-fou au niveau du code, pas un verrou infra.
  • ignore_changes — liste d'attributs que Terraform doit ignorer lors du calcul du diff, même s'ils divergent entre state et réalité. Utile quand un attribut est modifié hors Terraform de façon légitime (auto-scaling qui ajuste une taille, valeur injectée par un contrôleur Kubernetes) et qu'on ne veut pas que chaque plan le signale comme drift. ignore_changes = all ignore tous les attributs (rarement souhaitable, masque un vrai drift).

Voir aussi