Atelier fondamental 2 — Terraform : modifier, remplacer, détruire

Pratique guidée16 min
Durée
20 min
Module
1/7
Prérequis
l'atelier fondamental 1 terminé, sans destroy : le dossier travail/atelier-1 contient main.tf, bonjour.txt et terraform.tfstate, et terraform state list y répond local_file.bonjour
Tu vas construire
rien de neuf ; tu vas faire vivre ce que tu as créé : le casser à la main, le voir réparé, changer son contenu, le voir remplacé, puis tout détruire jusqu'à un state vide
Livrable
la sortie du terraform plan de l'étape 5 (celle qui contient must be replaced) et celle de terraform state list de l'étape 9 (vide)

Comment lire cette page. Dix étapes, une commande à la fois. Pour chacune : la commande, la sortie exacte de la machine du cours (Terraform 1.12.2), et ce qu'il faut regarder. Tape toi-même chaque commande. Les blocs « Pour bien comprendre » sont facultatifs. Les commandes terraform … sont identiques partout ; quand une commande dépend du système, les deux versions sont données, l'une sous l'autre. Si le dossier de l'atelier 1 n'existe plus ou si state list ne répond pas local_file.bonjour, refais l'atelier fondamental 1 d'abord (kit : https://github.com/hrhouma2/aiopsatlas-terraform-labo-fr).

Objectif

L'atelier 1 t'a fait créer une ressource et lire le state. Mais une infrastructure ne se crée pas une fois pour toutes : elle se modifie, quelqu'un la casse par mégarde, et un jour on la démonte. Ce sont les trois gestes de cet atelier. Tu vas d'abord supprimer bonjour.txt à la main, comme un collègue qui « nettoie » un dossier, et voir Terraform s'en apercevoir puis le recréer : c'est la dérive de la leçon 01. Tu vas ensuite changer une ligne de main.tf et découvrir que, pour le provider local, changer le contenu d'un fichier ne le modifie pas : il le remplace, avec un symbole que tu dois savoir reconnaître avant de dire yes. Enfin tu vas tout détruire, et vérifier que le relevé est vide et que le kit compte zéro.

Le vocabulaire en une image

Toujours l'architecte. Une dérive (drift), c'est un ouvrier qui a déplacé un mur sans le dire : le bâtiment ne correspond plus au relevé ; l'architecte s'en aperçoit quand il va voir sur place (Refreshing state...). Modifier sur place (~), c'est repeindre un mur : le mur reste le même. Remplacer (-/+), c'est démolir le mur et en reconstruire un au même endroit : il a un nouveau numéro dans le relevé. Détruire (-), c'est démolir sans reconstruire. Le devis dit toujours lequel des trois il prévoit ; toi, tu le lis avant de signer.

Symbole dans le planPhrase dans le planCe que Terraform faitDans cet atelier
+will be createdcrée un objet nouveauétape 3 : le fichier supprimé à la main
~will be updated in-placechange un attribut, l'objet restejamais pour content d'un local_file
-/+must be replaceddétruit l'objet, puis en crée un autreétape 5 : content change
-will be destroyedsupprime l'objetétape 8 : destroy

Où taper

Le même terminal que pour l'atelier 1, dans le dossier travail/atelier-1 du kit lab-terraform. Et VS Code pour modifier main.tf. Les commandes terraform … sont identiques partout ; les commandes pour supprimer, lire ou lister un fichier sont données dans les deux versions.

Étape 1 — Retrouver le point de départ

Windows (PowerShell), depuis la racine du kit :

powershell
cd travail\atelier-1

Linux, macOS, WSL 2, Git Bash :

bash
cd travail/atelier-1

Puis :

text
terraform state list
text
local_file.bonjour

Ce que la commande demande : « Qu'est-ce que tu gères dans ce dossier ? »

À regarder : une ligne, local_file.bonjour. C'est l'état où l'atelier 1 t'a laissé. Si tu lis No state file was found! ou rien du tout, le dossier n'est pas le bon ou la ressource a été détruite : refais l'atelier 1 avant de continuer.

Étape 2 — Casser à la main

Windows (PowerShell) :

powershell
Remove-Item .\bonjour.txt
Get-ChildItem

Linux, macOS, WSL 2, Git Bash :

bash
rm bonjour.txt
ls

Ce que les commandes demandent : « Supprime bonjour.txt, puis montre-moi le dossier. »

À regarder : bonjour.txt n'est plus là ; main.tf et terraform.tfstate y sont toujours. Tu viens de faire ce qu'un collègue fait dans la console AWS quand il supprime un bucket « qui ne servait à rien » : le réel a changé, mais ni le code ni le relevé ne le savent encore.

