Un module Terraform ressemble à une fonction : il prend des variables en entrée, produit des sorties, et encapsule une logique réutilisable. La comparaison s’arrête là sur un point structurel qui surprend souvent : un module n’a pas son propre state, il partage celui du module racine qui l’appelle.
Ce que “pas de state propre” implique réellement
Un module appelé plusieurs fois depuis le module racine crée des ressources distinctes, mais toutes suivies dans le même fichier de state que le reste de la configuration. Il n’existe pas d’isolation de state entre un module et son appelant : un terraform state list depuis le module racine montre toutes les ressources, y compris celles créées par des modules imbriqués, préfixées par leur chemin d’appel.
module "vpc_prod" {
source = "./modules/vpc"
cidr = "10.0.0.0/16"
}
module "vpc_staging" {
source = "./modules/vpc"
cidr = "10.1.0.0/16"
}
# Les deux instances apparaissent dans le même state,
# préfixées par le chemin du module qui les a créées
terraform state list
# module.vpc_prod.aws_vpc.main
# module.vpc_staging.aws_vpc.main
Pourquoi ça compte pour le blast radius d’un apply
Puisque tout partage le même state, un terraform apply sur la configuration racine évalue et peut modifier l’ensemble des ressources de tous les modules appelés, pas seulement celui qu’on vient de modifier. Une modification dans un module utilisé par dix autres configurations ne se limite jamais à l’endroit où le changement a été fait : elle se propage à chaque appelant au prochain apply, un rayon d’impact qui grandit avec le nombre d’appelants, pas avec la taille du changement lui-même.
Le piège du découpage trop fin
Un module par ressource individuelle (un module pour un bucket S3, un autre pour une politique IAM qui s’y attache) semble modulaire, mais crée une dépendance implicite entre plans : le module de politique IAM a besoin de l’ARN du bucket, produit par un autre module, ce qui force soit un passage de données entre modules à chaque appel, soit un couplage via des data sources qui recréent une dépendance cachée que Terraform doit résoudre correctement à chaque plan.
# Découpage trop fin : chaque appel doit repasser
# manuellement ce qu'un module plus large gérerait en interne
module "bucket" {
source = "./modules/s3-bucket"
}
module "bucket_policy" {
source = "./modules/iam-policy"
bucket_arn = module.bucket.arn
}
Un module regroupé par unité fonctionnelle cohérente (un module « stockage sécurisé » qui inclut le bucket et sa politique) évite cette dépendance inter-modules explicite, au prix d’une modularité moins fine si un jour le bucket doit exister sans sa politique.
Le versioning, la vraie discipline qui manque souvent
Un module référencé par un chemin local (./modules/vpc) n’a pas de version : toute modification du module affecte immédiatement tous ses appelants dès le prochain apply, sans étape de validation intermédiaire. Un module référencé depuis un registre ou un dépôt Git avec un tag explicite permet de figer une version connue et de la faire évoluer délibérément, module appelant par module appelant.
# Version épinglée : le module n'évolue que si
# quelqu'un change explicitement ce numéro
module "vpc" {
source = "app.terraform.io/example-org/vpc/aws"
version = "5.2.0"
}
Sans cette discipline de version, un module partagé entre plusieurs équipes devient un point de couplage fragile : personne ne sait avec certitude quelle version tourne réellement où, jusqu’à ce qu’un changement dans le module casse un appelant qui n’avait pourtant rien modifié de son côté.
À retenir
Un module Terraform ne dispose pas de son propre state : il partage celui du module racine, ce qui signifie qu’un apply évalue toujours l’ensemble des ressources de tous les modules appelés, pas seulement celui qui vient de changer. Un découpage trop fin en modules crée des dépendances implicites entre plans ; un module non versionné (chemin local) propage ses changements immédiatement à tous ses appelants sans étape de validation. Ces deux réflexes (regrouper par unité fonctionnelle cohérente, versionner explicitement un module partagé) évitent le couplage fragile qui apparaît dès qu’une industrialisation CI/CD fait grandir le nombre de configurations Terraform partageant les mêmes modules.