Aller au contenu

L'état

200 Pratiquer ⏱ 1 h 15 terraformopentofuiac

À la fin, vous saurez

  • Expliquer les quatre rôles de l'état et pourquoi les étiquettes ne suffisent pas à le remplacer
  • Lire un fichier d'état : version, numéro de série, lignée, ressources, instances, attributs sensibles
  • Utiliser terraform state list, show, mv et rm, et prévoir l'effet de chacune sur le plan suivant
  • Détecter une dérive avec un plan en mode rafraîchissement seul, et l'enregistrer en connaissance de cause
  • Montrer qu'un secret marqué sensible est en clair dans l'état, et protéger le fichier en conséquence
  • Décrire ce que font le verrouillage, les sauvegardes et le chiffrement côté client d'OpenTofu

Prérequis

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

Pourquoi

À la première leçon pratique, terraform apply a laissé dans le répertoire un fichier terraform.tfstate que vous n'avez pas écrit. Il est facile de le prendre pour un fichier technique sans importance, à ignorer comme un cache. C'est l'erreur la plus coûteuse que l'on puisse faire avec Terraform.

Imaginez la configuration de Signalements appliquée en production : deux instances, une base, un répartiteur. Le fichier d'état est perdu (supprimé par un git clean, resté sur le portable d'une personne partie en congé). Le lendemain, quelqu'un lance terraform apply. Terraform ne sait plus que ces ressources existent : il les crée une seconde fois. Au mieux, le plan échoue parce qu'un nom est déjà pris ; au pire, il réussit, et vous avez deux bases, deux factures, et une application qui pointe vers la nouvelle, vide.

Inversement, deux personnes qui appliquent en même temps avec deux copies de l'état peuvent chacune détruire ce que l'autre vient de créer. Et le fichier contient, en clair, des mots de passe que la leçon précédente croyait avoir cachés.

Comprendre l'état, c'est comprendre comment Terraform « sait » ce qui existe. Cette leçon l'ouvre, le manipule, et montre ses dangers ; la suivante le met à l'abri.

Les concepts

Les quatre rôles de l'état

L'état (state) est la mémoire de Terraform : un document qui relie chaque ressource de la configuration à un objet réel, avec ce que Terraform en sait. La documentation de Terraform lui donne quatre rôles.

  1. La correspondance avec le monde réel. La configuration dit scaleway_vpc_private_network.signalements ; l'état dit que cette ressource correspond à l'objet fr-par/11111111-... chez Scaleway. Sans cette table, Terraform ne pourrait pas savoir quel réseau privé, parmi tous ceux du projet, est le sien.
  2. Les métadonnées. L'état garde notamment les dépendances entre ressources au moment de leur création. Quand vous retirez une ressource de la configuration, Terraform doit la détruire dans le bon ordre, alors que la configuration ne dit plus rien d'elle : c'est l'état qui s'en souvient.
  3. Les performances. L'état garde les attributs de chaque objet. Sur une grande infrastructure, interroger toutes les API avant chaque plan serait lent et buterait sur les limites de débit ; l'état sert de cache, et l'option -refresh=false permet même de s'y fier sans rien relire.
  4. La synchronisation. Une équipe doit partager le même état, et ne pas l'écrire à deux en même temps : c'est l'objet du stockage distant et du verrouillage (leçon 7).

Pourquoi pas des étiquettes ? On pourrait imaginer que Terraform retrouve ses ressources en posant une étiquette geree-par=terraform sur chacune. La documentation raconte que les premiers prototypes l'ont essayé, et y ont renoncé : toutes les ressources n'acceptent pas d'étiquettes, et toutes les plateformes n'en proposent pas. Les étiquettes restent utiles pour les humains et la facture ; elles ne remplacent pas l'état.

Le fichier, ligne par ligne

Avec le stockage par défaut (le backend local), l'état est un fichier JSON nommé terraform.tfstate, à la racine du répertoire de travail. Ses champs de premier niveau :

ChampContenu
versionLa version du format du fichier (4 depuis Terraform 0.12)
terraform_versionLa version de Terraform qui l'a écrit en dernier
serialUn compteur, incrémenté à chaque écriture de l'état
lineageUn identifiant unique, fixé à la création de l'état, et qui ne change plus
outputsLes valeurs des sorties
resourcesLes ressources gérées et les sources de données lues

