Guide - TF-PRO HashiCorp Terraform Authoring and Operations Professional
Dernière révision : mai 2026
Une référence concise des modèles d'architecture évalués par l'examen TF-PRO. Lisez de haut en bas ou sautez à une section.
HCL et Configuration
Une ressource a besoin d'un nombre variable de blocs imbriqués identiques (par exemple, des règles d'entrée) pilotés par une liste/carte.
Utilisez un bloc `dynamic` dont le `for_each` itère sur la collection ; référencez chaque élément via l'itérateur de bloc (nom par défaut = libellé du bloc) à l'intérieur de `content {}`.
Pourquoi: Les blocs dynamiques génèrent des blocs imbriqués répétés sans copier-coller ; l'itérateur maintient chaque bloc généré lié à son élément source.
Créez une ressource par entrée dans une carte d'objets, avec une clé stable afin que le réordonnancement ne force jamais le remplacement.
Définissez `for_each = var.objects` (une carte). Utilisez `each.key` pour la clé stable et `each.value.<attr>` pour les champs. Évitez `count` ici - les décalages d'index provoquent des perturbations.
Pourquoi: Les clés de carte sont des identités stables dans l'état ; les index de liste sont positionnels et se décalent lorsque des éléments sont ajoutés/supprimés.
Choisissez entre `count` et `for_each` pour plusieurs instances.
Utilisez `for_each` lorsque les instances ont des identités distinctes (un ensemble/une carte) ; utilisez `count` uniquement pour N copies identiques et non sensibles à l'ordre. Préférez `for_each` pour tout ce qui peut croître/diminuer.
Pourquoi: `for_each` s'adresse par clé (resource["key"]) ; `count` s'adresse par index (resource[0]) ce qui entraîne un réarrangement lors des insertions/suppressions.
Une variable d'entrée est un objet où certains attributs sont optionnels et nécessitent des valeurs par défaut.
Tapez-le comme `object({ name = string, size = optional(number, 10) })`. `optional(type, default)` fournit la valeur par défaut lorsque l'appelant omet l'attribut.
Pourquoi: `optional()` avec une valeur par défaut maintient les appelants concis tout en garantissant une valeur concrète en aval - pas de gestion de null partout.
Validez une hypothèse sur une ressource avant l'application, ou garantissez un résultat après.
Utilisez `lifecycle { precondition { ... } }` pour affirmer les entrées avant la création/mise à jour, et `postcondition` pour affirmer les sorties après. Les deux prennent `condition` + `error_message`.
Pourquoi: Les conditions personnalisées échouent rapidement avec un message clair au lieu de produire une application défectueuse ou une erreur en aval confuse.
Une ressource doit être recréée chaque fois qu'une autre ressource ou un attribut change.
Ajoutez `lifecycle { replace_triggered_by = [aws_x.y.id] }`. Lorsque la valeur référencée change, Terraform force le remplacement de cette ressource.
Pourquoi: Exprime une dépendance de remplacement de manière déclarative, évitant le `-replace` manuel à chaque changement lié.
Le remplacement d'une ressource provoque des temps d'arrêt car l'ancienne est détruite avant que la nouvelle n'existe.
Définissez `lifecycle { create_before_destroy = true }` afin que Terraform provisionne d'abord le remplacement, puis détruise l'ancien. Assurez-vous d'avoir des noms uniques/pas de conflits majeurs.
Pourquoi: Remplacement sans interruption ; mais attention aux collisions de noms et aux limites de quota pendant que les deux existent brièvement.
Rejetez les valeurs d'entrée invalides tôt (par exemple, un environnement qui n'est pas dev/stage/prod).
Ajoutez un bloc `validation { condition = contains(["dev","stage","prod"], var.env), error_message = "..." }` à la variable.
Pourquoi: Détecte les mauvaises entrées au moment de la planification avec un message lisible au lieu d'échouer profondément dans un appel de fournisseur.
Référencez une valeur qui pourrait ne pas exister sans faire planter le plan.
Utilisez `try(local.maybe.value, "default")` pour revenir en arrière en cas d'erreurs, ou `can(expr)` pour obtenir un booléen indiquant si une expression réussit.
Pourquoi: Gestion élégante des données optionnelles/à forme variable ; évite "Error: Unsupported attribute" sur les clés absentes.
Transformez une liste en une carte, ou filtrez/formatez une collection pour un argument de ressource.
Utilisez une expression `for` : `{ for u in var.users : u.name => u.role if u.active }` (carte) ou `[for x in list : upper(x)]` (liste).
Pourquoi: Les expressions `for` sont la manière idiomatique de remodeler les données ; la clause `if` filtre, la forme `k => v` construit des cartes.
Une variable ou une sortie contient un secret qui ne doit pas apparaître dans la sortie de planification/application.
Marquez la variable `sensitive = true` (ainsi que les sorties). Terraform la masque dans la sortie CLI, bien qu'elle soit toujours stockée dans l'état.
Pourquoi: Empêche la divulgation accidentelle dans les journaux/sorties CI ; l'état lui-même doit toujours être protégé (backend chiffré, contrôle d'accès).
Gérez des ressources dans deux régions/comptes au sein d'une même configuration.
Déclarez des fournisseurs aliasés (`provider "aws" { alias = "west" region = "us-west-2" }`) et définissez `provider = aws.west` sur les ressources ou transmettez-les aux modules.
Pourquoi: Les alias permettent à une configuration de cibler plusieurs instances de fournisseurs ; les modules les reçoivent explicitement via l'argument `providers`.
Une dépendance cachée (non exprimée par des références) provoque des problèmes d'ordonnancement.
Ajoutez `depends_on = [aws_iam_role_policy.x]` pour forcer l'ordonnancement. Utilisez-le avec parcimonie - préférez les dépendances implicites via les références d'attributs.
Pourquoi: `depends_on` explicite gère les dépendances que le graphe ne peut pas inférer, mais une utilisation excessive crée des plans conservateurs et plus lents.
Rendez un fichier de configuration/données utilisateur à partir d'un modèle avec des variables structurées.
Utilisez `templatefile("${path.module}/tpl.tftpl", { items = local.items })` ; le modèle utilise l'interpolation `%{ for }` / `${}`.
Pourquoi: `templatefile` maintient un rendu pur/au moment de la planification (contrairement au fournisseur de template déprécié) et prend en charge les boucles/conditionnelles.
Construisez une liste plate de toutes les combinaisons (sous-réseau, règle) à alimenter un seul `for_each`.
Utilisez `setproduct(var.subnets, var.rules)` pour le produit cartésien, ou `flatten([for ...])` pour fusionner les listes imbriquées en une seule.
Pourquoi: Ces fonctions transforment les données imbriquées en la collection plate et à clé unique requise par `for_each`.
Modules
Un module de registre change et modifie inopinément l'infrastructure lors du prochain `init`.
Épinglez avec `version = "~> 4.2"` (modules de registre uniquement). Pour les sources Git, épinglez une balise `?ref=v4.2.0`. Exécutez `terraform init -upgrade` délibérément pour déplacer les épingles.
Pourquoi: Les modules non épinglés flottent vers la dernière version ; l'épinglage rend les mises à niveau intentionnelles et révisables.
Un module racine a besoin d'une valeur produite profondément à l'intérieur d'un module enfant.
Exposez-le comme une `output` dans le module enfant, puis référencez `module.child.output_name`. Les valeurs non sorties ne sont pas accessibles aux appelants.
Pourquoi: Les modules sont encapsulés ; les sorties sont le seul moyen pour les données de traverser la frontière du module vers le haut.
Instanciez le même module une fois par équipe/environnement à partir d'une carte.
Définissez `for_each` sur le bloc de module : `module "env" { for_each = var.envs; source = "./env"; name = each.key }`. Référencez `module.env["prod"]`.
Pourquoi: `for_each` sur les modules adapte un modèle sans copier-coller des blocs ; les clés donnent des adresses stables.
Un module enfant doit créer des ressources dans un fournisseur non par défaut (aliasé).
Passez les fournisseurs explicitement : `module "x" { providers = { aws = aws.west } }`. Le module enfant déclare le fournisseur dans `required_providers` avec `configuration_aliases`.
Pourquoi: Les modules n'héritent pas implicitement des fournisseurs aliasés ; la carte des fournisseurs relie l'alias parent à l'enfant.
Renommer une ressource ou la déplacer dans un module la détruirait et la recréerait normalement.
Ajoutez un bloc `moved { from = aws_instance.old; to = module.compute.aws_instance.new }`. Terraform met à jour les adresses d'état sans destruction.
Pourquoi: Les blocs `moved` rendent les refactorisations sûres et révisables dans le code, remplaçant la commande manuelle `terraform state mv`.
Un module monolithique est devenu ingérable et mélange des préoccupations de réseau, de calcul et de données.
Décomposez-le en modules enfants ciblés et composez-les dans un module racine, en passant les sorties de l'un comme entrées à l'autre. Gardez les modules à usage unique.
Pourquoi: La composition améliore la réutilisation et la testabilité ; les modules à portée étroite versionnent et évoluent indépendamment.
Les consommateurs transmettent des combinaisons d'entrées invalides à un module partagé.
Ajoutez des blocs `validation` et des `precondition`s à l'intérieur du module pour faire respecter les contrats, et documentez les entrées avec `description`.
Pourquoi: Un module est propriétaire de son contrat ; la validation interne protège tous les appelants, pas seulement une configuration racine.
Les modules profondément imbriqués rendent difficile le suivi du flux de données et du passage des fournisseurs.
Gardez l'imbrication peu profonde (1-2 niveaux). Passez les fournisseurs et les entrées clés explicitement à chaque niveau ; évitez de compter sur une héritance implicite profonde.
Pourquoi: Les arbres peu profonds sont plus faciles à comprendre ; l'imbrication profonde amplifie la complexité du passage des fournisseurs et de la gestion des sorties.
Publiez un module réutilisable dans le registre privé et faites-le évoluer sans casser les appelants.
Balisez les versions avec semver (`v1.2.0`) ; les changements d'entrée/sortie incompatibles augmentent la version majeure. Les appelants épinglent avec des contraintes `~>` .
Pourquoi: Le versionnement sémantique permet aux consommateurs d'adopter les correctifs/fonctionnalités en toute sécurité et d'opter délibérément pour des changements incompatibles.
Choisissez l'emplacement d'un module en fonction de son niveau de maturité.
Chemins locaux (`./modules/x`) pour les modules dans le dépôt, Git (`git::...?ref=tag`) pour les modules partagés mais non publiés, registre (`namespace/name/provider`) pour les modules versionnés/publiés.
Pourquoi: Le type de source correspond à la portée de partage ; seules les sources de registre prennent en charge l'argument `version` et la résolution des contraintes.
Une sortie de module contient un secret consommé par le module racine.
Marquez la sortie du module `sensitive = true`. La consommer dans un contexte non sensible provoquera une erreur tant que vous ne la traiterez pas également comme sensible.
Pourquoi: La sensibilité se propage à travers la frontière du module, empêchant les fuites accidentelles dans la sortie racine.
Un ensemble de ressources existant basé sur `count` doit devenir `for_each` sans détruire les instances.
Ajoutez des blocs `moved` mappant chaque index `resource[0]` à la nouvelle adresse `resource["key"]`, puis passez à `for_each`.
Pourquoi: Les blocs `moved` re-clés l'état de l'adressage positionnel à l'adressage par identité, évitant la destruction/recréation.
CLI et Gestion de l'État
Vous avez renommé une ressource dans la configuration ; le plan veut maintenant détruire l'ancienne et en créer une nouvelle.
Préférez un bloc `moved` dans la configuration. Pour les corrections ad-hoc/CLI, utilisez `terraform state mv aws_x.old aws_x.new` pour re-pointer l'objet existant.
Pourquoi: Les deux mettent à jour l'adresse d'état afin que Terraform voie l'objet existant comme la ressource renommée - pas de destruction.
Mettez une ressource existante, créée manuellement, sous la gestion de Terraform.
Ajoutez un bloc `import { to = aws_x.y; id = "i-123" }` et exécutez `terraform plan -generate-config-out=gen.tf` pour échafauder la configuration, puis affinez et appliquez.
Pourquoi: L'importation pilotée par la configuration est révisable et génère une configuration de départ, contrairement à l'ancienne commande impérative `terraform import`.
Arrêtez de gérer une ressource avec Terraform mais laissez-la s'exécuter dans le cloud.
Exécutez `terraform state rm aws_x.y`. Terraform oublie l'objet ; il n'est pas détruit. Supprimez également sa configuration pour éviter un plan de recréation.
Pourquoi: `state rm` détache sans supprimer - utile lors du transfert d'une ressource à un autre outil/équipe.
Déplacez l'état d'un backend local vers S3 (ou vers HCP Terraform).
Ajoutez/remplacez le bloc `backend`/`cloud`, exécutez `terraform init` - Terraform détecte le changement et vous invite à migrer l'état existant vers le nouveau backend.
Pourquoi: `init` orchestre la copie ; répondre oui migre l'état en toute sécurité plutôt que de démarrer à vide.
Deux ingénieurs exécutent `apply` simultanément sur le même état distant.
Utilisez un backend qui prend en charge le verrouillage (S3+DynamoDB, HCP Terraform, etc.). Terraform acquiert un verrou par opération ; la seconde exécution attend ou génère une erreur.
Pourquoi: Le verrouillage empêche les écritures concurrentes qui corrompraient l'état. Ne le désactivez jamais à la légère.
Une application plantée a laissé un verrou périmé et maintenant chaque exécution est bloquée.
Confirmez qu'aucune opération n'est en cours, puis `terraform force-unlock <LOCK_ID>`. Utilisez l'ID du message d'erreur.
Pourquoi: `force-unlock` efface un verrou orphelin ; le faire pendant qu'une opération réelle est en cours risque de corrompre l'état.
Détectez la dérive entre la configuration/l'état et l'infrastructure réelle sans proposer de changements.
Exécutez `terraform plan -refresh-only` (ou `apply -refresh-only` pour mettre à jour l'état). Il signale les différences sans planifier les changements de ressources.
Pourquoi: Sépare la détection de dérive de la planification des changements - vous voyez ce qui a changé dans le cloud avant de décider de réconcilier.
Une ressource se comporte mal et vous voulez la recréer sans modifier la configuration.
Exécutez `terraform apply -replace="aws_instance.web"`. C'est le remplacement moderne de la commande `terraform taint` dépréciée.
Pourquoi: `-replace` force la destruction et la recréation d'une ressource lors de la prochaine application, de manière déclarative via la CLI.
Vous êtes tenté d'utiliser `-target` de manière routinière pour accélérer les applications.
Utilisez `-target` uniquement pour la récupération d'erreurs ou les corrections chirurgicales. Évitez-le comme flux de travail normal - il produit des applications partielles et peut ignorer les dépendances.
Pourquoi: Le ciblage de routine masque les problèmes de dépendance et produit un état incomplet ; HashiCorp le documente comme un outil exceptionnel.
Un fournisseur a déplacé des espaces de noms (par exemple, `hashicorp/aws` vers un fork) et l'état fait référence à l'ancienne adresse.
Exécutez `terraform state replace-provider registry.terraform.io/hashicorp/aws registry.example.com/org/aws`.
Pourquoi: Réécrit les références de fournisseur dans l'état afin que `init`/`plan` résolvent la nouvelle source sans recréer de ressources.
Une configuration a besoin des sorties produites par une autre configuration/espace de travail.
Utilisez la source de données `terraform_remote_state` (ou les sorties d'exécution HCP Terraform) pour lire les sorties d'un autre état en lecture seule.
Pourquoi: Partage les valeurs entre les limites d'état sans dupliquer les ressources ; seules les sorties exportées sont lisibles.
Les paramètres de backend (bucket, clé) diffèrent selon l'environnement et ne doivent pas être codés en dur.
Laissez-les en dehors du bloc `backend` et passez-les à l'initialisation : `terraform init -backend-config=prod.hcl` (ou `-backend-config="key=..."`).
Pourquoi: La configuration partielle permet de réutiliser une configuration dans différents environnements tout en fournissant des valeurs de backend spécifiques à l'environnement lors de l'initialisation.
Vous avez besoin d'états séparés pour dev/stage/prod à partir d'une seule configuration.
Utilisez les espaces de travail CLI (`terraform workspace new prod`) pour une isolation légère, ou des configurations racines/espaces de travail HCP séparés pour une séparation plus forte.
Pourquoi: Chaque espace de travail a son propre état ; référencez `terraform.workspace` pour varier la dénomination/taille. Pour une isolation forte, préférez des backends/espaces de travail distincts.
Opérations HCP Terraform
Évitez de stocker des clés cloud à longue durée de vie dans les variables des espaces de travail HCP Terraform.
Configurez des identifiants de fournisseur dynamiques : HCP Terraform utilise OIDC/identité de charge de travail pour obtenir des identifiants de courte durée auprès d'AWS/Azure/GCP/Vault par exécution.
Pourquoi: Élimine les secrets statiques ; les identifiants sont générés juste à temps et expirent, réduisant ainsi le rayon d'impact.
Les mêmes variables (configuration du fournisseur, balises) sont nécessaires dans de nombreux espaces de travail.
Définissez un ensemble de variables et appliquez-le à un projet ou à des espaces de travail sélectionnés. Les variables au niveau de l'espace de travail remplacent les valeurs de l'ensemble de variables.
Pourquoi: Les ensembles de variables évitent de dupliquer la configuration partagée ; la précédence (espace de travail > ensemble) permet à un espace de travail de la remplacer si nécessaire.
Appliquez des garde-fous (pas de S3 public, balises requises) à chaque exécution.
Attachez un ensemble de politiques Sentinel ou OPA. Définissez le niveau d'application : consultatif (avertissement), semi-obligatoire (contournable avec permission), ou obligatoire (blocage).
Pourquoi: La politique en tant que code gère les exécutions de manière centralisée ; les niveaux d'application équilibrent la rigueur et la flexibilité opérationnelle.
Intégrez une vérification externe (estimation des coûts, analyse de sécurité) dans le pipeline d'exécution.
Configurez une tâche d'exécution à une étape (pré-planification, post-planification, pré-application). HCP Terraform appelle le service externe et conditionne l'exécution à son résultat.
Pourquoi: Les tâches d'exécution étendent le pipeline avec des vérifications tierces sans plomberie CI personnalisée.
Choisissez comment les exécutions sont déclenchées pour un espace de travail.
Piloté par VCS (commit/PR déclenche le plan), piloté par CLI (`terraform plan/apply` sur le distant), ou piloté par API (configuration téléchargée). Choisissez par flux de travail d'équipe.
Pourquoi: Le pilotage par VCS convient à GitOps ; le pilotage par CLI convient à l'itération locale ; le pilotage par API convient aux pipelines personnalisés. Ils sont mutuellement exclusifs par espace de travail.
Accordez à une équipe un accès en écriture aux espaces de travail de staging mais un accès en lecture seule à la production.
Définissez les permissions au niveau de l'organisation/projet/espace de travail : attribuez l'accès de l'équipe (lecture/planification/écriture/admin) par projet ou espace de travail ; utilisez le regroupement par projet pour gérer à grande échelle.
Pourquoi: Des permissions granulaires et délimitées appliquent le principe du moindre privilège ; les octrois au niveau du projet réduisent la gestion par espace de travail.
L'application d'un espace de travail de mise en réseau doit automatiquement mettre en file d'attente une exécution dans les espaces de travail d'application dépendants.
Configurez un déclencheur d'exécution : l'espace de travail aval s'abonne à l'amont ; une application réussie met en file d'attente l'exécution aval.
Pourquoi: Les déclencheurs d'exécution enchaînent les espaces de travail dépendants afin que les changements d'infrastructure partagée se propagent dans l'ordre.
Permettez aux utilisateurs non-Terraform de provisionner des infrastructures standardisées via un formulaire.
Publiez un module sans code dans le registre privé ; les utilisateurs l'instancient via l'interface utilisateur, en fournissant uniquement les entrées - sans rédaction HCL.
Pourquoi: Les modules sans code démocratisent le provisionnement en libre-service tout en maintenant la configuration sous-jacente gouvernée et versionnée.
Partagez des modules et des fournisseurs vérifiés au sein de l'organisation.
Publiez dans le registre privé HCP Terraform ; les consommateurs référencent `app.terraform.io/org/name/provider` avec des contraintes de version.
Pourquoi: Un registre privé centralise la découverte, le versionnement et la gouvernance des modules internes.
Organisez des dizaines d'espaces de travail par équipe/application pour les permissions et les ensembles de variables.
Regroupez les espaces de travail en projets ; appliquez les permissions d'équipe et les ensembles de variables au niveau du projet.
Pourquoi: Les projets mettent à l'échelle la gouvernance - vous gérez l'accès et la configuration partagée par projet au lieu de par espace de travail.
Détectez continuellement quand la production dérive de l'état configuré.
Activez les évaluations de santé (détection de dérive / validation continue) sur l'espace de travail ; HCP Terraform actualise périodiquement et signale la dérive et les assertions échouées.
Pourquoi: Les évaluations automatisées mettent en évidence la dérive et les postconditions brisées entre les applications, avant qu'elles ne causent des incidents.
HCP Terraform doit atteindre l'infrastructure à l'intérieur d'un réseau privé sans entrée publique.
Déployez des agents HCP Terraform dans le réseau privé et attribuez l'espace de travail à un pool d'agents ; les exécutions se font via l'agent.
Pourquoi: Les agents permettent à HCP Terraform d'opérer dans des environnements privés/isolés sans les exposer publiquement.
Une mauvaise application a corrompu l'état et vous devez le récupérer.
HCP Terraform conserve l'état versionné ; revenez à une version d'état antérieure depuis l'interface utilisateur/API de l'espace de travail et re-planifiez.
Pourquoi: Le versionnement d'état intégré fournit des points de récupération sans avoir à gérer vous-même les instantanés de backend.