HCTA0-004 — Fiche concise (révision express)
IAC / BASICS
Terraform = IaC déclaratif HashiCorp, lang HCL, open-source / décrit état désiré → compare état réel → applique le diff
Workflow standard cloud-agnostique : init → plan → apply (mm pr tt provider)
Cloud-agnostique = workflow standardisé, PAS ressources portables (aws_instance ≠ déployable sur Azure)
Multi-cloud = plusieurs clouds dans 1 config (AWS+Azure+GCP) / Hybrid = public + on-prem (vSphere, k8s self-hosted)
Pourquoi : éviter vendor lock-in, workloads par affinité, résilience, cloud-agnosticité
Identifiant : aws_instance=type, mon_instance=nom logique, aws_instance.mon_instance=id complet (state, console)
PROVIDERS
Provider = plugin qui traduit .tf (HCL) en appels API d'un service (AWS, Azure, GitHub, Docker…)
Déclaré dans terraform { required_providers {} } (source+version) + bloc provider "x" {} pr config (region, creds)
Téléchargés au terraform init → dans .terraform/ / changement de version = relancer init (-upgrade)
BP : TJR figer version, JAMAIS secrets en clair (env vars / auth système)
ALIAS : DEFAULT 1 seule config par provider / alias = "2" pr multi-region/multi-compte → réf via aws.2
Random provider : random_password, random_uuid (à déclarer dans required_providers OBLIGATOIRE)
COMMANDES CLÉS
init : init dir, dl providers/modules, crée .terraform/, prépare backend
validate : syntaxe+cohérence interne, statique, NE contacte PAS provider, NE lit PAS state (rapide, CI)
fmt : reformate .tf / -recursive sous-dossiers / -check (exit≠0 si à reformater, CI)
plan : simule diff config vs state / -out=tfplan sauve plan → apply tfplan / -refresh-only sync state seul
apply : applique le diff / apply tfplan applique plan sauvé
destroy : détruit TOUT ce que gère le state (prudence)
refresh : sync state avec réalité cloud (OBSOLÈTE depuis 0.15 → préférer plan -refresh-only)
import : intègre ressource existante au state (resource doit déjà être déclarée dans .tf !) → puis plan
taint/untaint : marque/démarque ressource "à recréer" au prochain apply
force-unlock <ID> : libère un lock bloqué
graph : terraform graph | dot -Tpng > g.png (besoin graphviz) / get -update : dl/maj modules
providers schema : structure JSON des providers (ressources, args obligatoires/facultatifs)
console : REPL pr tester expressions/fonctions/types (ex type(...)) / -state= pr interroger le state
show (tt le state lisible, -json) / output (-json pr CI)
STATE
terraform.tfstate = carte ressources déployées (id, attrs, métadonnées) → lien config ↔ réalité
Rôle : suivi ressources, base de plan/apply/destroy, dépendances implicites, stocke outputs
SERIAL = n° version state (+1 à chaque modif) / LINEAGE = id unique du state (change si init ailleurs)
.tfstate.backup créé à chaque apply (state d'avant dernier apply)
State peut contenir des SECRETS en clair → chiffrer, accès restreint, JAMAIS éditer à la main (sauf expert)
BP : versionner .tf mais PAS le state local, backend distant en équipe
state list/show <r>/rm <r> (délie sans détruire)/mv <r> (rename ou déplace entre modules)
Bloc moved {from to} = équivalent déclaratif de state mv / removed {from, lifecycle{destroy=false}} = state rm déclaratif
BACKEND
Définit OÙ + COMMENT le state est stocké / DEFAULT = local
Distant (S3, Azure Blob…) pr partage/sécu/CI/équipe / gère LOCKING + historique
Indépendant des providers / changement de backend = re-init pr migrer le state
LOCKING : 2e apply concurrent BLOQUÉ (ex S3+DynamoDB) → erreur acquiring lock
.terraform.lock.hcl (≠ state lock) : verrouille versions+hashes providers (reproductibilité) / maj via init -upgrade
VARIABLES
variable "x" { type, default, description } (type/desc facultatifs)
Déclaration valeur : CLI -var="x=y" / fichier .tfvars (-var-file=) / env TF_VAR_x / default
terraform.tfvars ou *.auto.tfvars chargé AUTO (sans -var-file)
PRÉCÉDENCE (gagnant en 1er) : 1.CLI -var > 2..tfvars/.tfvars.json > 3.TF_VAR_ env > 4.default code
validation { condition, error_message } (||=OU, &&=ET) → bloque si faux
sensitive = true : masque dans outputs/logs MAIS visible en clair dans tfstate !
ephemeral = true (TF 1.10+) : JAMAIS dans state ni plan file (var/output/resource jetables)
write_only (_wo) : envoyé au provider, jamais relu → pas dans state (+_wo_version à incrémenter)
Règle secret : préférer ephemeral+write_only à sensitive seul
CUSTOM CONDITIONS
validation (sur variable) : avant tout, BLOQUE
precondition (lifecycle) : avant création/lecture ressource, BLOQUE
postcondition (lifecycle) : après création/lecture, BLOQUE (self.x accessible)
check {} (top-level) : fin de plan/apply, WARNING seulement (NE bloque PAS), monitoring continu
OUTPUTS / LOCALS / DATA
output : expose valeurs post-apply (IP, id) / output, -json / réf inter-module module.NAME.OUTPUT
locals : valeurs intermédiaires/calculs, NON modifiables de l'extérieur, centralise (DRY)
datasource data "x" "y" {} : LIT ressource existante (non créée par TF) pr réutiliser ses attrs / réf data.x.y.attr
Interpolation ${...} (ex web-${var.env})
TYPES
Primitifs : string, number, bool
Collections : list (ordonnée), set (non ordonnée sans doublon), map (clé-valeur)
Complexe : object({...}), tuple([...])
RÉPÉTITION
count = N copies identiques → réf r[0], r.*.id (splat) / index numérique
for_each (map/set) → each.key/each.value / clés stables (mieux pr ajout/suppr sans tt recréer)
Piège count vs for_each : count réindexe (suppr milieu = décale tt) / for_each clé = stable
for expr : [for x in liste : f(x)] (liste) / {for i in l : k => v} (map)
dynamic "bloc" { for_each, content {} } : génère blocs imbriqués dynamiquement
splat r[*].id : tous les id des instances count/for_each
BLOCS SPÉCIAUX / LIFECYCLE
nested blocks = blocs dans blocs (ex default_action {} dans listener)
depends_on = [r] : dépendance EXPLICITE (ordre forcé) / implicite = réf attr d'une autre ressource (auto)
lifecycle :
create_before_destroy = true : crée AVANT de détruire (zéro downtime, besoin name_prefix si nom unique)
prevent_destroy = true : bloque suppression accidentelle (VPC, S3 critiques)
ignore_changes = [attr] ou [all] : ignore modifs hors TF (ex tags)
replace_triggered_by = [r.attr] : force recréation si autre ressource/var change
PROVISIONERS (last resort)
local-exec (cmd locale), remote-exec (sur la ressource), file (copie fichier)
RIEN stocké dans le state (destroy n'annule pas la cmd) / À éviter sauf nécessité
null_resource + provisioner pr exécuter du shell
FONCTIONS (utiles exam)
try(a,b,default) : 1re sans erreur / coalesce() : 1er non-null/non-vide / lookup(map,k,default) : fallback sûr
templatefile(path,{vars}) : injecte vars dans template (remplace data template_file ARCHIVÉ 2021)
length, contains, element (cyclique), keys, values, zipmap, merge (clés dupl = remplacées)
toset (dédup non ordonné) vs distinct (dédup garde ordre) / flatten, compact (vire ""), setproduct
join(liste→str) ↔ split(str→liste) / upper/lower / cidrsubnet("10.0.0.0/16",8,1)→10.0.1.0/24 / regex
Heredoc <<EOF ... EOF (multi-lignes, user_data) / escapes \n \" \\ / \$ = var shell non interprétée par TF
DEBUG / SÉCU
TF_LOG : TRACE>DEBUG>INFO>WARN>ERROR / TF_LOG_PATH= fichier / TF_LOG_CORE / TF_LOG_PROVIDER
TRACE = gros volumes (clés API, payloads) → JAMAIS commit ces logs
BP : Git pr versionner, code review, backend distant + locking, OpenTofu = state crypté
MODULES
Module = unité réutilisable (input=variables, logique=main.tf, output=résultats) comme une fonction
Pourquoi : réutilisabilité, organisation, collab, standardisation, maintenance (1 maj → partout)
Fichiers : main.tf (ressources), variables.tf (inputs), outputs.tf (exposés), README.md
Root/Parent module = entry point (appelle les childs) / Child module = appelé
SOURCES : local ./modules/vpc / Git git::https://...git?ref=v1.2.0 / Registry terraform-aws-modules/vpc/aws
VERSIONING : Git via ?ref=tag / Registry via version = "~> 4.0" (PAS de version pr modules locaux)
~> 4.0 = >=4.0 <5.0 (pessimistic) / figer pr éviter breaking changes, rollback facile
Flux : child variable → root passe valeur à l'appel / child output → root module.NAME.OUTPUT
Child module hérite providers du root, NE déclare PAS provider {} / cas alias → providers = { aws = aws.us } explicite
Appel multiple : count/for_each sur le bloc module {}
terraform get -update : dl/maj modules
MULTI-ENV
- Dossiers séparés :
envs/dev,envs/prod(chacun sa config+state+tfvars) → flexible, explicite - Workspaces CLI :
workspace new/select dev, mm code, states isolés +tfvarspar env BP : 1 tfvars par env + backend distant Chemins :path.cwd(où TF lancé) /path.root(root module) /path.module(module courant)
HCP / TERRAFORM CLOUD
HCP Terraform (ex-Terraform Cloud) = SaaS HashiCorp : state remote, runs, gouvernance, collab
Features : state versionné+locking auto, UI+API, runs sur push Git, RBAC, secrets/Vault, Sentinel/OPA, Run Tasks
HIÉRARCHIE : Organisation > Projets > Workspaces (+ Teams, Module Registry privé, Policies)
Organisation = top-level (entreprise/client) / SSO SAML, 2FA, éviter "owner" partout
Projet = couche intermédiaire org↔workspaces / regroupe par domaine, RBAC par projet
Workspace = unité d'exécution ISOLÉE (1 state, vars, runs, logs) / BP : 1 workspace = 1 composant = 1 env
PAS de multi-env dans 1 workspace (pas de var env de switch)
WORKSPACES — MODES & VARS
CLI-driven : terraform apply local + HCP backend / besoin terraform login + bloc cloud {}
VCS-driven (GitOps) : lié à repo Git → push/PR = plan auto (prod, contrôle qualité)
API-driven : runs via API REST HCP (CI/CD perso : GitLab CI, GH Actions)
Vars : Terraform vars (TF_VAR_, visibles plan) / Env vars (creds) / Sensitive (masquées UI, MAIS dans tfstate)
Variable Sets : définir 1 fois → appliquer à plusieurs ws/projets (creds partagés)
Portée Var Set : Global (toute l'org) / Project-scoped / Workspace-scoped
Précédence : workspace > project > global (var ws ÉCRASE var set)
Run Triggers : apply OK sur ws A → plan auto sur ws B (chaînage déploiements) / MAX 20 sources / cycles INTERDITS / lance plan seul (apply selon config)
Auto-apply (dev/préprod) vs Manual approval (prod/critique)
VCS / POLICY / CONNEXION
VCS supportés : GitHub (+Enterprise), GitLab, Bitbucket Cloud (PAS Server), Azure DevOps / autres via API
BP VCS : 1 branche = 1 env, PR+review obligatoire, protection de branche, auto-apply seulement sur branche protégée
Sentinel = policy-as-code propriétaire HashiCorp (objets tfplan/tfconfig/tfstate natifs, stack HC)
OPA = standard open-source CNCF, langage Rego (cohérence si déjà OPA ailleurs, anti lock-in)
Enforcement (Sentinel & OPA) : advisory (warning) / soft-mandatory (bloque, admin override) / hard-mandatory (bloque, AUCUN override)
Bloc cloud {} (TF 1.1+, MODERNE, sélection workspace par tags) vs backend "remote" (LEGACY)
terraform login : ouvre navigateur → API token dans ~/.terraform.d/credentials.tfrc.json / logout pr révoquer
CI/CD : PAS de login → env var TF_TOKEN_app_terraform_io
Module Registry privé : modules publiés depuis repo VCS avec tag vX.Y.Z valide, visibles ds tte l'org
Explorer : recherche/analyse ressources cross-workspaces (ex "qui utilise TF 1.5 ?")
Change Requests : workflow review/approbation d'un plan avant apply (séparation rôles, audit trail, N approbateurs)
PIÈGES EXAM
Précédence vars : CLI > tfvars > TF_VAR_ env > default
count (index, réindexe) vs for_each (clé stable) → for_each pr ajout/suppr propre
sensitive masque l'affichage MAIS reste en clair dans tfstate (≠ ephemeral/write_only)
refresh obsolète → plan -refresh-only / import ne génère PAS la config (à écrire avant)
child module hérite providers (pas de bloc provider, sauf alias via providers={})
check {} = warning (ne bloque pas) vs pre/postcondition = bloque
Var Set précédence : workspace > project > global / cloud {} moderne > backend "remote" legacy
terraform_remote_state (data source) : lit outputs d'un autre state distant pr partager des valeurs entre configs