Aller au contenu
Premier projet : init, plan, apply, destroy

Premier projet : init, plan, apply, destroy

100 Comprendre ⏱ 1 h terraformopentofuiac

À la fin, vous saurez

  • Installer Terraform ou OpenTofu depuis un dépôt signé, et vérifier la version installée
  • Expliquer ce que fait terraform init, et le rôle du fichier .terraform.lock.hcl
  • Lire un plan : création, modification en place, remplacement, destruction
  • Appliquer un plan, constater l'idempotence et provoquer un remplacement
  • Enregistrer un plan, l'appliquer tel quel, et comprendre pourquoi un plan périmé est refusé
  • Détruire proprement, et savoir quels fichiers versionner

Prérequis

Testé avec local 2.9.1 opentofu 1.13.1 random 3.9.1 terraform 1.16.1 , vérifié le 5 octobre 2026

Pourquoi

La leçon 1 a montré un plan sans l'appliquer. Cette leçon fait tourner le cycle complet, du répertoire vide à la destruction, et s'arrête sur chaque étape : ce qu'elle lit, ce qu'elle écrit, ce qu'elle peut casser.

On travaille encore avec des fournisseurs locaux, random et local, pour une raison simple : le cycle de Terraform est le même pour un fichier sur votre disque et pour une base de données chez Scaleway, mais une erreur sur le premier ne coûte rien. Les réflexes que vous prenez ici (lire la dernière ligne du plan, ne jamais confirmer par habitude, ne pas versionner l'état) sont ceux qui, plus tard, éviteront de détruire une base de production.

Toutes les sorties de cette leçon ont été produites avec Terraform 1.16.1 et l'option -no-color ; les identifiants aléatoires seront différents chez vous.

Les concepts

Les cinq commandes du cycle

L'aide de Terraform (terraform -help) range ses commandes en deux groupes, et les cinq premières sont celles du travail quotidien :

CommandeCe qu'elle faitCe qu'elle modifie
initPrépare le répertoire : télécharge les fournisseurs (et les modules), configure le stockage de l'état.terraform/, .terraform.lock.hcl
validateVérifie que la configuration est cohérente (syntaxe, types, références)Rien
planCompare la description à l'état et à la réalité, et affiche les actions nécessairesRien (sauf avec -out)
applyCalcule un plan, demande confirmation, puis exécuteL'infrastructure et l'état
destroyCalcule un plan de destruction de tout ce que gère la configuration, puis exécuteL'infrastructure et l'état

Avec OpenTofu, les mêmes commandes s'écrivent tofu init, tofu plan, etc. Les sorties sont presque identiques, au nom de l'outil près.

Le répertoire de travail

Terraform travaille sur un répertoire : il lit tous les fichiers .tf qu'il contient (pas les sous-répertoires), dans n'importe quel ordre, et les considère comme une seule configuration, le module racine. Au fil des commandes, le répertoire se peuple :

~/signalements-iac/
├── main.tf                   votre description (versionné)
├── .terraform.lock.hcl       versions et empreintes des fournisseurs (versionné)
├── .terraform/               fournisseurs téléchargés (jamais versionné)
├── terraform.tfstate         l'état, créé par apply (jamais versionné)
└── terraform.tfstate.backup  l'état précédent (jamais versionné)

Cette distinction entre ce qui se versionne et ce qui ne se versionne pas est l'une des choses les plus importantes de la leçon ; elle est détaillée plus bas.

Les contraintes de version

Le bloc terraform d'un projet fixe deux sortes de versions :

terraform {
  required_version = ">= 1.10"

  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.7"
    }
  }
}
  • required_version contraint la version de l'outil lui-même : avec une version plus ancienne, Terraform refuse de travailler.
  • required_providers déclare chaque fournisseur, sa source (l'adresse dans le registre, espace/nom) et une contrainte de version.

L'opérateur ~> est le plus utilisé : ~> 3.7 accepte 3.7, 3.8, 3.9... mais pas 4.0. Il autorise les évolutions compatibles et refuse un changement de version majeure, qui peut casser la configuration. >=, <=, = et != existent aussi, et se combinent avec des virgules (">= 3.7, < 4.0").

En pratique

Installer l'outil

Sur Ubuntu ou Debian, chaque projet propose un dépôt APT signé. Le principe est celui de la leçon 12 du cours Linux : on installe la clé de signature dans un fichier dédié, et l'on déclare le dépôt avec signed-by, pour que cette clé ne vaille que pour lui.

Pour Terraform, la documentation de HashiCorp donne :

$ wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
$ sudo apt update && sudo apt install terraform
  • La première ligne télécharge la clé publique de HashiCorp et la convertit au format binaire attendu par APT (--dearmor).
  • La deuxième déclare le dépôt pour l'architecture et le nom de code de votre distribution, limité à cette clé.
  • La troisième installe le paquet.

Pour OpenTofu, la documentation du projet propose un script d'installation, ou la même démarche manuelle avec deux clés (celle qui signe les paquets et celle du dépôt) :

$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL https://get.opentofu.org/opentofu.gpg | sudo tee /etc/apt/keyrings/opentofu.gpg >/dev/null
$ curl -fsSL https://packages.opentofu.org/opentofu/tofu/gpgkey | sudo gpg --no-tty --batch --dearmor -o /etc/apt/keyrings/opentofu-repo.gpg >/dev/null
$ sudo chmod a+r /etc/apt/keyrings/opentofu.gpg /etc/apt/keyrings/opentofu-repo.gpg
$ echo "deb [signed-by=/etc/apt/keyrings/opentofu.gpg,/etc/apt/keyrings/opentofu-repo.gpg] https://packages.opentofu.org/opentofu/tofu/any/ any main" | sudo tee /etc/apt/sources.list.d/opentofu.list >/dev/null
$ sudo apt-get update && sudo apt-get install -y tofu

Le script d'installation (install-opentofu.sh) automatise ces étapes ; comme pour tout script téléchargé, lisez-le avant de l'exécuter.

Sans dépôt (poste sans droits d'administration, image de CI), les deux projets publient des archives binaires accompagnées d'un fichier de sommes de contrôle et de sa signature. Vérifiez la signature du fichier de sommes avec la clé publique de l'éditeur, puis la somme de l'archive, avant de placer le binaire dans votre PATH.

Vérifiez l'installation :

$ terraform version
Terraform v1.16.1
on linux_amd64

En ligne, la commande signale aussi quand une version plus récente existe ; c'était le cas ici, la 1.16.5 étant sortie le 2 octobre 2026.

Le premier fichier

Dans un répertoire vide ~/signalements-iac, créez main.tf avec le contenu de la leçon 1 : un bloc terraform, une ressource random_pet qui fabrique un suffixe de deux mots, et une ressource local_file qui écrit un petit inventaire.

terraform {
  required_version = ">= 1.10"

  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.7"
    }
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

resource "random_pet" "suffixe" {
  length = 2
}

resource "local_file" "inventaire" {
  filename = "${path.module}/inventaire.txt"
  content  = "Bucket des pièces jointes : signalements-pj-${random_pet.suffixe.id}\n"
}

Une ressource se déclare par le mot-clé resource, son type (random_pet, qui commence par le nom du fournisseur) et son nom local (suffixe), libre, qui sert à la désigner ailleurs dans la configuration. L'ensemble forme son adresse : random_pet.suffixe.

init : préparer le répertoire

$ terraform init
Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/random versions matching "~> 3.7"...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/random v3.9.1...
- Installed hashicorp/random v3.9.1 (signed by HashiCorp)
- 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. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

Ligne par ligne :

  • Initializing the backend : Terraform prépare le stockage de l'état. Sans configuration, c'est un fichier local, terraform.tfstate ; la leçon 7 le déplace dans un bucket.
  • Finding ... versions matching : pour chaque fournisseur, il interroge le registre et choisit la plus récente version qui respecte la contrainte, ici 3.9.1 pour ~> 3.7.
  • Installed ... (signed by HashiCorp) : il télécharge le binaire du fournisseur et vérifie sa signature. Un fournisseur publié par un partenaire, comme celui de Scaleway, est annoncé signed by a HashiCorp partner avec l'identifiant de sa clé.
  • Le message final demande de versionner le fichier de verrouillage. C'est important ; voyons pourquoi.

Le répertoire .terraform/ contient les fournisseurs téléchargés, rangés par registre, espace de noms, nom, version et plateforme :

.terraform/providers/registry.terraform.io/hashicorp/random/3.9.1/linux_amd64
.terraform/providers/registry.terraform.io/hashicorp/local/2.9.1/linux_amd64

Chacun contient un exécutable (celui de random pèse environ 18 Mo, celui de Scaleway plus de 40). C'est un cache local, propre à votre machine et à votre plateforme : il ne se versionne jamais.

Le fichier .terraform.lock.hcl, lui, ressemble à ceci (extrait) :

provider "registry.terraform.io/hashicorp/local" {
  version     = "2.9.1"
  constraints = "~> 2.5"
  hashes = [
    "h1:qGLHCuYSus+uHNnoEL4SuJqOs5yrNOcB7gnuHoVqizo=",
    "zh:25606c7a5e308144fb627f6e31611bb52ff72bb9ae2d27af39673ab1a6b3c1bf",
    ...
  ]
}

Il note, pour chaque fournisseur, la version retenue et les empreintes des archives. Les prochaines exécutions de init, sur votre poste, celui d'un collègue ou dans la CI, reprendront exactement cette version et refuseront une archive dont l'empreinte ne correspond pas. C'est le fichier de verrouillage que vous connaissez des gestionnaires de dépendances, avec la même double fonction : reproductibilité et intégrité. Les lignes zh: sont les empreintes des archives publiées pour toutes les plateformes ; la ligne h1: est une empreinte du contenu installé, calculée ici pour Linux. Une équipe qui travaille sur plusieurs systèmes complète le fichier avec terraform providers lock -platform=linux_amd64 -platform=darwin_arm64.

Relancé, init s'appuie sur le fichier :

- Reusing previous version of hashicorp/random from the dependency lock file
- Reusing previous version of hashicorp/local from the dependency lock file
- Using previously-installed hashicorp/random v3.9.1
- Using previously-installed hashicorp/local v2.9.1

Pour passer volontairement à une version plus récente dans les limites de la contrainte, on lance terraform init -upgrade, puis l'on versionne le fichier modifié, comme une mise à jour de dépendance.

plan : voir avant d'agir

terraform plan affiche le plan déjà commenté à la leçon 1 : deux ressources à créer (+), des attributs connus et d'autres (known after apply), et la ligne de synthèse Plan: 2 to add, 0 to change, 0 to destroy. Il se termine par un avertissement :

Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.

Retenez-le, on y revient plus bas.

apply : exécuter, après confirmation

$ terraform apply
...
Plan: 2 to add, 0 to change, 0 to destroy.

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

random_pet.suffixe: Creating...
random_pet.suffixe: Creation complete after 0s [id=rational-glowworm]
local_file.inventaire: Creating...
local_file.inventaire: Creation complete after 0s [id=6312c01310f708efdcb790458d96a5251de6f54a]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
  • apply recalcule un plan, l'affiche, puis demande une confirmation. Seul le mot yes, en entier, est accepté : y, oui ou Entrée annulent.
  • L'ordre des créations suit les dépendances : le fichier attend le nom aléatoire, qu'il utilise.
  • Chaque ressource affiche son identifiant une fois créée : un nom pour random_pet, l'empreinte SHA-1 du contenu pour local_file. Chez Scaleway, ce sera l'identifiant de la ressource, souvent préfixé par sa zone ou sa région.

Le fichier existe :

$ cat inventaire.txt
Bucket des pièces jointes : signalements-pj-rational-glowworm

et un nouveau fichier est apparu, terraform.tfstate, qui garde la correspondance entre vos blocs et ce qui a été créé (leçon 6).

L'option -auto-approve saute la confirmation. Elle a sa place dans une CI qui applique un plan déjà relu et enregistré (leçon 12). Sur un poste, c'est le moyen le plus sûr d'appliquer un jour un plan que l'on n'a pas lu.

L'idempotence, constatée

Relancez terraform plan sans rien changer :

random_pet.suffixe: Refreshing state... [id=rational-glowworm]
local_file.inventaire: Refreshing state... [id=6312c01310f708efdcb790458d96a5251de6f54a]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.

Les lignes Refreshing state montrent que Terraform ne se contente pas de son état : il relit la réalité (ici, le fichier sur le disque) avant de comparer. Rien n'a changé, il n'y a rien à faire. Un apply à ce stade se terminerait par 0 added, 0 changed, 0 destroyed.

Supprimez le fichier à la main (rm inventaire.txt) et relancez le plan : Terraform constate que la ressource a disparu et propose de la recréer. C'est la dérive en miniature, et la convergence qui la corrige.

Modifier : en place ou par remplacement

Changez length = 2 en length = 3 dans la ressource random_pet, puis lancez le plan :

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # local_file.inventaire must be replaced
-/+ resource "local_file" "inventaire" {
      ~ content              = <<-EOT
            Bucket des pièces jointes : signalements-pj-rational-glowworm
        EOT -> (known after apply) # forces replacement
      ...
      ~ id                   = "6312c01310f708efdcb790458d96a5251de6f54a" -> (known after apply)
        # (3 unchanged attributes hidden)
    }

  # random_pet.suffixe must be replaced
-/+ resource "random_pet" "suffixe" {
      ~ id        = "rational-glowworm" -> (known after apply)
      ~ length    = 2 -> 3 # forces replacement
        # (1 unchanged attribute hidden)
    }

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

Ce plan mérite d'être lu lentement :

  • Le symbole -/+ annonce un remplacement : l'objet sera détruit puis recréé. Ce n'est pas une modification.
  • Le commentaire # forces replacement désigne l'attribut responsable. Un nom aléatoire ne se modifie pas : changer sa longueur oblige à en tirer un nouveau. C'est le fournisseur qui le sait, pas Terraform.
  • Le remplacement se propage : le contenu du fichier dépend du nom, donc le fichier est remplacé lui aussi. Un seul changement en a provoqué deux.
  • ~ marque un attribut qui change, et -> sépare l'ancienne valeur de la nouvelle.
  • La synthèse dit 2 to add, 2 to destroy, alors que l'on pensait « modifier un paramètre ».

Transposez à Scaleway : changer l'image d'une instance, la zone d'un volume ou certains paramètres d'une base de données force un remplacement. Pour une instance sans état, c'est acceptable ; pour une base, c'est une perte de données. D'où la règle : chaque -/+ se comprend avant d'être appliqué.

À l'inverse, une modification en place porte le symbole ~. Le fournisseur intégré terraform_data permet de la voir sans cloud : une ressource terraform_data avec input = "1.2.0", passée à "1.3.0", donne :

  ~ update in-place

Terraform will perform the following actions:

  # terraform_data.version_app will be updated in-place
  ~ resource "terraform_data" "version_app" {
        id     = "e02c56ae-bd64-3473-b623-e8784f340f9e"
      ~ input  = "1.2.0" -> "1.3.0"
      ~ output = "1.2.0" -> (known after apply)
    }

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

L'identifiant ne change pas : c'est le même objet, modifié. Le tableau complet des symboles :

SymboleActionCe qui arrive à l'objet réel
+créationil est créé
~modification en placeil est modifié, garde son identité
-/+remplacementdétruit, puis recréé (nouvel identifiant)
+/-remplacement inversécréé d'abord, puis l'ancien détruit (leçon 8)
-destructionil est détruit
<=lectureune source de données est lue (leçon 4)

Enregistrer un plan, et l'appliquer tel quel

Le plan affiché par terraform plan et celui qu'exécute ensuite terraform apply sont deux calculs différents, faits à deux moments différents. Si quelque chose change entre les deux (un collègue applique, une ressource est modifiée dans la console), vous approuvez un plan et en exécutez un autre. L'option -out résout le problème :

$ terraform plan -out=prochain.tfplan
...
Plan: 0 to add, 1 to change, 0 to destroy.

Saved the plan to: prochain.tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "prochain.tfplan"

Le fichier est une archive (file prochain.tfplan répond Zip archive data) qui contient le plan calculé, la configuration et l'état de départ. terraform show prochain.tfplan le relit, et terraform apply prochain.tfplan l'exécute sans demander de confirmation et sans recalculer : la relecture a eu lieu au moment du plan.

Que se passe-t-il si l'état a changé entre-temps ? Appliquez le plan une première fois, puis essayez de l'appliquer une seconde fois :

Error: Saved plan is stale

The given plan file can no longer be applied because the state was changed by
another operation after the plan was created.

Terraform refuse : le plan a été calculé à partir d'un état qui n'est plus l'état actuel. C'est une protection, et c'est la raison pour laquelle les pipelines appliquent un plan enregistré plutôt qu'un apply -auto-approve (leçon 12).

Un plan enregistré peut contenir des valeurs sensibles en clair, comme l'état : il ne se versionne pas, et se supprime après usage.

destroy : tout retirer

$ terraform destroy
...
Plan: 0 to add, 0 to change, 3 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

terraform_data.version_app: Destroying... [id=e02c56ae-bd64-3473-b623-e8784f340f9e]
terraform_data.version_app: Destruction complete after 0s
local_file.inventaire: Destroying... [id=6312c01310f708efdcb790458d96a5251de6f54a]
local_file.inventaire: Destruction complete after 0s
random_pet.suffixe: Destroying... [id=rational-glowworm]
random_pet.suffixe: Destruction complete after 0s

Destroy complete! Resources: 3 destroyed.
  • destroy détruit tout ce que la configuration gère, et seulement cela : une ressource créée à la main dans la console, inconnue de l'état, n'est pas touchée.
  • L'ordre est l'inverse de la création : le fichier, qui dépend du nom, disparaît avant le nom.
  • There is no undo : sur un vrai fournisseur, c'est littéral.

terraform plan -destroy montre le plan de destruction sans rien exécuter ; c'est la commande à lancer avant un destroy, et celle qu'utilisent les pipelines pour démonter un environnement d'aperçu. Pour retirer une ressource, on ne lance pas destroy avec une option de ciblage : on supprime son bloc de la configuration, et le prochain plan propose sa destruction, relue comme toute modification.

Ce qui va dans Git

Le modèle Terraform.gitignore maintenu par GitHub est un bon point de départ. Son contenu essentiel :

# Local .terraform directories
.terraform/

# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log
crash.*.log

*.tfvars
*.tfvars.json

override.tf
override.tf.json
*_override.tf
*_override.tf.json

.terraform.tfstate.lock.info

.terraformrc
terraform.rc

À retenir :

  • Versionner : les fichiers .tf, et le fichier .terraform.lock.hcl (il n'est pas dans la liste, et c'est voulu).
  • Ne jamais versionner : .terraform/ (binaires locaux), *.tfstate (l'état, qui contient des secrets), les plans enregistrés (ajoutez *.tfplan, que le modèle propose seulement en commentaire).
  • Avec discernement : les fichiers *.tfvars, ignorés par le modèle parce qu'ils contiennent souvent des valeurs sensibles. Ceux qui ne contiennent que des valeurs non sensibles (une taille d'instance, une liste de zones) gagnent à être versionnés ; la leçon 5 explique comment séparer les deux.

Sous le capot

Le rafraîchissement. Avant chaque plan, Terraform demande à chaque fournisseur de relire chaque ressource de l'état (Refreshing state). Sur un fournisseur cloud, c'est un appel d'API par ressource : c'est pourquoi un plan sur une grosse configuration prend du temps, et peut buter sur les limites de débit de l'API. L'option -refresh=false le saute, au prix de ne plus voir la dérive.

La confirmation n'est qu'une étape du plan. apply sans argument fait, dans l'ordre : rafraîchir, calculer le plan, l'afficher, attendre yes, exécuter. apply prochain.tfplan saute les quatre premières étapes : il exécute le plan enregistré, après avoir vérifié que l'état n'a pas changé depuis.

Les fichiers de sauvegarde de l'état. Chaque apply qui modifie l'état réécrit terraform.tfstate et garde la version précédente dans terraform.tfstate.backup. Le champ serial de l'état augmente à chaque écriture ; la leçon 6 explique comment il sert à détecter les conflits.

Le cache des fournisseurs. Chaque répertoire de travail télécharge ses fournisseurs. Pour ne pas retélécharger 40 Mo par projet, on définit un cache partagé, par la variable TF_PLUGIN_CACHE_DIR ou le fichier de configuration de la CLI : .terraform/ ne contient alors que des liens vers ce cache.

Pièges courants

Oublier init après un changement de fournisseur. Ajouter un fournisseur ou changer une contrainte de version produit une erreur au plan, qui demande de relancer terraform init. C'est normal : init est la seule commande qui télécharge.

Versionner l'état ou oublier le fichier de verrouillage. Le premier expose les secrets et crée des conflits impossibles à fusionner ; le second laisse chaque poste choisir sa propre version des fournisseurs, et produit des plans différents pour la même configuration.

Répondre yes par réflexe. La confirmation n'a de valeur que si l'on a lu le plan, en commençant par la ligne Plan:. Un to destroy inattendu se refuse, toujours.

Croire qu'un plan affiché sera celui exécuté. Sans -out, apply recalcule. Pour un changement sensible, enregistrez le plan, relisez-le, appliquez-le.

Confondre « modifier » et « remplacer ». -/+ n'est pas ~. Sur une ressource qui porte des données, c'est la différence entre un réglage et une perte.

Lancer l'outil depuis le mauvais répertoire. Terraform travaille sur le répertoire courant. Lancé ailleurs, il voit une configuration vide et un état vide, ou pire, un autre projet. L'option globale -chdir=chemin évite les cd hasardeux dans les scripts.

Sécurité

  • Les droits des fichiers produits. Le plan montrait file_permission = "0777" par défaut pour local_file. Avec un masque umask de 0002 (celui des sessions Ubuntu), le fichier créé est en -rwxrwxr-x : exécutable, et modifiable par le groupe. Pour un fichier qui contiendrait une clé ou un mot de passe, fixez file_permission = "0600", ou mieux, utilisez la ressource local_sensitive_file, qui masque son contenu dans les sorties. Le principe vaut pour tous les fournisseurs : lisez les valeurs par défaut dans le plan.
  • L'état local est lisible par le groupe. Le fichier terraform.tfstate a été créé en -rw-rw-r-- avec ce même masque. Il contiendra des secrets dès que vous créerez une base de données. Sur un poste partagé, c'est une fuite ; la leçon 7 le déplace dans un stockage distant chiffré et aux accès restreints.
  • Vérifiez ce que vous installez. Dépôt signé ou archive vérifiée pour l'outil, signatures et empreintes du fichier de verrouillage pour les fournisseurs : ce sont des binaires qui manipuleront vos identifiants cloud.
  • -auto-approve sur un poste est une faute. En CI, il n'a de sens qu'avec un plan enregistré et relu.

En production

  • Épinglez la version de l'outil pour toute l'équipe et la CI : une contrainte required_version assez étroite, et un outil comme tenv ou mise qui installe la version attendue par le projet. Deux versions différentes peuvent produire des plans différents, et une version récente peut mettre à jour le format de l'état d'une façon que l'ancienne ne lit plus.
  • Mettez à jour les fournisseurs comme des dépendances : init -upgrade dans une branche, relecture du changement du fichier de verrouillage et des notes de version, plan sur chaque environnement, puis fusion. Dependabot et Renovate savent proposer ces mises à jour.
  • Un plan en CI à chaque demande de fusion, publié en commentaire, et un apply du plan enregistré après fusion : c'est l'objet de la leçon 12.
  • Démontez ce qui ne sert plus. Les environnements d'essai qui ne sont jamais détruits sont une source classique de dépenses et de surface d'attaque (cours cloud, leçon 8). plan -destroy dans un pipeline programmé, ou une date d'expiration dans une étiquette, aident à ne pas oublier.

Exercices

1. Le fichier disparu (niveau 100). Après un apply réussi, supprimez inventaire.txt à la main. Que montre terraform plan, et pourquoi Terraform le voit-il alors que l'état n'a pas changé ? Corrigez la situation.

Solution

Le plan propose de recréer local_file.inventaire (+). Terraform le voit parce qu'il rafraîchit l'état avant de comparer : le fournisseur local constate que le fichier n'existe plus, et la ressource est retirée de l'état rafraîchi. terraform apply le recrée, avec le même contenu, puisque le nom aléatoire, lui, n'a pas changé. C'est la convergence : la description l'emporte sur la modification manuelle.

2. Remplacement en cascade (niveau 100). Dans la configuration de la leçon, changez seulement le filename de local_file.inventaire en inventaire-pj.txt. Avant de lancer le plan, prédisez les symboles et la ligne de synthèse, puis vérifiez.

Solution

Seul le fichier est concerné : local_file.inventaire est remplacé (-/+, filename marqué # forces replacement, puisque ce fournisseur ne sait pas renommer un fichier), et random_pet.suffixe ne bouge pas, puisqu'il ne dépend pas du fichier. Synthèse : Plan: 1 to add, 0 to change, 1 to destroy. Le nouveau fichier contiendra le même nom de bucket.

3. Le plan périmé (niveau 100). Ouvrez deux terminaux dans le même répertoire. Dans le premier, enregistrez un plan qui modifie terraform_data.version_app. Dans le second, appliquez une autre modification. Revenez au premier et appliquez le plan enregistré. Que se passe-t-il, et pourquoi est-ce une bonne nouvelle ?

Solution

apply refuse avec Error: Saved plan is stale : l'état a changé depuis le calcul du plan. Sans ce refus, vous exécuteriez des actions calculées sur une infrastructure qui n'existe plus sous cette forme. Il faut recalculer un plan, le relire, puis l'appliquer. Avec un état partagé (leçon 7), c'est exactement ce qui protège deux personnes qui travaillent en même temps.

4. Préparer le dépôt (niveau 100). Initialisez un dépôt Git dans ~/signalements-iac, ajoutez un .gitignore adapté, et vérifiez avec git status que seuls main.tf, .terraform.lock.hcl et .gitignore sont proposés à l'ajout après un apply.

Solution

Partez du modèle Terraform.gitignore et ajoutez *.tfplan, et inventaire.txt puisqu'il est produit par Terraform. Après git init et un apply, git status --short doit lister .gitignore, .terraform.lock.hcl et main.tf comme non suivis, et rien d'autre : ni .terraform/, ni terraform.tfstate, ni sa sauvegarde, ni le plan.

Récapitulatif

  • Le cycle tient en cinq commandes : init (fournisseurs, stockage de l'état), validate, plan (ce qui va se passer), apply (exécuter après yes), destroy. OpenTofu : les mêmes, avec tofu.
  • init choisit la version la plus récente qui respecte la contrainte (~> 3.7), vérifie la signature, et l'inscrit avec ses empreintes dans .terraform.lock.hcl, qui se versionne. .terraform/ ne se versionne pas.
  • Un plan se lit par ses symboles : + créer, ~ modifier en place, -/+ remplacer (# forces replacement), - détruire, et d'abord par sa ligne Plan:.
  • Deux apply successifs sans changement ne font rien : c'est l'idempotence. Avant chaque plan, Terraform rafraîchit l'état en relisant la réalité.
  • plan -out fige un plan ; apply plan l'exécute tel quel, et refuse un plan périmé.
  • L'état et les plans contiennent des secrets : jamais dans Git, et des droits stricts.

Pour aller plus loin

  • La page d'installation de Terraform et celle d'OpenTofu, pour les autres systèmes et les archives binaires.
  • terraform -help et terraform <commande> -help : l'aide intégrée fait foi pour la version installée.
  • La leçon suivante, qui détaille le langage HCL : blocs, types, expressions et fonctions.
Voir ma constellation →

Sources