HCTA-004 — Terraform Modulaire
HCTA-004 – HashiCorp Certified Terraform Associate

Historique du document
| Date | Version | Auteur | Commentaires |
|---|---|---|---|
| 01/08/2025 | 0.1 | Hamid HAMILA | Version initiale |
Document de référence
| Nom du document | Version / Date |
|---|---|
| 1 – HCTA003 – TERRAFORM MODULAIRE.pdf | 1.0 / 01-08-2025 |
Sommaire
- A. Introduction
- B. Structure Modulaire
- C. Module Local vs Externe
- D. Variables, Outputs et Flux de données
- E. Réutilisation et abstraction
- F. Gestion Multi-environnement
- G. Visualisation & Debug
- H. Utilitaires
- I. Workflow Modulaire
A. Introduction
Un module Terraform est une unité réutilisable de configuration qui regroupe un ensemble de ressources Terraform. Il agit comme une fonction dans un langage de programmation classique : on lui passe des "paramètres" (variables), il exécute de la logique (main.tf), et renvoie des "résultats" (outputs).
Pourquoi utiliser des modules ?
- Réutilisabilité : éviter de dupliquer du code entre projets.
- Organisation : mieux structurer son infrastructure (ex : un module VPC, un module EC2, etc.).
- Collaboration : travailler à plusieurs sur des briques séparées.
- Standardisation : appliquer les mêmes règles de tagging, de naming, de sécurité.
- Maintenance : mettre à jour une logique une seule fois, la modification se répercute partout.
Les modules permettent une véritable approche modulaire, composable et scalable de la gestion d'infrastructure.

B. Structure Modulaire
Un module Terraform suit une structure de fichiers bien définie. Cette organisation facilite la lisibilité du code, la collaboration entre les équipes et l'intégration dans des workflows CI/CD.

Rôle des fichiers :
main.tf: contient la définition principale des ressources à créer.variables.tf: déclare toutes les entrées attendues par le module (avec type et valeur par défaut).outputs.tf: liste les valeurs que le module va exposer à l'extérieur (ID de ressource, IP, etc.).README.md: documente l'utilisation, les variables et outputs du module.
Un bon module doit être autonome, testable et bien documenté.
Parent / Root vs Child module :
tf-module-example/
├── modules/
│ ├── server-module/ ← Child Module
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── variables.tf
│ └── storage-module/ ← Child Module
└── main.tf ← Root Module
Bonnes pratiques : nommer les fichiers avec clarté, documenter chaque variable et output, et éviter de mélanger la logique métier dans les modules.
C. Module Local vs Externe
Terraform permet de référencer des modules depuis différentes sources :
Module local
Utilisé lorsqu'un module est situé dans un sous-dossier du projet. Avantages : rapide, simple pour prototyper, sans dépendance externe.
module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
}
Module Git (externe)
Utilisé pour partager un module entre plusieurs projets via un dépôt Git. Avantages : versionné, centralisé, facile à maintenir à l'échelle d'une organisation.

module "vpc" {
source = "git::https://github.com/mon-org/terraform-vpc.git?ref=v1.2.0"
}
Module du Terraform Registry
Utilisé pour consommer des modules publics ou communautaires. Avantages : validé par la communauté, documenté, rapide à intégrer.

module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 4.0"
}
C.I. Versionning
Pour garantir la stabilité d'un projet Terraform, il est crucial de figer les versions des modules que l'on utilise, notamment ceux provenant de Git ou du Registry public.
module "vpc" { # git
source = "git::https://github.com/org/vpc-module.git?ref=v1.0.3"
}
module "vpc" { # terraform registry
source = "terraform-aws-modules/vpc/aws"
version = "~> 4.0"
}
Pourquoi versionner ?
- Éviter les changements non maîtrisés (breaking changes).
- Pouvoir faire des rollbacks faciles.
- Assurer une compatibilité avec les autres composants.
Bonnes pratiques :
- Utiliser des tags Git (
v1.0.0) pour versionner vos propres modules. - Ajouter un
CHANGELOG.mdpour documenter les modifications. - Tester toute nouvelle version avant publication.
D. Variables, Outputs et Flux de données
Les variables et outputs sont essentiels pour faire circuler l'information entre les modules et leur environnement d'appel.
Les variables du child module sont reprises dans les inputs du parent/root module :
Exemple — extrait du diagramme ci-dessous :
# variables.tf (child)
variable "gds_key" {
description = "GDS key"
}
# outputs.tf (child)
output "gds_key_out" {
value = var.gds_key
}
# main.tf (root) — appel du module
module "neo4j-environment" {
source = "../neo4j-tf-module"
vpc_base_cidr = "10.123.0.0/16"
env_prefix = "neo4j-test-mod"
gds_key = "the-gds-key"
}
# instances.tf — usage de la variable dans un template
# "${path.module}/neo4j.tftpl",
# {
# gdsKey = "${var.gds_key}"
# }
# neo4j.tftpl
# echo "GDS Key: ${gdsKey}" >> /tmp/test.out

