Aller au contenu
Infrastructure as Code medium

Verrouiller le state Terraform : ce que le verrou bloque vraiment

40 min de lecture

logo terraform

Quand deux personnes lancent terraform apply en même temps sur le même projet, la seconde écrase le travail de la première. Le verrouillage du state l'empêche : Terraform pose un verrou avant toute opération susceptible d'écrire, et le libère à la fin. Ce guide montre comment observer un verrou réel, quelles commandes il rejette (le plan en fait partie, contrairement à une idée répandue), lesquelles il laisse passer, et comment l'activer sur un backend distant, où il n'est pas toujours automatique.

Tous les comportements mesurés de ce guide ont été exécutés sur Terraform v1.15.4, backend local, sorties et codes de retour compris. Les arguments du backend S3 proviennent de sa documentation officielle.

  • Observer un verrou réel et lire ses sept champs
  • le fichier de verrou est écrit, et pourquoi ce n'est pas toujours la racine
  • Ce que le verrou rejette et ce qu'il laisse passer, code de retour à l'appui
  • -lock-timeout : attendre le verrou au lieu de le désactiver
  • use_lockfile : activer le verrouillage sur S3, où il est opt-in

Le verrouillage (state locking) garantit qu'une seule opération à la fois peut écrire le state. Terraform pose un verrou au début de l'opération, le maintient pendant toute sa durée, et le libère à la fin. Une autre opération qui voudrait écrire pendant ce temps est rejetée, pas mise en attente.

Sans lui, voici ce qui arrive quand Alice et Bob travaillent sur le même projet :

ÉtapeAliceBobRésultat
1Lit le state (serial 5)
2Lit le state (serial 5)Même point de départ
3apply crée VM-ASerial 6
4apply crée VM-BÉcrase le serial 6 d'Alice
5VM-A disparaît du state

VM-A existe toujours sur l'hyperviseur mais plus dans le state : Terraform ne la gère plus, et personne ne la détruira. Le verrouillage supprime cette fenêtre en faisant échouer l'opération de Bob à l'étape 4.

Un verrou ne dure que le temps de l'opération. Sur une configuration triviale, l'apply se termine en une fraction de seconde et il n'y a rien à voir. Pour l'observer, il faut une ressource lente. La ressource terraform_data est intégrée à Terraform, donc aucun provider à télécharger :

# main.tf : ressource lente, pour rendre le verrou observable
terraform {
backend "local" {
path = "etat/projet.tfstate"
}
}
resource "terraform_data" "lent" {
input = "verrouillage"
provisioner "local-exec" {
command = "sleep 20"
}
}
  1. Dans un premier terminal, lancez l'apply

    Fenêtre de terminal
    terraform init
    terraform apply -auto-approve

    Le provisioner occupe l'apply pendant vingt secondes.

  2. Dans un second terminal, trouvez le fichier de verrou

    Ne devinez pas son nom, cherchez-le :

    Fenêtre de terminal
    find . -name '*.lock.info'
    ./etat/.projet.tfstate.lock.info
  3. Affichez son contenu

    Fenêtre de terminal
    cat etat/.projet.tfstate.lock.info | python3 -m json.tool
    {
    "ID": "11015c79-a539-8dad-096b-b96778355839",
    "Operation": "OperationTypeApply",
    "Info": "",
    "Who": "bob@master1",
    "Version": "1.15.4",
    "Created": "2026-07-27T19:51:22.084516333Z",
    "Path": "etat/projet.tfstate"
    }
  4. Une fois l'apply terminé, vérifiez que le verrou a disparu

    Fenêtre de terminal
    find . -name '*.lock.info' | wc -l
    0

Le cycle de vie est automatique : créé au début de l'opération, supprimé à la fin.

Le fichier de verrou suit le state, il n'a pas d'emplacement fixe. Son nom se dérive du chemin du state : le nom du fichier de state, préfixé d'un point, suffixé de .lock.info, dans le répertoire du state.

