Qu'est-ce que l'état Terraform et pourquoi c'est critique sur AWS
L'état Terraform est le fichier (ou l'ensemble de fichiers) qui enregistre la photographie exacte de votre infrastructure AWS telle que Terraform l'a déployée. Chaque ressource créée, chaque configuration appliquée, chaque attribut assigné est stocké là. Sans cet état, Terraform ne saurait pas quelles ressources vous avez déjà créées, comment les modifier ou lesquelles supprimer lors d'un changement de code.
Cet état est tellement critique sur AWS que sa perte ou sa corruption signifie que Terraform ne reconnaît plus les ressources qu'il a lui-même créées. Vous vous retrouvez avec des instances EC2, des bases de données RDS, des buckets S3 orphelins dans votre compte AWS, facturés mais invisibles à Terraform. Inversement, si vous supprimez le fichier d'état par erreur, Terraform peut décider de recréer les mêmes ressources en pensant qu'elles n'existent pas, doublant votre infrastructure et vos coûts.
Dans les équipes qui franchissent le stade artisanal sur AWS (scale-ups et ETI dépassant quelques milliers d'euros par mois), l'état cesse rapidement d'être un problème local stocké sur un ordinateur portable. Dès que plusieurs ingénieurs travaillent sur la même infrastructure, l'état devient un artefact partagé qui doit être verrouillé (pour éviter que deux déploiements simultanés ne se corrompent mutuellement), sauvegardé (pour survivre à une perte de machine), et versionné (pour remonter le temps en cas d'erreur). C'est pourquoi les équipes AWS sérieuses stockent l'état Terraform sur S3 avec un backend distant plutôt que en local.
Configurer le backend distant S3 pour sécuriser l'état
Par défaut, Terraform stocke l'état dans un fichier local nommé terraform.tfstate. Ce fichier contient la totalité de votre infrastructure en texte brut, y compris les mots de passe, les clés API, et les détails des bases de données. Le garder en local fonctionne pour un projet personnel ou un prototype, mais dès que vous travaillez en équipe sur une infrastructure de production, c'est un risque majeur.
La solution standard sur AWS est de stocker l'état sur un backend distant, typiquement un bucket S3 associé à un système de verrouillage via DynamoDB. Voici comment mettre cela en place. D'abord, créez un bucket S3 dédié à l'état Terraform, par exemple terraform-state-monentreprise-prod. Activez le versioning sur ce bucket pour pouvoir revenir à une version antérieure de l'état en cas de corruption ou d'erreur. Ensuite, activez le chiffrement par défaut (Server-Side Encryption avec une clé KMS managée par AWS ou une clé personnalisée) pour que personne ne puisse lire le contenu brut du fichier d'état stocké sur S3.
Créez aussi une table DynamoDB nommée terraform-locks avec une clé primaire appelée LockID (type chaîne). Cette table permet à Terraform de verrouiller l'état pendant qu'un déploiement est en cours, empêchant un second ingénieur de modifier simultanément le même état et de le corrompre.
Dans votre code Terraform, déclarez le backend ainsi. Créez un fichier backend.tf contenant un bloc terraform avec un bloc backend s3. Spécifiez bucket (le nom exact du bucket S3), key (par exemple prod/terraform.tfstate pour tracer l'environnement), region (la région AWS où vous avez créé le bucket), et dynamodb_table (le nom exact de la table DynamoDB pour le verrouillage). Une fois ce fichier ajouté et committé dans votre dépôt git, initialisez Terraform avec terraform init. Terraform détecte la nouvelle configuration du backend et migre automatiquement votre état local vers S3.
Pour une maîtrise complète, restreignez l'accès au bucket S3 et à la table DynamoDB via des politiques IAM : seuls les rôles AWS utilisés par vos CI/CD pipelines et vos développeurs autorisés doivent pouvoir lire, écrire et verrouiller l'état. Bloquez aussi tout accès public au bucket S3 en utilisant une politique de blocage du public et des ACLs privées.
Détecter et comprendre la dérive d'infrastructure (drift)
Malgré tous vos efforts pour gérer l'infrastructure via Terraform, il existe un risque permanent sur AWS : que quelqu'un modifie directement une ressource via la console AWS, l'API AWS, un autre script, ou même un script de patch de sécurité automatisé. Une instance EC2 peut avoir ses groupes de sécurité changés manuellement, une RDS peut passer d'une taille de 100 Go à 200 Go, une politique IAM peut être modifiée pour contourner une restriction. Ces changements ne sont pas reflétés dans votre code Terraform, créant une divergence entre l'état Terraform (qui croit toujours que l'instance a son ancienne configuration) et la réalité AWS.
Terraform appelle cela une dérive (drift). Elle est dangereuse parce qu'elle crée une illusion de contrôle : votre code Terraform semble intact et à jour, mais l'infrastructure réelle a dérivé. Si vous appliquez une modification ultérieure via Terraform, vous risquez d'écraser accidentellement le changement manuel, ou d'obtenir un comportement imprévisible.
Pour détecter une dérive, utilisez la commande terraform plan -refresh-only. Cette commande interroge chaque ressource AWS pour voir son état réel actuel, puis la compare avec ce que Terraform croit savoir (l'état stocké sur S3). Si une ressource a changé en dehors de Terraform, terraform plan vous montre la différence. Par exemple, si une instance EC2 avait trois groupes de sécurité dans l'état, mais en a actuellement cinq dans AWS (modifiés à la main), vous verrez une ligne comme security_groups[3] = sgr-new-id et security_groups[4] = sgr-another-id dans le plan.
Une fois la dérive détectée, vous avez plusieurs choix. Le plus simple est de mettre à jour votre code Terraform pour refléter la réalité : modifier le bloc ressource dans votre code pour inclure les groupes de sécurité supplémentaires, puis relancer terraform plan pour confirmer que le plan est maintenant vide. Alternativement, si le changement manuel était une erreur, vous pouvez utiliser terraform apply pour forcer l'infrastructure réelle à revenir à l'état que Terraform connaît. Attention : cette opération peut être destructrice (par exemple, supprimer les groupes ajoutés manuellement), donc à utiliser avec prudence et toujours après avoir vérifié le plan.
Dans les équipes qui automatisent réellement leur déploiement, la détection de dérive est souvent intégrée dans la pipeline CI/CD : un job schedulé chaque matin execute terraform plan -refresh-only et, s'il détecte une dérive, envoie une alerte à Slack ou crée une issue automatique dans le tracker. Cela garantit qu'aucune modification manuelle n'échappe à l'équipe.
Gérer l'état multi-environnement sans collision
Quand vous déployez une infrastructure sur plusieurs environnements (développement, staging, production), vous avez plusieurs fichiers d'état Terraform à gérer, un par environnement. La tentation naturelle est de créer plusieurs dossiers terraform-dev, terraform-staging, terraform-prod, chacun avec son propre fichier terraform.tfstate local. Cela marche tant que vous êtes seul, mais cela explose rapidement en équipe.
L'approche correcte est de stocker un seul code Terraform avec des variables qui diffèrent par environnement, et de gérer les backends séparés pour chaque environnement via une clé S3 différente. Reprenons l'exemple du fichier backend.tf : au lieu de coder en dur le bucket S3 et la clé, utilisez un fichier de configuration backend séparé par environnement. Créez backend-prod.tfvars contenant bucket = terraform-state-monentreprise et key = prod/terraform.tfstate. Créez backend-dev.tfvars avec key = dev/terraform.tfstate. Quand vous initialisez Terraform pour la production, lancez terraform init -backend-config=backend-prod.tfvars. Pour le développement, utilisez terraform init -backend-config=backend-dev.tfvars.
Cela garantit que deux ingénieurs travaillant simultanément sur dev et prod utilisent des fichiers d'état complètement différents, stockés à des emplacements différents dans S3 et verrouillés par des entrées DynamoDB différentes. Aucune collision possible. Ajoutez aussi des variables Terraform pour les ressources qui diffèrent entre environnements : taille des instances, nombre de répliques, etc. Un fichier terraform.tfvars-dev contient environment = dev, instance_type = t3.small, replica_count = 1. Un terraform.tfvars-prod contient environment = prod, instance_type = m5.large, replica_count = 3.
Pour éviter les erreurs manuelles (par exemple, initialiser avec le mauvais backend ou oublier de charger le bon fichier de variables), automatisez tout dans votre pipeline CI/CD. Votre job Terraform pour prod doit commencer par terraform init -backend-config=backend-prod.tfvars && terraform plan -var-file=terraform.tfvars-prod. Le pipeline garantit que vous ne pouvez jamais accidentellement créer une ressource de production dans l'état de développement.
Sauvegarder et restaurer l'état en cas d'erreur
Même avec les meilleures pratiques, des erreurs arrivent. Un ingénieur peut commettre une typo dans une variable Terraform qui supprime accidentellement une base de données en production, la détecte trop tard, et doit restaurer l'infrastructure à son état d'une heure avant. Ou une corruption d'état met le fichier en JSON invalide, et terraform plan refuse de s'exécuter. Sans une stratégie de sauvegarde, c'est la panique et le temps d'arrêt prolongé.
Grâce au versioning S3 que vous avez activé sur votre bucket de l'état, AWS garde automatiquement un historique complet de toutes les versions du fichier d'état. Chaque fois que Terraform écrit un nouvel état après un apply, S3 enregistre la version précédente. Si vous détectez une erreur quelques minutes plus tard, vous pouvez restaurer facilement.
Pour restaurer une version antérieure de l'état via la console AWS, allez sur le bucket S3, cliquez sur terraform.tfstate, et regardez l'onglet Versions. Vous verrez une liste timestampée de toutes les versions du fichier. Cliquez sur l'ID de version qui correspond au moment avant l'erreur, téléchargez ce fichier, vérifiez son contenu en JSON pour confirmer qu'il semble sain, puis re-uploadez-le en tant que la dernière version du fichier terraform.tfstate. Terraform récupérera automatiquement cette version restaurée au prochain terraform plan.
Pour une approche encore plus sûre et automatisée, créez un script en Python ou Bash qui, avant chaque terraform apply en production, effectue une sauvegarde explicite de l'état actuel en le copiant vers un dossier archivé, par exemple s3://terraform-state-monentreprise/backups/terraform.tfstate.2024-01-15T14h30m. Un point important : ne supprimez jamais les anciennes versions de votre bucket S3 (évitez un cycle de vie qui expirerait les versions après N jours). L'espace de stockage S3 pour un fichier d'état, même avec un an d'historique, coûte quelques euros par an. Ce coût de sauvegarde est négligeable face au risque de ne pas pouvoir revenir à un état sain.
Dans un contexte d'équipe, documentez aussi votre procédure de restauration d'état et testez-la une fois par an lors d'un exercice de disaster recovery. Trop d'équipes découvrent leur procédure de restauration au moment critique quand elle échoue. Un test préalable élimine cette surprise.
Automatiser la détection de dérive dans le pipeline CI/CD
Détecter la dérive manuellement chaque semaine est fragile : cela dépend de la vigilance humaine et de la mémorisation de faire la commande terraform plan -refresh-only. Dans les équipes opérant réellement une infrastructure AWS sérieuse, la détection de dérive est automatisée dans la pipeline CI/CD.
La pratique recommandée est de programmer un job CI/CD quotidien (ou toutes les 6 heures pour les environnements critiques) qui exécute terraform plan -refresh-only pour chaque environnement. Si le plan détecte une dérive (c'est-à-dire s'il y a une différence entre l'état Terraform et la réalité AWS), le job envoie une notification. Cela peut être un message sur Slack avec le détail des changements, un e-mail adressé à l'équipe SRE/DevOps, ou une issue automatiquement créée dans votre tracker (Jira, GitHub Issues, etc.).
Voici un exemple de job GitLab CI ou GitHub Actions. En GitLab CI, ajoutez un job nommé detect-drift qui s'exécute chaque matin via une planification : schedule : cron: '0 8 *'. Le job lance terraform init -backend-config=backend-prod.tfvars && terraform plan -refresh-only -no-color -out=tfplan. Si terraform plan détecte une différence, il retourne un code de sortie non-zéro. Vous pouvez attraper cette sortie et envoyer les détails du plan à Slack via curl vers un webhook Slack, ou archiver le fichier tfplan comme artefact du job pour inspection manuelle.
En GitHub Actions, créez un workflow YAML scheduled qui exécute un job detect-drift toutes les 6 heures. Après avoir lancé terraform plan -refresh-only, utilisez l'action actions/github-script pour créer une issue automatiquement si des changements sont détectés, ou publier un commentaire dans un issue ou discussion fixe.
Le bénéfice clé est la visibilité : vous détectez une dérive dès qu'elle apparaît, pas après qu'elle ait cassé quelque chose ou créé une incohérence cachée. L'équipe reçoit une alerte, discute de la cause (modification manuelle accidentelle, patch de sécurité appliqué par un tiers, ou changement volontaire qui n'a pas été committé), et décide : mettre à jour le code Terraform pour refléter la réalité, ou annuler la modification manuelle. Cette boucle de feedback continue garantit que le code Terraform reste le reflet fidèle de votre infrastructure AWS.