Étape 3 — Voir la dérive

text
terraform plan
text
local_file.bonjour: Refreshing state... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]

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.bonjour will be created
  + resource "local_file" "bonjour" {
      + content              = "Bonjour Terraform"
      + content_base64sha256 = (known after apply)

      + filename             = "./bonjour.txt"
      + id                   = (known after apply)
    }

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

Ce que la commande demande : « Compare le code, le relevé et le réel, et dis-moi ce qui manque. »

À regarder : Refreshing state... [id=fd9aee55…] : Terraform est allé voir le fichier que son relevé mentionne, et ne l'a pas trouvé. Résultat : il le retire de son relevé et le devis dit will be created, Plan: 1 to add. Le même plan qu'à l'atelier 1, étape 5, alors que tu n'as pas touché au code. C'est ça, une dérive : le réel a bougé sans passer par Terraform, et Terraform l'a détecté au moment du plan, pas avant.

Pour bien comprendre
  • Terraform ne surveille rien en continu. Entre deux commandes, il ne sait pas ce qui se passe. C'est le Refreshing state... du plan (et de l'apply) qui relit le réel. Un fichier supprimé il y a trois jours n'est découvert qu'au prochain plan.
  • Pourquoi + et pas -/+ ? L'objet n'existe plus du tout : il n'y a rien à détruire, seulement à créer. -/+ (étape 5) est pour un objet qui existe encore mais qu'il faut refaire.
  • Dans le cloud, c'est la situation la plus fréquente : quelqu'un a modifié une règle de pare-feu dans la console, et le plan suivant propose de la remettre comme le code le dit. Le plan te le montre ; à toi de décider si c'est le code ou la console qui avait raison.

Étape 4 — Réparer

text
terraform apply
text
local_file.bonjour: Refreshing state... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]



Plan: 1 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

local_file.bonjour: Creating...
local_file.bonjour: Creation complete after 0s [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]

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

Ce que la commande demande : « Recalcule le devis, attends mon yes, puis fais-le. »

À regarder : bonjour.txt est de retour, et son id est le même qu'avant (fd9aee55…) : l'identifiant d'un local_file est l'empreinte de son contenu, et le contenu n'a pas changé. Vérifie avec Get-Content .\bonjour.txt ou cat bonjour.txt : Bonjour Terraform. Un script n'aurait pas su qu'il fallait réparer ; Terraform l'a su parce qu'il a un relevé et un code à comparer.

Étape 5 — Changer le contenu, lire le devis de remplacement

Dans VS Code, modifie la ligne content de main.tf pour obtenir :

hcl
resource "local_file" "bonjour" {
  filename = "${path.module}/bonjour.txt"
  content  = "Bonjour Terraform, deuxième version"
}

Enregistre, puis :

text
terraform plan
text
local_file.bonjour: Refreshing state... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]

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.bonjour must be replaced
-/+ resource "local_file" "bonjour" {
      ~ content              = "Bonjour Terraform" -> "Bonjour Terraform, deuxième version" # forces replacement
      ~ content_base64sha256 = "SvXkP5Iqo1rJKHgTVJnjanxfsNVGhb+r52mzUqZJYyg=" -> (known after apply)
      ~ content_base64sha512 = "r3+PevYNJRFsHMz4H4U1DK+lg89nhO9SLJZMAqqtzPKiIv3ilrmbx5sYh2JCW9o5EF0NXanYmuxMvI2QN4Z1Ag==" -> (known after apply)
      ~ content_md5          = "ff3a967c227a58691a3d34a931d3eeb5" -> (known after apply)
      ~ content_sha1         = "fd9aee5556589b4d797e6d49f8ec894e29e57713" -> (known after apply)
      ~ content_sha256       = "4af5e43f922aa35ac92878135499e36a7c5fb0d54685bfabe769b352a6496328" -> (known after apply)
      ~ content_sha512       = "af7f8f7af60d25116c1cccf81f85350cafa583cf6784ef522c964c02aaadccf2a222fde296b99bc79b188762425bda39105d0d5da9d89aec4cbc8d9037867502" -> (known after apply)
      ~ id                   = "fd9aee5556589b4d797e6d49f8ec894e29e57713" -> (known after apply)
        # (3 unchanged attributes hidden)
    }

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

Ce que la commande demande : la même qu'à l'étape 3. Mais cette fois c'est le code qui a changé, pas le réel.

À regarder, ligne par ligne. C'est le plan le plus important du module :

LigneCe qu'elle dit
-/+ destroy and then create replacementLa légende : le symbole utilisé est -/+, « détruire puis créer un remplaçant ».
# local_file.bonjour must be replacedPas will be updated in-place : la ressource doit être remplacée.
-/+ resource "local_file" "bonjour" {Le bloc entier porte le -/+.
~ content = "Bonjour Terraform" -> "Bonjour Terraform, deuxième version" # forces replacementLe ~ dit que la valeur change, de l'ancienne (->) à la nouvelle. Le commentaire # forces replacement dit que c'est cet attribut qui oblige à remplacer.
~ content_md5 = "ff3a967c…" -> (known after apply)Les empreintes changeront ; Terraform ne connaît pas encore les nouvelles.
~ id = "fd9aee55…" -> (known after apply)L'identifiant va changer. Ce sera un autre objet.
# (3 unchanged attributes hidden)filename, directory_permission, file_permission ne bougent pas ; Terraform les cache pour la lisibilité.
Plan: 1 to add, 0 to change, 1 to destroy.Une destruction et une création, zéro modification.

C'est ta première réponse-livrable : garde cette sortie.

La différence essentielle entre ~ et -/+. Le ~ devant content dit quoi change. Le -/+ devant resource dit comment Terraform va s'y prendre : démolir, puis reconstruire. Pour un fichier texte, tu ne verras pas la différence. Pour une base de données, -/+ veut dire que les données disparaissent avec l'ancien objet. C'est le provider qui décide, attribut par attribut ; le provider local a décidé que content force le remplacement. Toi, tu lis les trois indices (must be replaced, # forces replacement, 1 to destroy) avant de taper yes.

Pour bien comprendre
  • Pourquoi le provider local remplace au lieu de modifier ? Parce qu'il ne sait pas « corriger » un fichier existant : il sait l'écrire ou l'effacer. L'id d'un local_file est d'ailleurs l'empreinte SHA-1 de son contenu ; changer le contenu change l'identifiant, et un objet dont l'identifiant change est, pour Terraform, un autre objet. Sur la machine du cours, file_permission force aussi le remplacement. D'autres providers font autrement : changer une étiquette (tag) sur une ressource AWS est un ~, changer son nom est souvent un -/+.
  • Où est-ce écrit ? Dans la documentation de chaque ressource sur le Registry, certains arguments sont marqués forces new resource (ou ForceNew). Avant un apply en production, c'est ce qu'on va lire.
  • Peut-on forcer un remplacement volontairement ? Oui : terraform apply -replace=local_file.bonjour remplace la ressource même sans changement de code. Utile quand un objet est corrompu. Tu le verras au projet 08.

Étape 6 — Appliquer le remplacement

text
terraform apply
text
local_file.bonjour: Refreshing state... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]



Plan: 1 to add, 0 to change, 1 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

local_file.bonjour: Destroying... [id=fd9aee5556589b4d797e6d49f8ec894e29e57713]
local_file.bonjour: Destruction complete after 0s
local_file.bonjour: Creating...
local_file.bonjour: Creation complete after 0s [id=bd2b5f8ccbb2cbc2b9bea872211549635a76b092]

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

Ce que la commande demande : la même qu'à l'étape 4.