Le couple lineage et serial protège contre les mélanges : Terraform refuse d'écraser un état par un autre de lignée différente (ce n'est pas le même état), ou par un état de même lignée mais de série plus ancienne (ce serait revenir en arrière).

Chaque élément de resources porte un mode (managed pour une ressource, data pour une source de données), un type, un name, le provider qui la gère, et une liste d'instances : une seule pour une ressource simple, plusieurs avec count ou for_each (leçon 9). Chaque instance contient ses attributes (arguments et attributs calculés, tous, avec leurs valeurs) et une liste sensitive_attributes qui dit lesquels masquer à l'affichage.

Le rafraîchissement et la dérive

Avant de planifier, Terraform rafraîchit l'état : pour chaque ressource, il demande au fournisseur de relire l'objet réel, et met à jour sa copie en mémoire. C'est ainsi qu'il remarque qu'un objet a été modifié ou supprimé hors de Terraform : la dérive (voir dérive de configuration).

Un plan normal fait deux choses à la fois : il constate la dérive, puis propose de ramener les objets à ce que dit la configuration. Le mode rafraîchissement seul (terraform plan -refresh-only) ne fait que la première : il montre ce qui a changé dehors, et propose seulement d'enregistrer ces changements dans l'état, sans toucher aux objets. C'est l'outil pour la situation que décrit la documentation : un objet modifié volontairement hors du circuit habituel, pendant un incident par exemple, dont il faut maintenant tenir compte.

Les commandes state

Le sous-ensemble terraform state lit et modifie l'état sans passer par un plan :

CommandeEffet
terraform state listListe les adresses des ressources de l'état
terraform state show <adresse>Affiche les attributs d'une ressource (valeurs sensibles masquées)
terraform state mv <source> <destination>Change l'adresse sous laquelle un objet est suivi
terraform state rm <adresse>Oublie un objet : il n'est plus géré, mais n'est pas détruit
terraform state pullAffiche l'état brut, quel que soit le stockage
terraform show -jsonAffiche l'état sous une forme JSON stable, destinée aux outils

state mv et state rm sont des opérations impératives : elles modifient l'état tout de suite, sans plan à relire, et chacune crée une sauvegarde horodatée du fichier précédent. Depuis Terraform 1.1 et 1.7, les blocs moved et removed font la même chose de façon déclarative, dans le code, relue en demande de fusion et appliquée par un plan : c'est la méthode à préférer, et le sujet de la leçon 11. Les commandes restent utiles pour réparer, et pour comprendre.

Les secrets dans l'état

L'état contient tous les attributs de chaque ressource, y compris ceux que la configuration a marqués sensibles. La documentation de Terraform le dit sans détour : l'état et les plans contiennent des informations qui peuvent inclure des valeurs sensibles, comme des mots de passe initiaux de bases de données ou des jetons d'API, et le stockage local les garde dans des fichiers en clair. La seule défense dans le code est de ne pas y mettre le secret : valeurs éphémères et arguments en écriture seule (leçon 5). La défense autour du code est de protéger l'état comme un secret : stockage chiffré, accès limité, journalisé (leçon 7).

OpenTofu va plus loin depuis sa version 1.7 : il sait chiffrer lui-même l'état et les fichiers de plan, avant de les écrire, quel que soit le stockage. La clé vient d'une phrase de passe (dérivée par PBKDF2), d'un service de gestion de clés (AWS KMS, Google Cloud KMS, Azure Key Vault, OpenBao) ou d'une commande externe ; l'algorithme est AES-GCM. Terraform ne propose pas ce chiffrement côté client : il dépend du chiffrement du stockage distant.

En pratique

Les manipulations se font dans un répertoire d'essai, avec random et local. Toutes les sorties ont été produites avec Terraform 1.16.1.

Créer un état

resource "random_password" "base" {
  length  = 24
  special = false
}

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

resource "local_file" "config" {
  filename = "${path.module}/signalements.env"
  content  = "BUCKET=signalements-pj-${random_pet.bucket.id}\n"
}

