Dernière révision : mai 2026
Configurez les services AWS figurant à l'examen 004 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 laboratoire, vous aurez exécuté le workflow Terraform principal de bout en bout et abordé chaque domaine de l'examen Associate — sans compte cloud ni le moindre coût. Nous utilisons les fournisseurs random et local, qui ne nécessitent aucune accréditation, de sorte que l'accent reste sur ce que l'examen teste réellement : comment Terraform se comporte.
Vous écrirez votre première ressource, la paramétrerez avec des variables et des sorties, générerez de nombreuses ressources avec for_each, refactoriserez la répétition en un module réutilisable, inspecterez et migrerez l'état, et enfin pointerez la configuration vers HCP Terraform pour des exécutions à distance. Chaque extrait est du Terraform pur — le même code fonctionne sans modification sur OpenTofu. Déposez les blocs dans un seul fichier main.tf (nous signalerons les quelques-uns qui vivent dans leurs propres fichiers), exécutez terraform init une fois, puis terraform apply étape par étape.
>= 1.5 ou OpenTofu >= 1.6 sur votre PATH (terraform version). Nous utilisons des blocs import basés sur la configuration à l'étape 6, qui nécessitent la version 1.5 ou ultérieure.random et local s'exécutent entièrement sur votre machine.mkdir tf-associate-lab && cd tf-associate-lab).Ce laboratoire est entièrement gratuit. Les fournisseurs random et local ne créent aucune ressource cloud — seulement quelques petits fichiers sur votre propre disque et des entrées dans un fichier terraform.tfstate local. L'étape 7 (HCP Terraform) utilise le niveau gratuit, ce qui est suffisant pour un seul espace de travail de laboratoire. Rien ici ne sera facturé au repos.
Avant toute exécution, nous déclarons la version de Terraform que nous attendons et les fournisseurs dont nous dépendons. L'épinglage est un sujet prisé à l'examen — les domaines Principes fondamentaux de Terraform et Workflow principal testent tous deux votre compréhension de required_version, du bloc required_providers et du rôle de terraform init dans le téléchargement des plugins de fournisseur dans .terraform/.
Nous choisissons délibérément random et local. Aucun des deux ne nécessite de connexion cloud, ce qui permet au laboratoire de rester gratuit et reproductible, et l'examen ne pose de toute façon jamais de questions sur un cloud spécifique — il interroge sur Terraform. Déposez ceci dans un nouveau fichier main.tf et exécutez terraform init ; vous verrez Terraform résoudre et verrouiller les deux fournisseurs dans un fichier .terraform.lock.hcl, ce qui est en soi un point de discussion du domaine des principes fondamentaux (validez le fichier de verrouillage afin que chaque exécution utilise des versions de plugin identiques).
terraform {
required_version = ">= 1.5"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
# Both providers run locally and need no "provider" configuration
# block at all - a useful reminder that providers are just plugins.Nous exerçons maintenant le cœur du domaine Utiliser le workflow Terraform principal (18 % de l'examen). Nous déclarons une ressource random_pet qui invente un nom convivial, puis une ressource local_file qui écrit ce nom sur le disque. La ressource local_file.greeting référence random_pet.name.id, et cette référence est ce qui indique à Terraform que le fichier dépend de l'animal — ordonnancement implicite des dépendances, aucun depends_on n'est nécessaire.
Exécutez le workflow dans l'ordre : terraform plan vous montre une différence de ce qui va changer avant que quoi que ce soit ne se produise, et terraform apply concrétise ces changements et enregistre le résultat dans terraform.tfstate. Exécutez terraform apply une deuxième fois sans rien modifier et vous verrez Aucun changement — c'est l'idempotence, et l'examen aime demander pourquoi une deuxième application est une opération nulle. Avec une ressource fonctionnelle et un nouveau fichier d'état en main, nous pouvons commencer à rendre la configuration flexible.
resource "random_pet" "name" {
length = 2
separator = "-"
}
resource "local_file" "greeting" {
filename = "${path.module}/hello.txt"
content = "Hello from ${random_pet.name.id}!\n"
}Les valeurs codées en dur sont acceptables pour une démonstration, mais le domaine Lire, générer et modifier la configuration (19 %) s'attend à ce que vous paramétriez. Nous ajoutons une variable d'entrée avec un type, une default et un bloc validation qui rejette tout ce qui se trouve en dehors de nos environnements autorisés — la validation s'exécute au moment de la planification et est une question d'examen fréquente. Nous calculons une carte locals une fois et la réutilisons, et nous exposons les résultats avec des blocs output afin que d'autres configurations (et terraform output) puissent les lire.
Remarquez la stratification testée par l'examen : variable est une entrée, locals est une valeur dérivée/intermédiaire, et output est le résultat publié. Appliquez à nouveau et essayez terraform output pet_name pour lire une seule valeur, ou terraform output -json pour l'utiliser dans un script. Avec les entrées et les sorties configurées, nous sommes prêts à arrêter d'écrire une ressource à la fois et à générer un ensemble complet.
variable "environment" {
description = "Deployment environment label."
type = string
default = "dev"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "environment must be one of: dev, staging, prod."
}
}
locals {
common_tags = {
environment = var.environment
managed_by = "terraform"
}
}
output "pet_name" {
description = "The generated pet name."
value = random_pet.name.id
}
output "tags" {
value = local.common_tags
}Les configurations réelles déclarent rarement une instance de chaque élément. Ici, nous nous penchons davantage sur le domaine Lire, générer et modifier la configuration en pilotant la création de ressources à partir d'une collection. Une variable set(string) liste les services logiques ; for_each crée ensuite une random_string et un local_file par service, adressables comme random_string.suffix["api"] et ainsi de suite.
Cette étape met également en évidence les expressions et les fonctions intégrées que l'examen s'attend à ce que vous reconnaissiez : each.key pour l'élément actuel, l'interpolation de chaînes pour construire un nom de compartiment par service, et jsonencode() pour transformer un objet HCL en un fichier JSON sur disque. Nous réutilisons var.environment et local.common_tags de l'étape 3 afin que chaque fichier généré porte des métadonnées cohérentes. La question suivante évidente — cela devient répétitif, comment puis-je l'empaqueter ? — est précisément ce à quoi les modules répondent.
variable "services" {
description = "Logical services to generate a config file for."
type = set(string)
default = ["api", "web", "worker"]
}
resource "random_string" "suffix" {
for_each = var.services
length = 6
special = false
upper = false
}
resource "local_file" "service_config" {
for_each = var.services
filename = "${path.module}/config/${each.key}.json"
content = jsonencode({
service = each.key
environment = var.environment
bucket = "${each.key}-${random_string.suffix[each.key].result}"
tags = local.common_tags
})
}Le domaine Interagir avec les modules Terraform attend de vous que vous créiez un module, que vous l'appeliez et que vous lui passiez des valeurs. Nous déplaçons la logique par service dans un module enfant sous ./modules/service, lui donnons ses propres entrées variable et une output, puis l'appelons depuis la racine avec for_each — une instance de module par service.
Les deux fichiers ci-dessous montrent clairement la limite : le module enfant ne sait rien des services qui existent (c'est le travail de l'appelant via var.name), et la racine ne sait rien de la manière dont un service est construit (c'est encapsulé dans le module). Exécutez terraform init à nouveau après avoir ajouté un module — l'examen teste que les nouvelles sources de modules nécessitent une réinitialisation pour être installées. Notre configuration étant désormais modulaire, le dernier grand sujet de l'examen Associate est ce que Terraform a discrètement suivi tout ce temps : l'état.
# modules/service/main.tf
variable "name" {
type = string
}
variable "environment" {
type = string
}
resource "random_string" "suffix" {
length = 6
special = false
upper = false
}
resource "local_file" "config" {
filename = "${path.root}/config/${var.name}.json"
content = jsonencode({
service = var.name
environment = var.environment
bucket = "${var.name}-${random_string.suffix.result}"
})
}
output "bucket_name" {
value = "${var.name}-${random_string.suffix.result}"
}
# main.tf (root) - call the module once per service
module "service" {
source = "./modules/service"
for_each = var.services
name = each.key
environment = var.environment
}
output "service_buckets" {
value = { for k, m in module.service : k => m.bucket_name }
}Les domaines Implémenter et maintenir l'état (19 %) et Utiliser Terraform en dehors du workflow principal (9 %) sont tous deux abordés ici. L'état est le registre JSON qui mappe les adresses de votre configuration aux objets réels ; terraform state list l'énumère et terraform state show <addr> affiche une entrée. L'examen s'attend à ce que vous sachiez que renommer une ressource dans la configuration la détruirait et la recréerait normalement — à moins que vous n'indiquiez à Terraform que l'adresse a été déplacée.
Un bloc moved fait exactement cela de manière déclarative : renommez local_file.greeting (de l'étape 2) en local_file.welcome et le bloc moved migre l'état sur place, de sorte que plan affiche un déplacement, et non une destruction et une recréation. (L'équivalent impératif est terraform state mv local_file.greeting local_file.welcome.) Nous montrons également un bloc import basé sur la configuration — la méthode 1.5+ pour adopter un objet préexistant dans l'état sans la commande CLI terraform import plus ancienne. L'état étant sous contrôle, il reste une capacité à maîtriser : l'exécution de tout cela à distance.
# Renaming a resource? A "moved" block migrates state in place
# instead of destroying and recreating the object. Replace the
# Step 2 "greeting" resource with this renamed "welcome" one.
moved {
from = local_file.greeting
to = local_file.welcome
}
resource "local_file" "welcome" {
filename = "${path.module}/hello.txt"
content = "Hello from ${random_pet.name.id}!\n"
}
# Config-driven import (Terraform 1.5+): adopt an object that
# already exists into state, no "terraform import" CLI command.
# terraform_data is a built-in resource - nothing to provision.
import {
to = terraform_data.tracked
id = "existing-id"
}
resource "terraform_data" "tracked" {}Le dernier domaine, Comprendre les capacités de HCP Terraform (5 %), complète l'examen. Un seul bloc cloud à l'intérieur de terraform {} remplace l'exécution locale par HCP Terraform : l'état est stocké à distance et verrouillé pendant les exécutions, terraform plan/apply s'exécute sur les runners de HashiCorp, et la sortie de l'exécution (plus un plan stocké) apparaît dans l'interface utilisateur web. C'est également là que l'examen met en contraste les fonctionnalités de HCP Terraform — état distant, historique des exécutions, application des politiques avec Sentinel/OPA et un registre de modules privé — avec le workflow purement local que nous avons utilisé aux étapes 1 à 6.
Remplacez my-org par votre propre organisation, exécutez terraform login une fois pour stocker un jeton API, puis terraform init pour migrer l'état vers l'espace de travail distant. Tout ce que vous avez écrit dans ce laboratoire s'exécute désormais sans changement — seul l'endroit où il s'exécute a été déplacé. Ce parcours complet, d'un simple main.tf à un espace de travail distant, représente l'intégralité du programme Associate en une seule séance.
terraform {
cloud {
organization = "my-org"
workspaces {
name = "terraform-associate-lab"
}
}
}
# Run once to authenticate, then re-init to migrate state:
# terraform login
# terraform initTout réside sur votre machine, le nettoyage est donc rapide :
terraform destroy pour supprimer les fichiers générés et les effacer de l'état.terraform-associate-lab de l'interface utilisateur de HCP Terraform (ou exécutez terraform destroy contre celui-ci d'abord), puis supprimez le bloc cloud.cd .. && rm -rf tf-associate-lab. Le cache de plugin local .terraform/, le fichier .terraform.lock.hcl et terraform.tfstate sont supprimés avec lui.L'examen Associate porte sur l'outil Terraform, et non sur un cloud en particulier, c'est pourquoi ce laboratoire ne provisionne intentionnellement aucune infrastructure cloud. Nous ignorons volontairement les ressources AWS / Azure / GCP : elles nécessitent des identifiants, peuvent entraîner des coûts et détourneraient l'attention des mécanismes que l'examen teste réellement — le workflow, l'état, le langage de configuration et les modules.
Quelques sujets de l'examen Associate sont mieux appris par la lecture que par la pratique : les backends autres que HCP Terraform (S3, Azure Blob, GCS, Consul), les provisioners comme remote-exec (que HashiCorp considère comme un dernier recours), et les workspaces pour gérer plusieurs instances d'état. Pour ceux-ci, les sections Parcourir et Guide de cette page de certification offrent la couverture conceptuelle. La valeur pratique ici est la mémoire musculaire de init → plan → apply, la lecture d'un fichier d'état et le refactoring sécurisé avec moved.