À regarder : quatre lignes d'action au lieu de deux, dans l'ordre du symbole -/+ : Destroying... puis Destruction complete (l'ancien, fd9aee55…), puis Creating... puis Creation complete (le nouveau, bd2b5f8c…). Et la dernière ligne : Apply complete! Resources: 1 added, 0 changed, 1 destroyed. Pas 1 changed : rien n'a été modifié, un objet a été détruit et un autre créé.

Étape 7 — Vérifier le fichier et le nouvel identifiant

Windows (PowerShell) :

powershell
Get-Content .\bonjour.txt

Linux, macOS, WSL 2, Git Bash :

bash
cat bonjour.txt
text
Bonjour Terraform, deuxième version

Puis :

text
terraform state show local_file.bonjour
text
# local_file.bonjour:
resource "local_file" "bonjour" {
    content              = "Bonjour Terraform, deuxième version"
    content_base64sha256 = "hg9L75JxH+5SDk+ZOADZinGONkirydetS0TFjN3uZ3o="
    content_base64sha512 = "EjT8mRVQU2JQ+Em/rnbRKYfe7E2UqIWw8yQMMtaatimH5XCKg4ZomW4PI5yO5jhujdSrU04vX17HE2kaqEVSkQ=="
    content_md5          = "9a1a1355b48e28ad727200e450b851e1"
    content_sha1         = "bd2b5f8ccbb2cbc2b9bea872211549635a76b092"
    content_sha256       = "860f4bef92711fee520e4f993800d98a718e3648abc9d7ad4b44c58cddee677a"
    content_sha512       = "1234fc991550536250f849bfae76d12987deec4d94a885b0f3240c32d69ab62987e5708a838668996e0f239c8ee6386e8dd4ab534e2f5f5ec713691aa8455291"
    directory_permission = "0777"
    file_permission      = "0777"
    filename             = "./bonjour.txt"
    id                   = "bd2b5f8ccbb2cbc2b9bea872211549635a76b092"
}

Ce que les commandes demandent : « Montre-moi le fichier réel, puis ce que le relevé en dit. »

À regarder : le fichier contient la deuxième version. Dans le relevé, content est la nouvelle valeur, toutes les empreintes ont changé, et l'id est bd2b5f8c…, celui affiché par Creation complete à l'étape 6. Compare avec le state show de l'atelier 1, étape 9 : même adresse local_file.bonjour, mais un autre objet. Le code, le relevé et le réel sont de nouveau d'accord : un terraform plan maintenant dirait No changes.

Étape 8 — Tout détruire

text
terraform destroy
text
local_file.bonjour: Refreshing state... [id=bd2b5f8ccbb2cbc2b9bea872211549635a76b092]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # local_file.bonjour will be destroyed
  - resource "local_file" "bonjour" {
      - content              = "Bonjour Terraform, deuxième version" -> null
      - content_base64sha256 = "hg9L75JxH+5SDk+ZOADZinGONkirydetS0TFjN3uZ3o=" -> null

      - filename             = "./bonjour.txt" -> null
      - id                   = "bd2b5f8ccbb2cbc2b9bea872211549635a76b092" -> null
    }

Plan: 0 to add, 0 to change, 1 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

local_file.bonjour: Destroying... [id=bd2b5f8ccbb2cbc2b9bea872211549635a76b092]
local_file.bonjour: Destruction complete after 0s

Destroy complete! Resources: 1 destroyed.

Ce que la commande demande : « Détruis tout ce que tu gères ici ; montre-moi le devis et attends mon yes. »

À regarder : le troisième symbole, - seul, et chaque attribut qui passe à -> null (« plus rien »). La question est plus grave qu'à l'apply : Do you really want to destroy all resources? et There is no undo. Tape yes. La phrase à attendre : Destroy complete! Resources: 1 destroyed. Le fichier a disparu du dossier.

Étape 9 — Lire un relevé vide

text
terraform state list
text

Puis, pour voir ce que répond Terraform quand on lui demande une ressource qu'il ne gère plus :

text
terraform state show local_file.bonjour
text
No instance found for the given address!

This command requires that the address references one specific instance.
To view the available instances, use "terraform state list". Please modify
the address to reference a specific instance.

Ce que les commandes demandent : « Liste ce que tu gères », puis « montre-moi local_file.bonjour ».

À regarder : state list ne répond rien : pas une ligne, pas d'erreur, un relevé vide. C'est différent du No state file was found! de l'atelier 1 avant apply : ici le fichier terraform.tfstate existe, mais sa liste de ressources est vide. Et state show répond No instance found for the given address! : l'adresse est correcte, mais plus rien n'y correspond, et Terraform te renvoie vers state list. C'est ta deuxième réponse-livrable : la sortie vide de terraform state list.

Étape 10 — Lister le dossier après

Windows (PowerShell) :

powershell
Get-ChildItem -Force

Linux, macOS, WSL 2, Git Bash :

bash
ls -la
text
total 17
drwxr-xr-x 1 rehou 197609    0 Sep 15 14:57 .
drwxr-xr-x 1 rehou 197609    0 Sep 15 14:57 ..
drwxr-xr-x 1 rehou 197609    0 Sep 15 14:57 .terraform
-rw-r--r-- 1 rehou 197609 1228 Sep 15 14:57 .terraform.lock.hcl
-rw-r--r-- 1 rehou 197609  130 Sep 15 14:57 main.tf
-rw-r--r-- 1 rehou 197609  181 Sep 15 14:57 terraform.tfstate
-rw-r--r-- 1 rehou 197609 1653 Sep 15 14:57 terraform.tfstate.backup

(Listing ls -la de la machine du cours ; Get-ChildItem -Force affiche les mêmes cinq noms : .terraform, .terraform.lock.hcl, main.tf, terraform.tfstate, terraform.tfstate.backup.)

Ce que la commande demande : « Montre-moi tout le dossier, fichiers cachés compris. »

À regarder : bonjour.txt n'est plus là. main.tf est intact : détruire les ressources ne touche jamais au code. terraform.tfstate est encore là mais minuscule (181 octets), et un terraform.tfstate.backup est apparu : la copie du relevé d'avant le destroy, que Terraform garde par prudence. .terraform/ et .terraform.lock.hcl restent aussi : si tu relances apply, pas besoin de refaire init.

Ouvre le state pour le voir vide, sans le modifier (Get-Content .\terraform.tfstate ou cat terraform.tfstate) :

json
{
  "version": 4,
  "terraform_version": "1.12.2",
  "serial": 6,
  "lineage": "69fb219e-a88c-a97f-5aed-a33e7c34ff69",
  "outputs": {},
  "resources": [],
  "check_results": null
}

"resources": [] : rien. Le "serial" compte les écritures dans le state (six ici : deux apply à l'atelier 1, puis réparation, remplacement et destruction) ; le "lineage" est l'identifiant de ce state, différent chez toi.

Vérification finale

Remonte à la racine du kit et lance le compteur :

Windows (PowerShell) :

powershell
cd ..\..
.\labo.ps1 etat

Linux, macOS, WSL 2, Git Bash :

bash
cd ../..
./labo.sh etat
text
travail atelier-1 : aucune ressource
Ressources encore gérées : 0 (0 attendu à la fin d'une séance).

Réponse attendue : aucune ressource et Ressources encore gérées : 0. C'est la fin de séance propre que chaque projet du cours exigera, quand les ressources coûteront de l'argent.

  • Tu as supprimé bonjour.txt à la main et vu plan proposer will be created sans que le code ait changé : une dérive, détectée au Refreshing state....
  • apply a recréé le fichier avec le même id.
  • Tu as changé content et lu les trois indices du remplacement : must be replaced, # forces replacement, Plan: 1 to add, 0 to change, 1 to destroy.
  • apply a affiché Destroying... puis Creating..., et Resources: 1 added, 0 changed, 1 destroyed. ; l'id a changé.
  • destroy a demandé yes avec There is no undo. et répondu Destroy complete! Resources: 1 destroyed.
  • state list ne rend rien ; state show rend No instance found for the given address! ; terraform.tfstate contient "resources": [].
  • etat rend Ressources encore gérées : 0.
  • Tu as gardé le plan de l'étape 5 et la sortie vide de l'étape 9 comme livrables.

Si ça coince

Afficher les cas fréquents
  • À l'étape 1, state list répond No state file was found! → Tu n'es pas dans travail/atelier-1, ou l'atelier 1 n'a pas été fait jusqu'à l'apply. Vérifie le dossier (Get-ChildItem -Force ou ls -la doit montrer terraform.tfstate), sinon refais l'atelier 1.
  • À l'étape 1, state list ne répond rien → La ressource a déjà été détruite (un destroy à la fin de l'atelier 1). Relance terraform apply, yes, puis reprends à l'étape 2.
  • À l'étape 3, le plan dit No changes. → Le fichier n'a pas été supprimé (faute de frappe dans Remove-Item ou rm, ou mauvais dossier). Vérifie avec Get-ChildItem ou ls, supprime-le, relance plan.
  • À l'étape 5, le plan dit No changes.main.tf n'a pas été enregistré après la modification (VS Code : point blanc dans l'onglet). Ctrl+S puis relance.
  • À l'étape 5, Error: Unterminated template string → Le guillemet fermant de la nouvelle valeur content a sauté pendant l'édition. Remets-le.
  • À l'étape 5, la ligne marquée # forces replacement n'est pas content → Tu as modifié un autre argument en même temps (par exemple file_permission = "0644"). Pour un local_file, celui-là aussi force le remplacement (vérifié : ~ file_permission = "0777" -> "0644" # forces replacement). Remets le fichier comme à l'étape 5, seule la ligne content doit changer.
  • Apply cancelled. → Autre chose que yes a été tapé. Relance, tape yes en entier.
  • À l'étape 8, destroy dit No changes. No objects need to be destroyed. puis Destroy complete! Resources: 0 destroyed. → Il n'y a déjà plus rien dans le state (Either you have not created any objects yet or the existing objects were already deleted outside of Terraform.). Passe à l'étape 9.
  • Après destroy, tu crois que le state n'a pas été nettoyé parce que terraform.tfstate existe encore → C'est normal. Ouvre-le : "resources": []. Le fichier reste, vide, avec un .backup à côté.
  • etat répond Ressources encore gérées : 1 → Le destroy a été annulé ou fait dans un autre dossier. Reviens dans travail/atelier-1, terraform destroy, yes, relance etat.