Après terraform init et terraform apply, le répertoire contient le fichier d'état :

$ ls -l terraform.tfstate
-rw-rw-r-- 1 camille camille 3394 oct.   5 12:53 terraform.tfstate
$ terraform state list
local_file.config
random_password.base
random_pet.bucket

Notez les droits : rw-rw-r--, lisible par tous les utilisateurs de la machine. Terraform crée le fichier avec les droits que permet votre masque (umask 002 sur un poste Ubuntu, comme le rappelle la leçon sur les permissions). Gardez-le en tête pour la suite.

Lire une ressource

$ terraform state show -no-color random_password.base
# random_password.base:
resource "random_password" "base" {
    bcrypt_hash = (sensitive value)
    id          = "none"
    length      = 24
    lower       = true
    min_lower   = 0
    min_numeric = 0
    min_special = 0
    min_upper   = 0
    number      = true
    numeric     = true
    result      = (sensitive value)
    special     = false
    upper       = true
}

La commande affiche tous les attributs, arguments par défaut compris, et masque result et bcrypt_hash : le fournisseur les déclare sensibles.

Ouvrir le fichier

$ jq '{version, terraform_version, serial, lineage, nb: (.resources|length)}' terraform.tfstate
{
  "version": 4,
  "terraform_version": "1.16.1",
  "serial": 4,
  "lineage": "407d4eb1-0932-48c5-14fe-5012308fa36f",
  "nb": 3
}

Le numéro de série vaut déjà 4 après un seul apply : Terraform écrit l'état au fil de l'application, à mesure que les ressources sont créées, pour ne rien perdre si l'opération est interrompue. Regardons maintenant le mot de passe :

$ jq '.resources[] | select(.type=="random_password")
      | {type, name, provider, attrs: (.instances[0].attributes | {result, length}),
         sensitive_attributes: .instances[0].sensitive_attributes}' terraform.tfstate
{
  "type": "random_password",
  "name": "base",
  "provider": "provider[\"registry.terraform.io/hashicorp/random\"]",
  "attrs": {
    "result": "yFZzVcLhqeN0p3gFjedzyF4o",
    "length": 24
  },
  "sensitive_attributes": [
    [
      {
        "type": "get_attr",
        "value": "bcrypt_hash"
      }
    ],
    [
      {
        "type": "get_attr",
        "value": "result"
      }
    ]
  ]
}

