Comment Terraform fonctionne

8 min
Public
débutant, leçon 01 lue
Durée
30 à 40 min
Module
1/7
Compétence visée
nommer les quatre pièces de Terraform, lire un bloc HCL mot par mot, et reconnaître dans le terminal les phrases que init, plan, apply et destroy affichent quand tout va bien

Une vue d'ensemble

Terraform relie quatre éléments :

  • La configuration décrit le résultat que tu veux.
  • Terraform lit cette configuration et construit un graphe de dépendances.
  • Les providers traduisent les demandes de Terraform en appels aux API des plateformes.
  • Le state associe les blocs de ton code aux véritables objets distants.

Dans l'image de la leçon 01 : la configuration est le plan, le state est le relevé, les providers sont les corps de métier, et le CLI est l'architecte.

Le langage HCL

Terraform utilise principalement le HashiCorp Configuration Language, ou HCL. Le code est organisé en blocs.

hcl
resource "aws_s3_bucket" "logs" {
  bucket = var.bucket_name

  tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
  }
}

Dans cet exemple :

  • resource annonce un objet que Terraform doit gérer ;
  • aws_s3_bucket est le type fourni par le provider AWS ;
  • logs est le nom local utilisé dans ce module ;
  • bucket et tags sont des arguments ;
  • var.bucket_name et var.environment viennent de variables.

Le type et le nom local forment ensemble l'adresse de la ressource : aws_s3_bucket.logs. C'est sous ce nom que tu la retrouveras dans un plan, dans terraform state list et dans les références d'autres blocs.

Provider

Un provider est un plugin qui connaît l'API d'une plateforme et fournit des types de ressources et de sources de données. Terraform installe les providers requis pendant terraform init. Les contraintes de version et le fichier de verrouillage rendent les exécutions plus prévisibles. Référence officielle — Exigences des providers

hcl
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

provider "aws" {
  region = "ca-central-1"
}

La version de Terraform CLI et la version d'un provider sont deux choses différentes. Tu les contrôles séparément : terraform version affiche la première (Terraform v1.12.2 sur la machine du cours), .terraform.lock.hcl fige la seconde.

Ressource et data source

Une ressource représente généralement un objet que Terraform crée ou gère.

hcl
resource "aws_security_group" "web" {
  name = "web"
}

Une data source lit un objet ou une information qui existe déjà sans en devenir automatiquement propriétaire.

hcl
data "aws_vpc" "default" {
  default = true
}

Tu peux ensuite référencer data.aws_vpc.default.id dans une ressource.

Variables, valeurs locales et outputs

Les variables sont les entrées configurables :

hcl
variable "environment" {
  type        = string
  description = "Nom de l'environnement"

  validation {
    condition     = contains(["dev", "test", "prod"], var.environment)
    error_message = "L'environnement doit être dev, test ou prod."
  }
}

Les locals calculent des valeurs internes afin d'éviter les répétitions :

hcl
locals {
  prefix = "application-${var.environment}"
}

Les outputs exposent des résultats utiles :

hcl
output "bucket_id" {
  description = "Identifiant du bucket"
  value       = aws_s3_bucket.logs.id
}

Un projet sans bloc output n'a rien à afficher : terraform output répond alors Warning: No outputs found. C'est le cas du projet 01 ; les outputs arrivent au projet 02.

Le graphe de dépendances

Terraform détecte une dépendance lorsqu'une ressource en référence une autre.

hcl
resource "aws_s3_object" "journal" {
  bucket = aws_s3_bucket.logs.id
  key    = "journal.txt"
  source = "journal.txt"
}

L'objet dépend du bucket. Terraform doit donc créer le bucket avant d'envoyer le fichier. Les ressources sans dépendance entre elles peuvent être traitées en parallèle. L'ordre des blocs dans le fichier n'a aucune importance : seule la référence compte.

Le workflow central

HashiCorp résume le workflow Terraform en trois étapes : Write, Plan, Apply. Référence officielle — Core workflow

Avant la première de ces étapes, il y a un geste unique par dossier : terraform init. Les sorties ci-dessous sont celles du projet 01 de ce module, capturées avec Terraform 1.12.2 sur la machine du cours ; tu les retrouveras mot pour mot quand tu le feras toi-même.

Init — préparer le dossier

powershell
terraform init
text
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.1...
- Installed hashicorp/local v2.9.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. …

Terraform has been successfully initialized!

Trois choses se passent : Terraform télécharge le provider (Installing hashicorp/local v2.9.1), le range dans le dossier caché .terraform/, et écrit .terraform.lock.hcl pour retenir la version choisie. La phrase à attendre est Terraform has been successfully initialized!. Sans init, toute autre commande s'arrête : Error: Inconsistent dependency lock file pour plan, Error: Missing required provider pour validate, et les deux te disent quoi faire (run: terraform init).

Write — écrire

Tu écris ou modifies les fichiers .tf, puis tu lances généralement :

powershell
terraform fmt
terraform validate

terraform fmt réaligne les espaces et l'indentation ; il affiche le nom des fichiers qu'il a modifiés, et rien du tout si tout était déjà propre. terraform validate vérifie la syntaxe et la cohérence des blocs sans toucher à rien, et répond :

text
Success! The configuration is valid.

Il attrape les fautes de frappe dans les noms d'arguments (An argument named "contenu" is not expected here. Did you mean "content"?) et les accolades oubliées (Error: Unclosed configuration block). Il ne vérifie pas que le résultat serait une bonne architecture.