Avec le path par défaut (terraform.tfstate à la racine), cela donne .terraform.tfstate.lock.info à la racine du projet, d'où l'idée répandue que c'est son emplacement. Ce n'est vrai que dans ce cas. Avec path = "etat/projet.tfstate", le verrou est écrit en etat/.projet.tfstate.lock.info, comme le montre le find ci-dessus. Un script de supervision qui cherche .terraform.tfstate.lock.info à la racine ne trouvera jamais rien sur un projet dont le state est ailleurs.

Attention à ne pas confondre les deux chemins : le champ Path du verrou vaut etat/projet.tfstate, c'est-à-dire le state verrouillé, et non le fichier de verrou lui-même.

Le fichier de verrou est un objet JSON de sept champs. Ils répondent à trois questions de diagnostic : qui détient le verrou, pour quelle opération, depuis quand.

ChampDescriptionExemple mesuré
IDIdentifiant unique du verrou (UUID), sert de nonce à force-unlock11015c79-a539-8dad-096b-b96778355839
OperationOpération en coursOperationTypeApply
InfoMessage libre, vide en usage courant""
WhoUtilisateur et machine détenteursbob@master1
VersionVersion de la CLI qui a posé le verrou1.15.4
CreatedDate de création, en UTC2026-07-27T19:51:22Z
PathState verrouilléetat/projet.tfstate

Le champ Info est souvent oublié dans les transcriptions, mais il fait bien partie du verrou et apparaît dans le message d'erreur.

Ce que le verrou rejette, et ce qu'il laisse passer

Section intitulée « Ce que le verrou rejette, et ce qu'il laisse passer »

Toute commande susceptible d'écrire le state est rejetée, terraform plan compris. C'est le point le plus souvent faux dans les mémentos : le plan rafraîchit le state, donc il peut l'écrire, donc il prend un verrou. Voici les codes de retour relevés pendant qu'un apply tenait le verrou, sur Terraform 1.15.4 :

Commande lancée pendant le verrouCode de retourVerdict
terraform plan1rejetée
terraform apply1rejetée
terraform force-unlock <ID>1rejetée (backend local)
terraform plan -lock=false0passe, sans protection
terraform state list0passe
terraform show -json0passe
terraform output -json0passe
terraform state pull0passe
terraform validate0passe

Les lectures pures passent. C'est un point de diagnostic direct : si terraform state list échoue, le problème n'est pas le verrou, il faut chercher ailleurs. La commande terraform state list n'accepte d'ailleurs même pas l'option -lock, elle répond flag provided but not defined: -lock, ce qui confirme qu'elle ne verrouille pas.

Voici le message complet renvoyé par un plan lancé pendant un apply :

Error: Error acquiring the state lock
Error message: resource temporarily unavailable
Lock Info:
ID: 11015c79-a539-8dad-096b-b96778355839
Path: etat/projet.tfstate
Operation: OperationTypeApply
Who: bob@master1
Version: 1.15.4
Created: 2026-07-27 19:51:22.084516333 +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.

Par défaut, une commande refusée par un verrou échoue immédiatement : -lock-timeout vaut 0s. C'est le réglage qui produit le plus de faux problèmes en intégration continue, où deux exécutions qui se suivent de près se rejettent l'une l'autre pour quelques secondes de recouvrement.

La bonne réponse n'est pas de désactiver le verrouillage, c'est de patienter :

Fenêtre de terminal
terraform apply -lock-timeout=5m

Terraform réessaie alors d'acquérir le verrou jusqu'à l'expiration du délai. Mesuré sur 1.15.4 : un plan -lock-timeout=10s lancé pendant un apply attend bien dix secondes avant de sortir en erreur, au lieu d'échouer aussitôt.

Voici le point qui coûte le plus cher en incident, et il est contre-intuitif. La documentation du backend local dit qu'il verrouille le state « using system APIs » : le verrou réel est un verrou système posé par le processus, que le noyau libère à la mort de ce processus, quelle qu'en soit la cause. Le fichier .lock.info n'est que la carte de visite de ce verrou : il dit qui détient quoi, il ne détient rien lui-même.

