Dernière révision : mai 2026
Configurez les services AWS figurant à l'examen TF-PRO avec Terraform simple — un bloc à la fois, chacun étant lié à un domaine de l'examen. Le même code fonctionne sur OpenTofu.
À la fin de ce labo, vous aurez mis en pratique les quatre domaines les plus importants de l'examen Terraform Pro : l'écriture de HCL avancé, la construction et la composition de modules, la modification de l'état (state surgery) et l'opération sur HCP Terraform — le tout avec les fournisseurs random et local ne nécessitant aucune authentification, il n'y a donc rien à payer et rien à nettoyer à part un dossier.
Vous modéliserez les entrées avec des contraintes de type riches et de la validation, transformerez des collections avec les expressions et fonctions sur lesquelles l'examen s'appuie, créerez un module réutilisable et le diffuserez avec for_each, puis refactoriserez l'état en direct avec les blocs import, moved et removed. La dernière étape connecte le tout à HCP Terraform pour l'exécution à distance. Chaque extrait est du Terraform simple — identique sur OpenTofu. Construisez-le dans un seul répertoire de travail ; nous indiquons les fichiers qui se trouvent sous ./modules.
>= 1.9 ou OpenTofu >= 1.8 (terraform version). Nous utilisons les blocs removed (1.7+) et import (1.5+), donc une version récente est plus importante ici que dans le labo Associate.random et local s'exécutent entièrement sur votre machine.mkdir tf-pro-lab && cd tf-pro-lab).terraform fmt et terraform validate après chaque modification — l'examen Pro suppose que vous les considérez comme faisant partie de la création, et non comme une réflexion après coup.Ce labo est totalement gratuit. Les fournisseurs random et local créent uniquement de petits fichiers sur votre propre disque plus un terraform.tfstate local ; rien n'est provisionné dans un cloud et rien n'est facturé au repos. L'étape 7 (HCP Terraform) s'exécute sur le niveau gratuit, qui couvre un seul espace de travail de laboratoire et les fonctionnalités opérationnelles abordées par l'examen.
Nous commençons le domaine HCL et Configuration en épinglant une version récente de Terraform — l'examen Pro suppose des fonctionnalités linguistiques qui n'existent que dans les versions plus récentes (attributs d'objet optional(), blocs removed). Le bloc required_providers verrouille random et local, et terraform init écrit un .terraform.lock.hcl que vous devriez commiter afin que les plans soient reproductibles au sein d'une équipe.
Les deux habitudes à prendre maintenant s'appliquent à tout le labo : terraform fmt canonicalise l'espacement et l'alignement (l'examen teste qu'il réécrit les fichiers sur place et sort avec un code non nul en mode -check), et terraform validate vérifie que votre configuration est cohérente en interne avant que tout fournisseur ne soit contacté. Avec la chaîne d'outils épinglée et ces deux commandes à portée de main, nous pouvons écrire du HCL qui mérite d'être validé.
terraform {
required_version = ">= 1.9"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
# Build the muscle memory:
# terraform fmt # rewrite files to canonical style
# terraform validate # check internal consistency, offlineL'examen Pro va bien au-delà de type = string. Ici, nous déclarons un type object avec des attributs optional() qui fournissent des valeurs par défaut lorsqu'un appelant les omet, puis attachons plusieurs blocs validation — l'examen teste qu'une variable peut en contenir plus d'un, chacun avec sa propre condition et error_message.
Le try() dans locals est l'autre mouvement de niveau Pro : il renvoie la première expression qui s'évalue sans erreur, de sorte que la lecture d'un attribut potentiellement absent se dégrade gracieusement au lieu d'arrêter le plan. Son équivalent can() (qui renvoie un booléen) apparaît plus tard dans la validation de l'étape 4. Cette variable platform devient la source unique de vérité pour le reste du labo — chaque étape ultérieure la lira, donc bien définir le type et ses garanties ici sera bénéfique par la suite.
variable "platform" {
description = "Platform configuration."
type = object({
name = string
replicas = optional(number, 2)
features = optional(set(string), [])
owners = list(string)
})
validation {
condition = var.platform.replicas >= 1 && var.platform.replicas <= 10
error_message = "replicas must be between 1 and 10."
}
validation {
condition = length(var.platform.owners) > 0
error_message = "At least one owner is required."
}
default = {
name = "lab"
owners = ["platform@example.com"]
}
}
locals {
# try() returns the first error-free expression - here it shields
# against features being unset and classifies the tier.
tier = length(try(var.platform.features, [])) > 0 ? "enhanced" : "standard"
}La manipulation des collections est la partie la plus dense du domaine HCL et Configuration. Nous utilisons setproduct() pour construire chaque paire région/service, puis une expression for pour remodeler ces paires en une carte indexée — le modèle exact qui alimentera ensuite un for_each. Parallèlement, flatten() + distinct() réduisent une liste de listes de propriétaires en un ensemble unique et propre.
Ce sont les fonctions auxquelles les questions Pro reviennent encore et encore : for avec un => pour produire une carte, setproduct pour les produits cartésiens, flatten pour supprimer un niveau d'imbrication, merge pour combiner des cartes, et jsonencode pour sérialiser le résultat. Nous rendons la carte deployments calculée dans un fichier afin que vous puissiez faire cat out/deployments.json et voir la forme produite par votre expression. Cette carte indexée est précisément le type de valeur que nous allons passer au for_each d'un module.
locals {
regions = ["us-east-1", "eu-west-1"]
services = ["api", "web"]
# setproduct builds every (region, service) pair; the for
# expression reshapes the pairs into a keyed map.
deployments = {
for pair in setproduct(local.regions, local.services) :
"${pair[0]}/${pair[1]}" => {
region = pair[0]
service = pair[1]
}
}
# flatten + distinct collapse nested owner lists into a unique set.
all_owners = distinct(flatten([
for owner in var.platform.owners : split(",", owner)
]))
}
resource "local_file" "matrix" {
filename = "${path.module}/out/deployments.json"
content = jsonencode(local.deployments)
}Maintenant, le domaine Modules (25 %). Nous extrayons un module enfant workload sous ./modules/workload avec ses propres required_providers, ses entrées validées et ses sorties publiées. Le can(regex(...)) dans la validation du nom est l'idiome Pro : regex lèverait une erreur en cas de non-correspondance, et can transforme cette erreur en un false que la condition peut utiliser.
Un module bien construit cache ses internes et expose un contrat stable — les appelants passent name et replicas, et récupèrent un id et un manifest_path, sans jamais toucher au random_string à l'intérieur. Le fait que le module déclare ses propres exigences de fournisseur est un point Pro délibéré : la configuration du fournisseur est héritée de la racine par défaut, mais les exigences du fournisseur sont déclarées par module. Avec un module propre en main, la question intéressante est de savoir comment l'instancier plusieurs fois et relier les instances entre elles.
# modules/workload/main.tf
terraform {
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
variable "name" {
type = string
validation {
# can() turns regex's raise-on-no-match into a usable bool.
condition = can(regex("^[a-z][a-z0-9-]{1,30}$", var.name))
error_message = "name must be 2-31 chars: lowercase letter, then letters/digits/hyphens."
}
}
variable "replicas" {
type = number
default = 1
}
resource "random_string" "suffix" {
length = 6
special = false
upper = false
}
resource "local_file" "manifest" {
filename = "${path.root}/out/${var.name}.json"
content = jsonencode({
name = var.name
replicas = var.replicas
id = "${var.name}-${random_string.suffix.result}"
})
}
output "id" {
description = "Stable identifier for this workload."
value = "${var.name}-${random_string.suffix.result}"
}
output "manifest_path" {
value = local_file.manifest.filename
}Toujours dans le domaine Modules, nous appelons maintenant module.workload une fois par charge de travail avec for_each sur un ensemble, définissons replicas conditionnellement par clé, puis consommons la sortie id de chaque instance dans un seul fichier agrégé registry.json. Itérer sur module.workload avec une expression for est la manière canonique dont l'examen connecte les sorties d'un module aux entrées d'une autre ressource.
Le depends_on explicite sur le fichier agrégé est un point d'apprentissage : les dépendances implicites de la référence module.workload ordonnent déjà les choses correctement, mais depends_on rend l'intention indubitable et est occasionnellement requis lorsqu'une dépendance n'est pas exprimée via des données. Exécutez terraform init après avoir ajouté la source du module, puis apply. Avec plusieurs instances de module en direct et leurs sorties agrégées, nous avons un état réel sur lequel opérer — ce qui est le domaine suivant.
# main.tf (root) - fan the module out, then aggregate its outputs
module "workload" {
source = "./modules/workload"
for_each = toset(["api", "web", "worker"])
name = each.key
replicas = each.key == "api" ? 3 : 1
}
resource "local_file" "registry" {
filename = "${path.module}/out/registry.json"
content = jsonencode({
for k, m in module.workload : k => m.id
})
depends_on = [module.workload]
}
output "workload_ids" {
value = { for k, m in module.workload : k => m.id }
}Le domaine Gestion de l'état et de la CLI (25%) est ce qui distingue l'examen Pro de l'Associate. Nous utilisons trois opérations d'état basées sur la configuration. Un bloc import adopte un objet existant dans l'état — nous ciblons un terraform_data intégré afin que l'exemple reste sans identifiants. Un bloc removed supprime une ressource de l'état sans détruire l'objet réel — exactement ce à quoi vous avez recours lorsque vous transférez la propriété à une autre configuration. Et terraform_data avec replace_triggered_by force un remplacement en aval chaque fois que le registre de l'étape 5 change, sans qu'aucun fournisseur ne soit impliqué.
Connaissez également les équivalents impératifs de la CLI que l'examen teste encore : terraform state list et state show pour inspecter, terraform state mv pour renommer, terraform state rm pour oublier, terraform plan -target=ADDR pour affiner une exécution, et terraform apply -replace=ADDR pour forcer la recréation d'une ressource. Une fois l'état fermement sous votre contrôle localement, le dernier domaine consiste à faire tout cela sur HCP Terraform.
# 1) Adopt an existing object into state (Terraform 1.5+),
# replacing the older imperative "terraform import" command.
import {
to = terraform_data.legacy
id = "existing-id"
}
resource "terraform_data" "legacy" {}
# 2) Stop managing a resource WITHOUT destroying it (Terraform 1.7+).
removed {
from = random_string.deprecated
lifecycle {
destroy = false
}
}
# 3) terraform_data + replace_triggered_by recreates a marker
# whenever the registry file content changes.
resource "terraform_data" "deploy_marker" {
input = local_file.registry.content
lifecycle {
replace_triggered_by = [local_file.registry]
}
}Le domaine Opérations HCP Terraform (20%) conclut l'examen. Un bloc cloud — ici utilisant workspaces { tags = [...] } afin qu'une configuration puisse être associée à plusieurs espaces de travail correspondant à des balises — déplace l'exécution de votre ordinateur portable. Les exécutions ont lieu sur HCP Terraform avec un état distant et verrouillé ; l'examen compare les modes d'exécution à distance, local et agent ainsi que les flux de travail d'exécution pilotés par VCS, pilotés par CLI et pilotés par API.
C'est également là que se rejoignent les fonctionnalités opérationnelles citées par l'examen Pro : les ensembles de variables pour partager les entrées entre les espaces de travail, les déclencheurs d'exécution pour enchaîner l'application d'un espace de travail au plan d'un autre, les tâches d'exécution pour les intégrations externes, les ensembles de politiques (Sentinel ou OPA) pour contrôler les applications, et un registre de modules privé pour publier le type de module que vous avez construit à l'étape 4. Remplacez my-org par votre organisation, exécutez terraform login, puis terraform init pour migrer l'état. La configuration que vous avez écrite au cours des étapes 1 à 6 s'exécute sans changement — seuls le lieu et les garde-fous qui l'entourent ont été améliorés, ce qui est toute l'histoire de l'examen Pro.
terraform {
cloud {
organization = "my-org"
workspaces {
tags = ["terraform-pro-lab"]
}
}
}
# Authenticate once, then migrate state to the remote workspace:
# terraform login
# terraform initTout est local, donc le démantèlement est rapide :
terraform destroy pour supprimer les fichiers générés et les effacer de l'état. En raison du bloc removed de l'étape 6, random_string.deprecated (s'il a jamais existé) est oublié plutôt que détruit — c'est intentionnel.terraform destroy sur l'espace de travail distant, puis supprimez l'espace de travail terraform-pro-lab dans l'interface utilisateur et supprimez le bloc cloud.cd .. && rm -rf tf-pro-lab. Le cache .terraform/, .terraform.lock.hcl et tout état local disparaissent avec lui.L'examen Pro teste la création et les opérations Terraform, et non un cloud particulier, c'est pourquoi ce labo ne provisionne aucune infrastructure cloud intentionnellement — cela le garde sans identifiants et permet à chaque étape de se concentrer sur HCL, les modules, l'état et HCP Terraform.
Quelques sujets pertinents pour l'examen Pro sont mieux étudiés que provisionnés ici : les blocs dynamic (qui nécessitent un type de ressource avec des blocs imbriqués répétables — une ressource cloud — pour être significatifs), les configuration_aliases des fournisseurs et le passage de fournisseurs aliasés dans les modules, la création de politiques Sentinel / OPA, et le flux de publication du registre de modules privé. Les sections Parcourir et Guide de cette page de certification les couvrent conceptuellement. La valeur pratique ici est la boucle de création et d'opérations autour de laquelle l'examen est construit : entrées complexes validées, modules composés, chirurgie d'état sans crainte et exécutions à distance.