WIKI Retour au Portfolio

Dernière mise à jour : 12 juin 2026

HCTA-004 — Terraform Modulaire

HCTA-004 – HashiCorp Certified Terraform Associate

Logo certification 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

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.

Terraform Modules & Workspaces


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.

Structure d'un module Terraform

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.

Workflow Git-based modules

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.

Recherche d'un module sur le Terraform Registry

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.md pour 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

Flux de données Parent → Child → Template → EC2

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

Échange Root ↔ Child module via variables et outputs


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 des dynamic blocks.
  • 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

Graphe de dépendances généré par terraform graph

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.

Exemple d'arborescence d'un provider schema


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" (provider hashicorp/template) est archivée depuis 2021. La fonction native templatefile() 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 :

Modular Infrastructure — projet complet