Compétence de standardisation Terraform
Cette compétence guide l'agent dans le refactoring de code Terraform brut, brownfield, importé (par exemple depuis Terraformer ou terraform import) ou hérité, en une architecture modulaire et prête pour la production, conforme aux standards HashiCorp.
Quand utiliser cette compétence
Activez cette compétence lorsque :
- L'utilisateur demande de "normaliser", "refactorer", "nettoyer" ou "modulariser" des configurations Terraform.
- Le code Terraform brut contient des chaînes codées en dur, des nombres magiques ou des identifiants cloud statiques (
ami-*,vpc-*,subnet-*,sg-*,arn:*). - Des configurations monolithiques dans un seul fichier (
main.tfavec tout dedans) doivent être séparées en une disposition multi-fichiers standard (provider.tf,variables.tf,locals.tf,outputs.tf,backend.tf). - Les ressources cloud ont besoin d'une taxonomie de balisage standardisée (par ex.
Environment,Project,ManagedBy = "terraform"). - La base de code manque de définitions de variables, de contraintes de type ou de blocs de description.
Flux de travail de standardisation
Suivez cette procédure en 5 phases lors de la standardisation de toute base de code Terraform :
┌────────────────────────────────────────────────────────┐
│ Phase 1 : Audit et identification des odeurs de code │
│ • Détecter les littéraux codés en dur, IDs, régions │
│ et CIDRs │
│ • Inspecter la structure des fichiers existants │
│ par rapport à la disposition standard │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ Phase 2 : Établir une architecture de fichiers │
│ standard │
│ • Extraire provider.tf, versions.tf et backend.tf │
│ • Créer variables.tf, locals.tf et outputs.tf │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ Phase 3 : Paramétriser les littéraux en variables │
│ et locales │
│ • Extraire les variables d'environnement/région │
│ avec types │
│ • Construire local.common_tags pour un balisage │
│ unifié des ressources │
│ • Créer terraform.tfvars.example │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ Phase 4 : Liaison par références dynamiques et │
│ renommage │
│ • Remplacer les IDs de ressources statiques par des │
│ références dynamiques │
│ • Remplacer les AMIs/sous-réseaux codés en dur par des │
│ data sources │
│ • Normaliser les étiquettes de ressources (snake_case, │
│ pas de types) │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ Phase 5 : Formater, valider et vérifier │
│ • Exécuter `terraform fmt -recursive` │
│ • Exécuter `terraform validate` │
│ • Vérifier avec `terraform plan` (zéro dérive) │
└────────────────────────────────────────────────────────┘
Instructions pas à pas
1. Phase 1 : Audit et découverte
Avant de faire des modifications, analysez le répertoire cible pour identifier :
- Disponibilité de l'interface CLI : Exécutez
terraform version(outofu versionpour OpenTofu) pour déterminer si les vérifications de formatage, de validation et de plan peuvent être exécutées dans la Phase 5. - Fichiers standard manquants : Vérifiez si
versions.tf,provider.tf,variables.tf,locals.tfetoutputs.tfexistent. - IDs et littéraux codés en dur :
- Identifiants cloud :
ami-[a-f0-9]+,vpc-[a-f0-9]+,subnet-[a-f0-9]+,sg-[a-f0-9]+,igw-[a-f0-9]+, liens complets GCP (https://www.googleapis.com/compute/...), IDsprojectstatiques - Blocs CIDR :
"10.0.0.0/16","0.0.0.0/0" - Types d'instances :
"t3.micro","m5.large","e2-standard-4" - Régions et numéros de compte statiques :
"us-east-1","us-central1","123456789012" - Identifiants bruts ou secrets non chiffrés
- Identifiants cloud :
- Référence de formatage : Si Terraform est installé, vérifiez si les fichiers sont conformes à
terraform fmt -check -recursive.
2. Phase 2 : Créer les fichiers centraux standard
Assurez-vous que la configuration cible est divisée en fichiers distincts, à responsabilité unique :
provider.tf / versions.tf
Centralisez les paramètres Terraform, la version minimale de l'interface CLI et les fournisseurs requis (adaptez pour AWS, GCP ou Azure) :
# versions.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
# Exemple GCP :
# google = {
# source = "hashicorp/google"
# version = "~> 5.5"
# }
}
}
# provider.tf (AWS)
provider "aws" {
region = var.aws_region
default_tags {
tags = local.common_tags
}
}
# provider.tf (alternative GCP)
# provider "google" {
# project = var.project_id
# region = var.region
# default_labels = local.common_labels
# }
Stratégie de balisage/étiquetage unique : Utilisez soit les
default_tags/default_labelsau niveau du fournisseur (en mettant uniquement les clés spécifiques aux ressources sur les ressources individuelles) soitmerge(local.common_tags, ...)/merge(local.common_labels, ...)sur chaque ressource — ne dupliquez jamais les deux.
backend.tf (si un état distant est utilisé)
Séparez la configuration de stockage d'état (s3 pour AWS, gcs pour GCP, azurerm pour Azure) :
terraform {
backend "s3" {
bucket = "myorg-terraform-state"
key = "services/app/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
# Exemple GCP :
# terraform {
# backend "gcs" {
# bucket = "myorg-terraform-state"
# prefix = "services/app"
# }
# }
3. Phase 3 : Convertir les chaînes simples en variables et locales
Extrayez tous les paramètres codés en dur dans variables.tf et locals.tf :
Règles pour variables.tf :
- Déclarez toujours
type: (string,number,bool,list(string),map(string)). - Incluez toujours
description: Expliquez le but et le format attendu. - Fournissez un
defaultuniquement quand c'est pertinent : Les options de configuration commeenvironmentouaws_regionpeuvent avoir des valeurs par défaut, mais les valeurs sensibles ne doivent pas en avoir. - Marquez les secrets comme
sensitive = true: Les variables de mot de passe, jeton et secret doivent être marquées comme sensibles.
Exemple de variables.tf :
variable "aws_region" {
type = string
description = "La région AWS où les ressources seront provisionnées."
default = "us-east-1"
}
variable "environment" {
type = string
description = "Nom de l'environnement de déploiement (ex. dev, staging, prod)."
default = "production"
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "L'environnement doit être l'un des suivants : dev, staging, production."
}
}
variable "instance_type" {
type = string
description = "Taille de l'instance EC2 pour le cluster applicatif."
default = "t3.medium"
}
variable "vpc_cidr" {
type = string
description = "Bloc CIDR de base pour le cloud privé virtuel."
default = "10.0.0.0/16"
}
Règles pour locals.tf :
Utilisez locals pour les valeurs dérivées en interne, les préfixes d'environnement et les balises communes :
locals {
name_prefix = "${var.project_name}-${var.environment}"
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "terraform"
}
}
terraform.tfvars.example
Créez toujours un fichier d'exemple de valeurs de variables pour que les coéquipiers sachent comment fournir les valeurs :
aws_region = "us-east-1"
environment = "production"
project_name = "checkout-service"
4. Phase 4 : Remplacer les valeurs statiques par des références dynamiques (garde-fous zéro-dérive)
Convertissez les identifiants cloud statiques et les références de ressources disjointes en expressions HCL natives tout en protégeant l'infrastructure en production contre un remplacement accidentel :
Garde-fou d'immuabilité et ForceNew (Critique pour brownfield / Terraformer)
- Distinguer les adresses Terraform des attributs cloud
name: Renommer une étiquette de ressource Terraform (tfer--prod-main-vpc→main) avec un blocmoved {}est sûr et sans interruption de service. Modifier un attribut cloudname(par ex.,google_compute_network.name,google_compute_subnetwork.name,google_compute_firewall.name,google_compute_instance.name, ouaws_s3_bucket.bucket) estForceNewet détruit et recrée l'infrastructure en production. Paramétrez les noms cloud existants dansvariables.tf/terraform.tfvarsau lieu de les réécrire en"${local.name_prefix}-...". - Épingler les images/AMIs importées sur les imports brownfield : Passer d'un
ami-*oudebian-12-bookworm-v20240515codé en dur à une recherchemost_recent = true/ familledataremplacera les VM existantes si une image plus récente existe. Épinglez le nom ou l'ID exact de l'image/AMI importé via une variable/un filtredatalorsque zéro-dérive est requis, et n'activezmost_recent = trueque lorsque l'utilisateur opte pour des mises à niveau d'image. - Garder le durcissement de sécurité séparé du refactoring zéro-dérive : Ne modifiez pas silencieusement les attributs comportementaux/de sécurité (par ex.,
http_tokens"optional"→"required",public_access_prevention"inherited"→"enforced", ou lesscopesOAuth des VM) lors d'une migration zéro-dérive. Considérez le durcissement comme une option explicite ou listez clairement chaque différence intentionnelle. - Préserver les clés de balises/étiquettes héritées lors de la migration : Si les ressources existantes utilisent
env = "prod", préservez les clés existantes (ou ajoutez les clés standardisées en plus) à moins que le remplacement des clés ne soit approuvé.
Liste de nettoyage spécifique à Terraformer
- Supprimez les attributs d'état calculés en lecture seule (
tags_allsur AWS ;effective_labelsetterraform_labelssur GCP) et les attributs vides/par défaut ("",[],{},false,0). - Supprimez les sous-blocs conflictuels vidés de l'état (par ex.,
network_interfaceprincipal EC2 lorsquesubnet_idest défini). - Remplacez les interpolations héritées
${data.terraform_remote_state.*.outputs.*}et les URL de liens complets par des références directes aux ressources (par ex.,google_compute_network.main.id) ou des recherchesdata. - Fusionnez les ressources dupliquées numérotées (
vm-01,vm-02) en une seule ressourcefor_eachassociée à des blocsmoved(to = google_compute_instance.api["01"]). - Extrayez les chaînes
user_data/startup-scriptéchappées en\nen ligne dans des fichiersscripts/chargés viafile()outemplatefile(). - Lors de la mutation des
scopesoumachine_typedes VM GCP, assurez-vous queallow_stopping_for_update = trueest définit si des mises à jour sur place sont prévues.
Anti-patron (Brut / Codé en dur) :
# MAUVAIS : IDs codés en dur et chaînes littérales
resource "aws_instance" "tfer--i-0123456789abcdef0" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
subnet_id = "subnet-01234567"
vpc_security_group_ids = [
"sg-0abcdef1234567890"
]
}
Patron standard (Refactoré) :
# 1. Préservation de l'état : Indiquer à Terraform que cette ressource a été renommée (empêche la destruction et la recréation)
moved {
from = aws_instance.tfer--i-0123456789abcdef0
to = aws_instance.web
}
# 2. Recherche dynamique pour l'image de base (épingler le nom exact pour le brownfield zéro-dérive, ou most_recent pour greenfield)
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = [var.ami_name_pattern]
}
}
# 3. Définition de ressource standardisée avec références dynamiques
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = aws_subnet.private[0].id
vpc_security_group_ids = [aws_security_group.web.id]
# Omettre common_tags ici si provider.tf définit déjà default_tags
tags = {
Name = var.instance_name
}
}
5. Phase 5 : Formatage, audit de dérive et validation
-
Liste de vérification d'audit de dérive avant plan (toujours effectuer, surtout si l'interface CLI
terraformn'est pas installée localement) :- Comparez le
namecloud,image/ami,location/zone,tags/labelset les paramètres de sécurité/réseau de chaque ressource par rapport à la source importée. - Vérifiez qu'aucun champ
ForceNewn'a été accidentellement modifié et que chaque ressourcetfer--*a un blocmoved {}correspondant. - Séparez et signalez toute différence intentionnelle (par ex., durcissement de sécurité) du refactoring zéro-dérive.
- Comparez le
-
Exécuter les vérifications de l'interface CLI Terraform (lorsqu'elle est installée) :
# 1. Formater le code selon l'indentation HCL canonique standard
terraform fmt -recursive
# 2. Initialiser les plugins de fournisseur (utiliser -backend=false lors de la validation hors ligne / sans état) et valider la syntaxe
terraform init -backend=false
terraform validate
# 3. Si vous travaillez dans un environnement actif avec état, assurez-vous que le plan ne produit AUCUNE différence non intentionnelle
terraform plan
- Résumer les modifications de
terraform plan(lorsqu'une différence est détectée) : Siterraform plansignale autre chose queNo changes, arrêtez-vous avant de recommanderapplyet présentez un résumé structuré :- Totaux du plan : Indiquez le nombre exact de ressources (
X à ajouter, Y à modifier, Z à détruire) et toute transition d'état demoved. - Modifications à haut risque en premier (
-/+remplacer,-détruire) : Mettez en évidence chaque ressource marquée pour remplacement ou suppression, nommez l'attributForceNewqui en est la cause (par ex.,name,image,boot_disk), et expliquez comment le corriger (comme ajouter un blocmoved {}manquant ou rétablir la valeur d'origine danstfvars). - Mises à jour sur place (
~) et créations (+) : Listez chaque ressource affectée et les attributs exacts qui changent (par ex.,labels,tags,scopes,public_access_prevention). - Dérive intentionnelle vs non intentionnelle : Marquez clairement quelles différences sont intentionnelles (durcissement de sécurité explicite ou standardisation des balises) par rapport à la dérive brownfield non intentionnelle afin que l'utilisateur puisse les approuver ou les annuler.
- Totaux du plan : Indiquez le nombre exact de ressources (
Directives et références détaillées
- Référez-vous à Disposition standard des fichiers pour les modèles de regroupement par domaine.
- Référez-vous à Règles de refactoring pour les conventions de nommage, les garde-fous
ForceNew, les balises/étiquettes et les listes de contrôle anti-patrons.