Plan — prévoir

Terraform compare l'état souhaité à ce qu'il connaît de l'infrastructure :

powershell
terraform plan
text
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.message will be created
  + resource "local_file" "message" {
      + content              = "Bonjour, ce fichier a été créé avec Terraform."
      + content_md5          = (known after apply)
      + filename             = "./message.txt"
      + id                   = (known after apply)

    }

Plan: 1 to add, 0 to change, 0 to destroy.

Le plan répond à trois questions, et la dernière ligne les résume en trois nombres :

  • Que va-t-on créer ? (to add)
  • Que va-t-on modifier ? (to change)
  • Que va-t-on détruire ou remplacer ? (to destroy)

Chaque ligne d'attribut porte le symbole de l'action. (known after apply) signale une valeur que Terraform ne peut pas connaître avant d'avoir créé l'objet : ici l'identifiant du fichier et les empreintes de son contenu.

Apply — appliquer

Après révision :

powershell
terraform apply

Terraform réaffiche le plan puis demande ton accord :

text
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

local_file.message: Creating...
local_file.message: Creation complete after 0s [id=ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Seul yes en entier est accepté ; y, Y ou une ligne vide donnent Apply cancelled. et rien n'est touché. Terraform appelle ensuite les providers dans l'ordre dicté par les dépendances, puis met à jour le state. La dernière ligne reprend les trois nombres du plan : Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Si tu relances terraform plan sans avoir rien changé, le devis est vide :

text
No changes. Your infrastructure matches the configuration.

C'est l'idempotence de la leçon 01, vue dans le terminal.

Destroy — tout défaire

powershell
terraform destroy

destroy est un apply dont le plan ne contient que des -. La question posée est plus insistante (Do you really want to destroy all resources? … There is no undo.), la réponse attendue est toujours yes, et la dernière ligne est :

text
Destroy complete! Resources: 1 destroyed.

Après un destroy, terraform state list n'affiche plus rien. Dans ce cours, chaque projet se termine ainsi.

Cycle complet

Les quatre symboles d'un plan

SymboleActionPhrase dans le planCe que ça change pour toi
+créerwill be createdUn objet nouveau ; rien d'existant n'est touché.
~modifier sur placewill be updated in-placeL'objet reste, un attribut change.
-/+remplacermust be replacedL'objet est détruit puis recréé ; son contenu et son identifiant disparaissent.
-détruirewill be destroyedL'objet disparaît.

Déclaratif ne veut pas dire magique

Terraform détermine comment atteindre l'état demandé, mais le comportement exact dépend de chaque provider. Certains arguments peuvent être modifiés sur place ; d'autres obligent à remplacer la ressource. Le provider local du projet 01 en donne un exemple net : changer la ligne content d'un local_file ne produit pas un ~, mais un remplacement -/+. Voici le plan réel, capturé après la modification du texte :

text
  # local_file.message must be replaced
-/+ resource "local_file" "message" {
      ~ content              = "Bonjour, ce fichier a été créé avec Terraform." -> "Deuxième version du fichier créée avec Terraform." # forces replacement
      ~ content_md5          = "6daf774f0eb6d3da439c871afec7cf90" -> (known after apply)
      ~ id                   = "ff6b93dc92e7e1d2ba4f9dad3cc16e03ac649b45" -> (known after apply)
        # (3 unchanged attributes hidden)
    }

Plan: 1 to add, 0 to change, 1 to destroy.

Lis les trois indices : must be replaced dans le titre, # forces replacement au bout de la ligne content, et 1 to add, 0 to change, 1 to destroy dans le résumé. Pour un fichier texte, la nuance est sans conséquence. Pour une base de données, -/+ veut dire perte des données : c'est pourquoi le plan doit être lu, même lorsque le changement de code semble petit.

La différence essentielle entre ~ et -/+. Le ~ devant content dit que la valeur change. Le -/+ devant resource dit comment Terraform va s'y prendre : détruire, puis recréer. Quand une ligne ~ porte # forces replacement, c'est elle qui a provoqué le -/+ du bloc entier.

L'essentiel

  1. Quatre pièces : la configuration décrit, le CLI compare, les providers appellent les API, le state mémorise.
  2. Un bloc resource "type" "nom" a une adresse, type.nom, que tu retrouves dans le plan et dans le state.
  3. init une fois par dossier (Terraform has been successfully initialized!), puis fmt, validate (Success! The configuration is valid.), plan (Plan: 1 to add, 0 to change, 0 to destroy.), apply avec yes (Apply complete! Resources: 1 added, 0 changed, 0 destroyed.), et destroy en fin de séance (Destroy complete! Resources: 1 destroyed.).
  4. Quatre symboles : + crée, ~ modifie sur place, -/+ détruit puis recrée, - détruit. Le résumé Plan: N to add, N to change, N to destroy. les compte.
  5. Le provider décide si un changement est un ~ ou un -/+ ; le local_file du projet 01 est remplacé dès que content change.

Questions de compréhension

  1. Quel composant communique directement avec l'API AWS ou GitHub ?
  2. Pourquoi terraform init doit-il être exécuté, et quels fichiers crée-t-il ?
  3. Quelle différence existe entre une ressource et une data source ?
  4. Comment Terraform sait-il qu'un objet S3 dépend d'un bucket ?
  5. Dans le plan du projet 01 après modification de content, quels trois indices disent qu'il s'agit d'un remplacement et non d'une modification sur place ?
  6. Que répond Terraform si tu tapes y au lieu de yes ?