Le mot de passe est là, en clair (il a été tiré pour cette démonstration et ne sert à rien d'autre). sensitive_attributes dit seulement à Terraform de le masquer à l'affichage. La sortie JSON destinée aux outils le montre tout autant :

$ terraform show -json | jq -r '.values.root_module.resources[]
      | select(.type=="random_password") | .values.result'
yFZzVcLhqeN0p3gFjedzyF4o

Toute personne, tout outil, tout job de CI qui peut lire l'état peut lire ce mot de passe.

Constater une dérive

Supprimons à la main le fichier signalements.env, comme quelqu'un supprimerait une ressource dans la console, puis demandons un plan en rafraîchissement seul :

$ rm signalements.env
$ terraform plan -refresh-only -no-color
random_pet.bucket: Refreshing state... [id=organic-eagle]
random_password.base: Refreshing state... [id=none]
local_file.config: Refreshing state... [id=5563e55f929b8ebc41d39907b7c39810d1e75d08]

Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the
last "terraform apply" which may have affected this plan:

  # local_file.config has been deleted
  - resource "local_file" "config" {
      - content              = <<-EOT
            BUCKET=signalements-pj-organic-eagle
        EOT -> null
      ...
      - filename             = "./signalements.env" -> null
      - id                   = "5563e55f929b8ebc41d39907b7c39810d1e75d08" -> null
    }


This is a refresh-only plan, so Terraform will not take any actions to undo
these. If you were expecting these changes then you can apply this plan to
record the updated values in the Terraform state without changing any remote
objects.

Les lignes Refreshing state... montrent la relecture de chaque objet. Le plan constate la suppression et ne propose aucune action, seulement d'enregistrer le constat. Un plan normal, lui, proposerait de recréer le fichier :

$ terraform plan -no-color | grep -E '^\s+#|Plan:'
  # local_file.config will be created
Plan: 1 to add, 0 to change, 0 to destroy.

Pour qu'un script sache s'il y a dérive, l'option -detailed-exitcode fait sortir plan avec le code 0 s'il n'y a rien à faire, 1 en cas d'erreur, 2 s'il y a des changements :

$ terraform plan -detailed-exitcode >/dev/null; echo $?
2

C'est la base d'une détection de dérive planifiée, que la leçon 11 met en place.

Si la suppression était voulue, terraform apply -refresh-only l'enregistre : la ressource quitte l'état, et le plan suivant proposera de la recréer, puisque la configuration la décrit toujours. Pour qu'elle ne revienne pas, il faut aussi la retirer du code. Ici, recréons-la par un terraform apply normal.

Renommer dans l'état, sans renommer dans le code

Le nom local bucket est mal choisi ; on veut suffixe_bucket. Que se passe-t-il si l'on renomme dans l'état seulement ?

$ terraform state mv -no-color random_pet.bucket random_pet.suffixe_bucket
Move "random_pet.bucket" to "random_pet.suffixe_bucket"
Successfully moved 1 object(s).
$ ls -a
.  ..  main.tf  signalements.env  .terraform  .terraform.lock.hcl  terraform.tfstate  terraform.tfstate.1791197602.backup
$ terraform plan -no-color | grep -E '^\s+#|Plan:'
  # local_file.config must be replaced
  # random_pet.bucket will be created
  # random_pet.suffixe_bucket will be destroyed
  # (because random_pet.suffixe_bucket is not in configuration)
Plan: 2 to add, 0 to change, 2 to destroy.

L'état et le code ne parlent plus de la même chose : le code décrit random_pet.bucket, que l'état ne connaît plus, donc à créer ; l'état connaît random_pet.suffixe_bucket, que le code ne décrit pas, donc à détruire. Avec un vrai bucket, c'est sa destruction, données comprises, et sa recréation sous un autre nom. state mv a laissé une sauvegarde horodatée, terraform.tfstate.1791197602.backup.

Renommons maintenant aussi dans le code (resource "random_pet" "suffixe_bucket" et la référence dans local_file.config) :

$ terraform plan -no-color | tail -3
Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.

Code et état concordent de nouveau, rien n'est détruit. La leçon à retenir : un changement d'adresse doit être fait des deux côtés à la fois. Le bloc moved (leçon 11) fait exactement cela, dans le code, en un seul changement relu.

Oublier une ressource

$ terraform state rm -no-color local_file.config
Removed local_file.config
Successfully removed 1 resource instance(s).
$ terraform plan -no-color | grep -E '^\s+#|Plan:'
  # local_file.config will be created
Plan: 1 to add, 0 to change, 0 to destroy.

Le fichier signalements.env existe toujours sur le disque : state rm ne détruit rien, il fait oublier. Mais la configuration le décrit encore, donc Terraform propose de le créer. Pour un fichier local, la création écrase l'existant ; pour une ressource cloud au nom unique, elle échouerait, et pour une ressource sans nom unique (une instance), elle en créerait une seconde. state rm n'a de sens que suivi d'un retrait du code, ou d'un import ailleurs (leçon 11).

Après un apply, le répertoire contient les sauvegardes laissées par chaque opération :

$ ls | grep backup
terraform.tfstate.1791197602.backup
terraform.tfstate.1791197609.backup
terraform.tfstate.backup

Les deux premières viennent de state mv et state rm ; terraform.tfstate.backup est l'état précédant la dernière écriture, que le stockage local garde à chaque mise à jour. Toutes contiennent le mot de passe en clair.

Deux opérations en même temps

Le stockage local verrouille le fichier pendant une opération. Lançons un apply qui dure vingt secondes (une ressource time_sleep), et, pendant ce temps, un plan dans le même répertoire :

$ terraform apply -auto-approve &
$ terraform plan -no-color
Error: Error acquiring the state lock

Error message: resource temporarily unavailable
Lock Info:
  ID:        108481bd-bea4-11bb-30ac-b5bcf4874e0b
  Path:      terraform.tfstate
  Operation: OperationTypeApply
  Who:       camille@sig-poste
  Version:   1.16.1
  Created:   2026-10-05 10:53:41.644712531 +0000 UTC
  Info:


Terraform acquires a state lock to protect the state from being written
by multiple users at the same time. Please resolve the issue above and try
again. For most commands, you can disable locking with the "-lock=false"
flag, but this is not recommended.

Le second processus est refusé, avec l'identité et l'opération de celui qui tient le verrou. Mais ce verrou est un verrou de fichier local : il protège deux commandes lancées sur la même machine, dans le même répertoire. Deux personnes qui travaillent chacune sur leur copie du dépôt ont chacune leur fichier d'état, et aucun verrou commun. C'est pourquoi une équipe ne peut pas travailler avec un état local : la leçon 7 le place dans un bucket partagé, avec un verrou partagé.

Sous le capot

Le cycle d'un plan. Terraform charge l'état précédent, le rafraîchit (une lecture par ressource, auprès de son fournisseur), puis compare, pour chaque ressource, la configuration à l'état rafraîchi. Le fournisseur décide pour chaque différence si elle se corrige sur place (~ update in-place) ou impose de recréer l'objet (-/+ must be replaced) : c'est une propriété de chaque argument dans son schéma. Le plan est la liste de ces décisions. À l'application, chaque ressource terminée est écrite dans l'état, d'où le numéro de série qui monte plusieurs fois.

Le verrou local. Le stockage local pose un verrou du système sur le fichier d'état (sous Linux, un verrou d'enregistrement sur le fichier ouvert), d'où le message resource temporarily unavailable, qui est le texte de l'erreur système EAGAIN. Le verrou disparaît avec le processus, même s'il est tué. Un verrou distant (leçon 7) n'a pas cette propriété : un processus tué peut le laisser en place, et il faut alors le lever avec terraform force-unlock, en s'assurant d'abord que plus personne ne travaille.

L'état n'est pas la vérité. La vérité est l'infrastructure réelle ; l'état est la dernière image que Terraform en a prise, et le code est l'intention. Le plan est la confrontation des trois. Une modification à la main dans la console crée un écart entre la réalité et l'état ; une modification du code non appliquée crée un écart entre l'intention et l'état. Le travail quotidien consiste à garder les trois alignés, et c'est exactement ce que fait un outil GitOps pour Kubernetes, en boucle.

Le chiffrement d'OpenTofu. Il se configure dans le bloc terraform, par un key_provider, une method (aes_gcm) et un bloc state (et plan). Un bloc fallback permet de changer de clé : OpenTofu essaie la nouvelle méthode en lecture, puis l'ancienne. L'option enforced = true refuse d'écrire un état non chiffré, pour qu'un oubli de configuration ne repasse pas en clair. La documentation signale une limite d'AES-GCM, la « saturation de clé » au-delà d'un très grand nombre de chiffrements avec la même clé, qui se traite par la rotation.

Pièges courants

Versionner l'état dans Git. C'est tentant pour « partager » : on publie alors les secrets à tous les clones, et deux branches portent deux états qui divergent. L'état se met dans .gitignore (*.tfstate, *.tfstate.*) et dans un stockage distant.

Modifier le fichier à la main. Un serial oublié, une virgule de trop, et l'état est refusé ou, pire, accepté mais faux. Si une correction est indispensable : terraform state pull > copie.json, modification, incrément de serial, terraform state push copie.json, en gardant la copie d'origine. Dans presque tous les cas, une commande state, un bloc moved, removed ou import fait mieux.

state rm en croyant détruire. La ressource continue d'exister et de coûter, sans plus être gérée par personne : c'est une ressource orpheline, que l'inventaire du cours cloud retrouve des mois plus tard.

state mv sans changer le code, ou l'inverse. Le plan suivant propose de détruire et recréer. Lisez toujours le plan après une opération sur l'état, et ne l'appliquez que s'il dit « No changes ».

-lock=false pour débloquer. Si le verrou est tenu, c'est que quelqu'un travaille, ou qu'un processus a été tué. Contourner le verrou, c'est accepter que deux écritures se croisent.

Oublier les sauvegardes. Les fichiers *.backup contiennent les mêmes secrets que l'état. Ils doivent suivre les mêmes règles : jamais dans Git, jamais copiés n'importe où.

Confondre -refresh-only et -refresh=false. Le premier ne fait que rafraîchir ; le second ne rafraîchit pas et planifie à partir de l'état tel quel, plus vite mais en aveugle.

Sécurité

  • L'état est un secret. Il contient les mots de passe générés, les clés créées, les adresses internes, parfois des certificats. Traitez-le comme le fichier config.yaml de la CLI : accès limité, jamais dans Git, jamais dans un artefact public de CI.
  • Le droit de lire l'état est un droit d'administration. Qui lit l'état lit les secrets ; qui écrit l'état peut faire détruire des ressources au prochain apply. La leçon 7 donne au stockage de l'état des droits distincts de ceux des personnes.
  • Sortez les secrets de l'état quand c'est possible : valeurs éphémères et arguments en écriture seule (leçon 5), ou création du secret hors de Terraform, dans un gestionnaire de secrets, Terraform ne manipulant que sa référence.
  • Chiffrez au repos : chiffrement du stockage distant pour Terraform, chiffrement côté client pour OpenTofu, qui protège même d'une personne qui aurait accès au bucket.
  • Les plans enregistrés (terraform plan -out=plan.bin) contiennent eux aussi les valeurs : un fichier de plan transmis d'un job de CI à un autre est un artefact sensible, à durée de vie courte.

En production

  • Jamais d'état local pour une infrastructure partagée. Un stockage distant, verrouillé, versionné et chiffré, avec un état par environnement : c'est la leçon 7.
  • Un état par périmètre. Un état unique pour tout le système rend chaque plan lent, chaque erreur large, et chaque droit d'accès trop grand. Brikman, dans Terraform: Up & Running, recommande d'isoler les environnements, puis les composants (réseau, données, applications) : le rayon d'impact d'une erreur se limite à un état. Le cours avancé y revient.
  • Les opérations sur l'état se relisent. Une commande state mv ou rm en production est une intervention : on l'annonce, on la fait à deux, on garde la sauvegarde, et l'on vérifie par un plan qui ne propose rien. Mieux, on la remplace par un bloc moved ou removed qui passe en demande de fusion.
  • La détection de dérive est planifiée. Un job nocturne qui lance terraform plan -detailed-exitcode en lecture seule et alerte sur le code 2 révèle les modifications faites à la main (leçon 11).
  • Une sauvegarde de l'état n'est pas une sauvegarde des données. Restaurer un état ancien ne restaure aucune base ni aucun bucket ; au contraire, il ferait proposer à Terraform de recréer ou de modifier ce qui a changé depuis.

Exercices

1. Lire le plan après une opération (niveau 100). Pour chacune des opérations suivantes, sans changer le code, prévoyez ce que dira le plan suivant : (a) terraform state rm random_pet.suffixe_bucket ; (b) suppression à la main du fichier créé par local_file.config, puis terraform apply -refresh-only -auto-approve ; (c) terraform state mv local_file.config local_file.configuration.

Solution

(a) Création d'un nouveau random_pet (nouveau nom tiré au hasard), et remplacement de local_file.config, dont le contenu dépend du nom. (b) Le rafraîchissement enregistre la disparition et retire la ressource de l'état ; le plan propose donc de la créer (1 to add). (c) Destruction de local_file.configuration, inconnu du code, et création de local_file.config, inconnu de l'état : 1 to add, 1 to destroy.

2. Trouver les secrets (niveau 200). Écrivez une commande jq qui liste, pour un fichier d'état, l'adresse de chaque ressource et le nom de chacun de ses attributs sensibles. Appliquez-la à votre état d'essai.

Solution
$ jq -r '.resources[] as $r | $r.instances[]
         | .sensitive_attributes[]?
         | "\($r.type).\($r.name) : \(map(.value) | join("."))"' terraform.tfstate
local_file.config : sensitive_content
random_password.base : bcrypt_hash
random_password.base : result

La première ligne surprend : le fournisseur local garde un attribut sensitive_content, même vide, dans chaque local_file.

La liste dit où sont les secrets, pas qu'ils sont protégés : leurs valeurs sont dans le même fichier. C'est une bonne base pour un contrôle en CI qui alerte quand une nouvelle ressource apporte un attribut sensible.

3. Un verrou bloqué (niveau 200). Une collègue vous signale : « terraform plan dit Error acquiring the state lock, et personne n'est en train de travailler. » L'état est local, sur un poste partagé. Que vérifiez-vous, dans quel ordre, et que faites-vous ?

Solution

Le message donne l'identité (Who), l'opération et l'heure de création du verrou. Sur un état local, le verrou est tenu par un processus vivant : on cherche ce processus (pgrep -a terraform, ps -ef), sur cette machine. S'il existe, on le laisse finir, ou l'on contacte la personne. Un verrou local disparaît avec son processus : s'il n'y a pas de processus, le message vient d'ailleurs (un autre répertoire, un état distant). On ne contourne pas avec -lock=false. La vraie correction est de ne pas partager un état local entre personnes.

4. Chiffrer l'état avec OpenTofu (niveau 200). Lisez la page State and plan encryption de la documentation d'OpenTofu, puis écrivez le bloc encryption qui chiffre l'état et les plans par une phrase de passe lue dans une variable d'environnement, et permet de lire un ancien état non chiffré pendant la migration. Que faut-il changer une fois la migration faite ?

Solution

D'après la documentation d'OpenTofu (la syntaxe est à vérifier sur la version utilisée) :

variable "phrase_etat" {
  type      = string
  sensitive = true
}

terraform {
  encryption {
    key_provider "pbkdf2" "phrase" {
      passphrase = var.phrase_etat
    }
    method "aes_gcm" "principal" {
      keys = key_provider.pbkdf2.phrase
    }
    method "unencrypted" "migration" {}

    state {
      method = method.aes_gcm.principal
      fallback {
        method = method.unencrypted.migration
      }
    }
    plan {
      method = method.aes_gcm.principal
    }
  }
}

La phrase arrive par TF_VAR_phrase_etat (au moins 16 caractères). Le fallback vers unencrypted sert à relire l'ancien état en clair : la documentation précise qu'OpenTofu refuse sinon de lire un état non chiffré, par protection contre un état manipulé. Après le premier tofu apply, qui réécrit l'état chiffré, la procédure de la documentation fait retirer le fallback et le bloc unencrypted, puis ajouter enforced = true dans state et dans plan. Perdre la phrase, c'est perdre l'état : elle se range dans un gestionnaire de secrets, et l'on fait une copie de l'état en clair, à l'abri, avant de commencer. Ce bloc n'est pas compris par Terraform.

Récapitulatif

  • L'état relie chaque ressource du code à un objet réel ; il garde aussi les dépendances, un cache des attributs, et sert à synchroniser une équipe. Les étiquettes ne le remplacent pas.
  • Le fichier terraform.tfstate est du JSON : version, serial (monte à chaque écriture), lineage (identité de l'état), resources avec leurs instances et tous leurs attributs.
  • Le rafraîchissement relit les objets ; plan -refresh-only montre la dérive sans rien corriger ; -detailed-exitcode la signale par le code 2.
  • state list et show lisent ; state mv et state rm modifient tout de suite, en laissant une sauvegarde ; un changement d'adresse se fait dans le code et dans l'état à la fois (blocs moved, leçon 11). state rm oublie, il ne détruit pas.
  • Les valeurs sensibles sont en clair dans l'état, ses sauvegardes et les plans enregistrés : l'état est un secret.
  • Le verrou local ne protège qu'une machine ; OpenTofu chiffre l'état côté client depuis la version 1.7.

Pour aller plus loin

  • Le chapitre 3 de Terraform: Up & Running, consacré à la gestion de l'état, et sa discussion de l'isolation par environnement et par composant.
  • La page Purpose of Terraform state de la documentation de Terraform, courte et éclairante.
  • La leçon suivante, qui place l'état dans un bucket Object Storage de Scaleway, avec un verrou partagé, un versionnement et un état par environnement.
Voir ma constellation →

Sources