Conséquence, vérifiée sur 1.15.4 : après un kill -9 en plein apply, le fichier reste bien sur le disque, mais il ne bloque rien. Le plan suivant s'exécute normalement et Terraform supprime le fichier lui-même :

Fenêtre de terminal
# Le fichier est bien resté après l'arrêt brutal
ls etat/.projet.tfstate.lock.info
etat/.projet.tfstate.lock.info
Fenêtre de terminal
# Le plan suivant aboutit malgré lui, et fait le ménage
terraform plan -input=false > /dev/null 2>&1; echo $?
ls etat/.projet.tfstate.lock.info
0
ls: cannot access 'etat/.projet.tfstate.lock.info': No such file or directory

Un fichier .lock.info fabriqué à la main ne bloque pas davantage : le plan passe et l'efface. Sur un backend local, un « verrou bloqué depuis des heures » n'existe donc pas, et le rm que l'on voit souvent recommandé est un rituel sans effet. Si une commande est réellement rejetée, c'est qu'un processus Terraform tourne encore : cherchez-le avec ps, ne supprimez pas le fichier.

force-unlock : à quoi il sert, et où il ne sert pas

Section intitulée « force-unlock : à quoi il sert, et où il ne sert pas »

terraform force-unlock libère un verrou côté serveur, sur un backend distant dont le détenteur a disparu (agent de CI tué, réseau coupé). Il prend l'identifiant du verrou en argument :

Fenêtre de terminal
terraform force-unlock 11015c79-a539-8dad-096b-b96778355839

Cet identifiant joue le rôle de nonce : il garantit que vous levez bien le verrou que vous croyez, et pas celui qu'un collègue vient de poser entre-temps. C'est la raison pour laquelle on ne recopie jamais un ID trouvé ailleurs que dans le message d'erreur du moment. La commande demande confirmation, et l'option -force s'en passe, ce qui la rend utilisable en automation :

Fenêtre de terminal
terraform force-unlock -force 11015c79-a539-8dad-096b-b96778355839

Sur un backend local, la commande échoue dans tous les cas, avec un code de retour 1, que le verrou soit tenu ou libre et que l'ID soit juste ou faux. Le message diffère selon la forme employée :

FormeMessage
terraform force-unlock <ID>Local state cannot be unlocked by another process
terraform force-unlock -force <ID>Failed to unlock state: LocalState not locked

Ce n'est pas un défaut à contourner, c'est cohérent avec la section précédente : il n'y a rien à déverrouiller à distance sur un verrou que le noyau a déjà libéré.

Sur un backend distant, le verrouillage n'est pas automatique : il dépend du backend et de sa configuration. Le backend S3 est explicite, « State locking is an opt-in feature of the S3 backend », et son argument use_lockfile « Defaults to false ». Autrement dit, une configuration S3 qui ne le pose pas n'a aucun verrou, quelle que soit la version de Terraform :

terraform {
backend "s3" {
bucket = "mon-org-tfstate"
key = "production/reseau.tfstate"
region = "eu-west-3"
use_lockfile = true # sans cette ligne, aucun verrou
}
}

Le verrou est alors un objet <key>.tflock écrit à côté du state dans le bucket, ici production/reseau.tfstate.tflock.

Trois points opérationnels que l'on découvre souvent trop tard :

  • Les permissions IAM du verrou ne sont pas celles du state. use_lockfile exige s3:GetObject, s3:PutObject et s3:DeleteObject sur l'objet .tflock, alors que s3:DeleteObject n'est pas nécessaire sur le state lui-même. Un verrou S3 qui ne se libère jamais vient presque toujours de cette permission manquante.
  • Le versioning du bucket est le dernier filet. La documentation S3 le recommande en tête de page : « It is highly recommended that you enable Bucket Versioning on the S3 bucket to allow for state recovery in the case of accidental deletions and human error. »
  • DynamoDB est déprécié. L'ancien mécanisme par table (dynamodb_table) est marqué « DynamoDB-based locking is deprecated and will be removed in a future minor version ». Pour migrer sans fenêtre sans verrou, les deux mécanismes peuvent être configurés simultanément le temps de la bascule, puis on retire dynamodb_table.