Les outputs d'un module peuvent être récupérés entre modules :
# Child module — déclare l'output
module "bucket" {
source = "github.com/myCompany-modules.git?ref=2.0.0/infrastructure-modules/aws-modules/s3"
env = module.environment.environment
name = "${local.prefix}-mybucket"
tags = local.tags
}
# Root module — accède via module.MODULE_NAME.OUTPUT_NAME
output "my-buckets" {
description = "S3 buckets created for my-server"
value = [values(aws_s3_bucket.this)[*]["arn"]]
}
# Référence depuis le root : module.bucket.my-buckets

E. Réutilisation et abstraction
Pour rendre un module vraiment réutilisable :
- Utiliser des paramètres au lieu de valeurs codées en dur.
- Rendre les blocs conditionnels avec
count,for_each, ou desdynamicblocks. - Générer dynamiquement des ressources à partir de listes ou maps.
Exemple concret :
# Exemple simple de composition
module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
}
module "bastion" {
source = "./modules/ec2"
subnet_id = module.vpc.public_subnet_id
instance_type = "t2.micro"
}
# Appel multiple du même module
module "db_instance_1" {
source = "./modules/ec2"
name = "db1"
subnet_id = module.vpc.private_subnet_id
}
module "db_instance_2" {
source = "./modules/ec2"
name = "db2"
subnet_id = module.vpc.private_subnet_id
}
# Ou plus dynamique avec for_each
module "apps" {
for_each = var.apps
source = "./modules/ec2"
name = each.key
subnet_id = module.vpc.app_subnet_id
instance_type = each.value.instance_type
}
F. Gestion Multi-environnement
Lorsque l'on gère une infrastructure cloud pour plusieurs environnements (dev, test, prod), il est essentiel de séparer clairement les configurations, les states et les variables. Deux grandes approches sont possibles : par dossiers séparés ou via les workspaces Terraform.
1. Par dossiers :
project/
├── modules/
│ └── vpc/
└── envs/
├── dev/
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tfvars
└── prod/
├── main.tf
├── variables.tf
└── terraform.tfvars
Chaque environnement a sa propre configuration, facilitant la personnalisation (tags, régions, tailles des ressources).
2. Par workspaces :
terraform workspace new dev
terraform workspace select dev
terraform apply
Le même code est utilisé, mais les états sont isolés dans des workspaces. Il faut combiner avec des terraform.tfvars spécifiques à chaque environnement.
Bonne pratique : un tfvars par environnement et spécifier un backend distant (S3, Blob Storage, etc.).
G. Visualisation & Debug
Pour bien comprendre le comportement de Terraform avec des modules, plusieurs outils et commandes sont utiles :
TERRAFORM CONSOLE
Permet d'évaluer des expressions Terraform en live.
> filebase64("main.tf")
IyBtYWluLnRmCnByb3ZpZGVyICJhd3MiIHsKICByZWdpb24gPSB2YXIud2ViX3JlZ2lvbiBPSAi...
> timestamp()
2024-10-08T11:12:58Z
> var.instance_type
"t2.micro"
> output.example_output
TERRAFORM GRAPH
Génère une représentation graphique des dépendances. Cela permet d'avoir une vue d'ensemble des relations entre ressources. (Il faut installer graphviz pour récupérer en PNG.)
terraform graph | dot -Tpng > graph.png
terraform graph -type=plan | dot -Tpng > graph.png

TERRAFORM GET
Télécharge ou met à jour les modules utilisés dans le projet.
terraform get -update
ip-172-31-7-227:/# terraform get
ip-172-31-7-227:/# terraform get -update
- Downloading registry.terraform.io/terraform-aws-modules/vpc/aws 3.0.0 for vpc...
- vpc in .terraform/modules/vpc
TERRAFORM SHOW
Affiche l'état actuel des ressources suivies (vu dans BASICS).
TERRAFORM PROVIDERS SCHEMA
La commande terraform providers schema permet d'afficher la structure détaillée des providers utilisés dans ton projet, avec toutes les ressources, les arguments disponibles, et les attributs en sortie.
Utilité :
- Explorer les ressources et données qu'un provider expose.
- Voir les champs obligatoires et facultatifs.
- Comprendre les ressources complexes.
Il faut ouvrir le schema.json généré dans un éditeur (ou avec jq) pour naviguer dedans.

H. Utilitaires
H.I. Chemins (path.x)
Ces variables spéciales sont très utiles pour référencer des fichiers ou construire dynamiquement des chemins sans avoir à coder des chemins absolus.
| Variable | Description |
|---|---|
path.cwd |
Chemin absolu d'où Terraform a été exécuté |
path.root |
Répertoire racine du root module |
path.module |
Répertoire courant du module (child ou root selon où c'est appelé) |
Exemple :
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = "t3.micro"
# path.module = répertoire du module courant
user_data = templatefile("${path.module}/scripts/init.sh", {
hostname = var.hostname
})
}
⚠️ La data source
data "template_file"(providerhashicorp/template) est archivée depuis 2021. La fonction nativetemplatefile()la remplace dans tous les usages modernes.
H.II. Locals
Les locals permettent de définir des valeurs intermédiaires ou constantes, non modifiables depuis l'extérieur du module.
Avantages : centralise des calculs, améliore la lisibilité et réduit les duplications.
Exemple :
locals {
region = "eu-west-1"
instance_type = "t3.micro"
full_name = "${var.environment}-${var.name}"
common_tags = {
environment = var.environment
managed_by = "terraform"
}
}
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = local.instance_type
tags = local.common_tags
}
H.III. Outputs
Les output sont utilisés pour exposer des valeurs depuis un module, notamment pour permettre au root module d'accéder aux résultats du child module.
Exemple :
# 1. Dans le child module : définir un output
output "instance_ip" {
value = aws_instance.web.public_ip
}
# 2. Dans le root module : y accéder via :
module "web" {
source = "./modules/web"
...
}
output "ip_web" {
value = module.web.instance_ip
}
H.IV. Providers et cas avancés
Beaucoup de bonnes pratiques concernent la bonne utilisation des providers :
- Un child module n'a pas à déclarer les blocs
provider. Il hérite des providers du root module. - Cas avancé avec plusieurs providers (multi-region, alias).
Lorsqu'un module a besoin d'un provider spécifique ou aliasé, il faut le lui passer explicitement via providers = {}.
provider "aws" {
region = "eu-west-1"
}
provider "aws" {
alias = "us"
region = "us-west-2"
}
module "infra_eu" {
source = "./modules/infra"
providers = {
aws = aws
}
}
module "infra_us" {
source = "./modules/infra"
providers = {
aws = aws.us
}
}
I. Workflow Modulaire
Module File Structure — Best Practices :
- Use a
modules/directory if stored locally - Use
variables.tf,outputs.tf,main.tf, etc. consistently - Ensure all templates and artifacts are included
- Document your modules with a
README
/my-terraform-project ← Root Module (entry point)
├── main.tf ← Calls child modules
├── variables.tf
├── outputs.tf
├── terraform.tfvars
├── providers.tf
└── modules/ ← Child modules directory
├── networking/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── README.md
├── compute/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── README.md
└── storage/
├── main.tf
├── variables.tf
├── outputs.tf
└── README.md
Modular Infrastructure — schéma complet d'un projet modulaire avec Root Module, Repos Module et Info Page Module :
