infra / terraform
Structuration de projet niveau expert
Explication
Ce que vous allez apprendre
- Organiser un projet Terraform en séparant clairement modules internes et environnements
- Découper le state par domaine fonctionnel pour limiter le "blast radius" d'un incident
- Utiliser
default_tagsau niveauproviderpour une cohérence automatique du tagging - Comprendre pourquoi
.terraform.lock.hcldoit toujours être committé - Générer automatiquement la documentation des modules avec
terraform-docs
Dans quel contexte ?
Après quinze leçons, l'équipe maîtrise chaque brique individuelle de Terraform : ressources, variables, state, modules, CI/CD, sécurité. Il reste une dernière question, essentielle une fois qu'une organisation grandit avec plusieurs équipes : comment organiser TOUS ces éléments ensemble, dans un projet qui restera compréhensible et sûr à faire évoluer dans deux ans, par des personnes qui ne l'ont pas écrit ?
D'abord, il faut poser une séparation claire entre deux types de dossiers
Les modules internes (modules/vpc, modules/rds-postgres) doivent rester génériques, sans la moindre référence à un environnement spécifique — jamais de condition du type "if production" à l'intérieur d'un module. Les environnements (environments/production), à l'inverse, ne font qu'ASSEMBLER ces modules génériques avec des paramètres propres à leur contexte.
Cette séparation nette évite qu'un module ne devienne, avec le temps, un fourre-tout impossible à réutiliser ailleurs qu'à l'endroit où il a été écrit initialement.
| Dossier | Contient | Connaît l'environnement ? |
|---|---|---|
modules/ | Blocs resource génériques et réutilisables | Non, jamais |
environments/<env>/ | Appels de modules avec des paramètres précis | Oui, c'est son rôle |
Prérequis
Cette leçon suppose une maîtrise complète des modules (leçon dédiée), du multi-environnement (leçon dédiée) et des backends distants — elle assemble ces trois notions dans une structure de référence complète.
Une fois cette séparation posée, il reste un problème de taille : un état trop gros
Un unique state monolithique, contenant l'intégralité de l'infrastructure d'une organisation, ralentit chaque plan/apply (qui doit rafraîchir l'état de TOUTES les ressources) et surtout, augmente le "blast radius" : une erreur sur une seule ressource peut bloquer un apply concernant des dizaines d'autres ressources sans aucun rapport.
Bonne pratique
Découpe le state par domaine fonctionnel (réseau, données, applicatif) plutôt que par simple commodité de fichier. Un problème dans le state "applicatif" n'a alors aucun impact sur le state "réseau", et chaque équipe peut avoir des permissions IAM distinctes sur son propre domaine.
Maintenant, deux détails qui semblent mineurs mais évitent des incidents réels
default_tags, défini une seule fois au niveau du bloc provider, applique automatiquement des tags cohérents (ManagedBy = "terraform", Environment = "production") à TOUTES les ressources créées ensuite, sans avoir à les répéter dans chaque bloc resource — une source d'oubli fréquente autrement.
Piège fréquent
Ne JAMAIS ignorer ou exclure .terraform.lock.hcl du contrôle de version. Ce fichier fixe les hashes exacts des providers utilisés ; sans lui committé, un développeur ou la CI pourrait télécharger une version légèrement différente d'un provider, avec un comportement subtilement différent — un bug particulièrement difficile à diagnostiquer, puisqu'il ne dépend d'aucune ligne de code HCL modifiée.
Le mot de la fin sur ce cours
terraform-docs génère automatiquement un README à jour listant les entrées et sorties d'un module, éliminant le risque classique d'une documentation manuelle qui dérive du code réel au fil du temps. Cette dernière leçon boucle la boucle du cours : depuis un simple bucket S3 créé à la main jusqu'à une organisation complète, modulaire, sécurisée et documentée — c'est exactement ce niveau de rigueur qui distingue une infrastructure de test d'une infrastructure de production digne de confiance.
Commandes & code
Structuration de projet niveau expert
# Structure recommandée pour une organisation avec plusieurs équipes/environnements
infra/
├── modules/ # modules internes réutilisables, PAS d'environnement spécifique ici
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── ecs-service/
│ └── rds-postgres/
│
├── environments/
│ ├── dev/
│ │ ├── main.tf # appelle les modules avec des paramètres "dev"
│ │ ├── backend.tf # backend séparé de prod
│ │ ├── terraform.tfvars
│ │ └── providers.tf
│ ├── staging/
│ │ └── ...(même structure)
│ └── production/
│ └── ...(même structure)
│
└── .github/workflows/
└── terraform.yml# environments/production/providers.tf : versions verrouillées, alias multi-région si besoin
terraform {
required_version = "~> 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.60"
}
}
}
provider "aws" {
region = "eu-west-3"
default_tags {
tags = {
ManagedBy = "terraform"
Environment = "production"
Repository = "infra"
}
}
}
# Provider secondaire pour une ressource cross-région (ex: réplication S3, CloudFront/ACM us-east-1)
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
}# environments/production/main.tf : composition claire, chaque module a une responsabilité unique
module "vpc" {
source = "../../modules/vpc"
environment = "production"
cidr = "10.0.0.0/16"
}
module "database" {
source = "../../modules/rds-postgres"
environment = "production"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
instance_class = "db.r6g.xlarge"
}
module "api_service" {
source = "../../modules/ecs-service"
environment = "production"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
desired_count = 6
}# Verrouiller les versions exactes des providers utilisés (fichier généré, à COMMITTER)
cat .terraform.lock.hcl # hashes des providers, garantit des builds reproductibles CI/local
# Découper les grosses configs pour accélérer plan/apply (state plus petit = plus rapide, moins de risque)
# Un state PAR domaine fonctionnel (réseau, données, applicatif) plutôt qu'un unique state monolithique# Principes de structuration niveau expert :
# 1. Modules internes SANS référence d'environnement (pas de "if prod" dans un module générique)
# 2. Un state PAR domaine/environnement : limite le "blast radius" d'un apply raté, accélère plan/apply
# 3. default_tags au niveau provider : cohérence automatique des tags sans les répéter partout
# 4. .terraform.lock.hcl TOUJOURS committé : builds reproductibles entre devs et CI
# 5. Pas de logique métier complexe en HCL : HCL décrit l'infra, pas des algorithmes (préférer un module dédié sinon)
# 6. Documentation générée automatiquement des modules (terraform-docs) plutôt que maintenue à la main# Génération automatique de documentation de module (README avec inputs/outputs à jour)
terraform-docs markdown table ./modules/vpc > modules/vpc/README.mdRésumé
- Séparer modules internes (génériques, sans logique d'environnement) et environnements (composent les modules).
- Un state par domaine fonctionnel limite le blast radius et accélère les
plan/applypar rapport à un state monolithique. .terraform.lock.hcldoit toujours être committé pour garantir des versions de providers reproductibles.default_tagsau niveauprovideretterraform-docsréduisent la duplication et la dérive de documentation.
Exercices pratiques
Mission : découper un state monolithique avant qu'il n'explose
Objectif : Réorganiser un projet Terraform à state unique en structure modules/environnements avec states séparés, et justifier chaque choix de découpage.
Contexte
L'organisation a grandi trop vite : tout le réseau, la base de données et le service applicatif de production vivent dans un seul répertoire infra/ avec un unique state, et un module modules/ecs-service contient encore une condition if var.environment == "production" en dur à l'intérieur. La semaine dernière, une erreur sur une simple règle de sécurité réseau a bloqué le déploiement du service applicatif pendant deux heures, alors que rien n'avait changé côté application. Il faut restructurer avant le prochain incident.