Chaque backend implémente le verrouillage avec les primitives de son stockage, et tous n'acceptent pas force-unlock. Le tableau ci-dessous résume ce que dit la documentation de chacun.

BackendMécanismeforce-unlock
localVerrou système sur le fichier, plus un .lock.info descriptifNon
s3Objet <key>.tflock, opt-in via use_lockfileOui
gcsObjet de verrou dans le bucketOui
azurermLease sur le blobOui
consulSession ConsulOui
pgAdvisory lock PostgreSQLNon
kubernetesRessource LeaseOui
ociObjet de verrou via l'en-tête If-None-Match: *Oui
httpRequêtes LOCK et UNLOCK, optionnel via lock_addressOui

Deux lignes méritent une explication. Le backend pg ne supporte pas force-unlock : ses verrous sont des advisory locks PostgreSQL, « these database-native locks will automatically unlock when the session is aborted or the connection fails ». Le diagnostic passe par la vue pg_locks, pas par une commande Terraform. Le backend http, lui, ne verrouille que si lock_address est renseigné : comme S3, il faut le demander.

Le réflexe utile est toujours le même : lire le code de retour et les champs du verrou avant d'agir, plutôt que de supprimer un fichier.

SymptômeCause probableSolution
Error acquiring the state lockUne opération est réellement en coursAttendre, ou relancer avec -lock-timeout=5m. Les champs Who et Created disent qui et depuis quand
Le message revient alors que personne n'appliqueUn processus Terraform survit (shell détaché, job CI zombie)pgrep -a terraform et terminer le processus. Sur backend local, supprimer le .lock.info ne changerait rien
Deux jobs CI se rejettent en rafale-lock-timeout vaut 0s par défautPoser -lock-timeout=5m dans le pipeline, jamais -lock=false
Local state cannot be unlocked by another processforce-unlock sur un backend localNormal : il n'y a rien à déverrouiller à distance. Chercher le processus vivant
Verrou S3 jamais libérés3:DeleteObject absent sur <key>.tflockAjouter la permission sur l'objet de verrou, puis terraform force-unlock <ID>
terraform state list échoue pendant un applyCe n'est pas le verrou : les lectures passentChercher la vraie cause (state absent, droits, backend injoignable)

Le lab verrouillage du state fait relever ces mesures par l'apprenant plutôt que les lui donner : il déplace le state hors de la racine, rend l'apply assez lent pour observer un verrou, puis consigne les codes de retour de chaque geste. Les tests refont l'expérience de leur côté et comparent, y compris l'arrêt brutal et son fichier résiduel. Il se joue hors ligne, sans compte cloud.

  • Le verrouillage garantit une seule écriture du state à la fois ; il est posé au début de l'opération et libéré à la fin.
  • terraform plan est verrouillé lui aussi : il rafraîchit, donc il peut écrire. Les lectures pures (state list, show -json, output) passent.
  • Le fichier de verrou suit le state : avec path = "etat/projet.tfstate" il s'écrit en etat/.projet.tfstate.lock.info, et son champ Path désigne le state.
  • Le verrou porte sept champs, Info compris ; ID sert de nonce à force-unlock.
  • En CI, la bonne réponse au rejet est -lock-timeout=5m, jamais -lock=false.
  • Sur backend local, un .lock.info resté après un crash ne bloque rien : le plan suivant aboutit et efface le fichier. Le rm manuel est inutile.
  • Sur S3, le verrouillage est opt-in : sans use_lockfile = true, aucun verrou. DynamoDB est déprécié, et le verrou exige s3:DeleteObject.
  • Les backends local et pg n'acceptent pas force-unlock.

Les questions ci-dessous portent sur ce qui bloque le plus souvent : la commande qu'on ne croyait pas verrouillée, le fichier qu'on supprime pour rien, et le verrou S3 qu'on croit actif. Chaque réponse donne la vérification correspondante.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn