
Vous copiez-collez les mêmes blocs de ressources entre projets ? Un module Terraform résout ce problème : il regroupe des ressources dans un répertoire dédié et les expose comme un bloc réutilisable. Vous l’appelez avec un bloc module, vous passez des paramètres, et Terraform crée les ressources pour vous — sans dupliquer une seule ligne.
Ce guide vous montre comment créer votre premier module, l’appeler depuis une configuration racine, et vérifier que l’infrastructure se déploie correctement. À la fin, vous serez capable de factoriser n’importe quelle configuration répétitive en module.
Prérequis : Terraform ≥ 1.11 installé, KVM/libvirt opérationnel. Avoir suivi les guides sur la déclaration de ressources et les variables Terraform.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Comprendre ce qu’est un module et pourquoi il est utile
- Créer un module réseau avec ses fichiers standards
- Appeler ce module depuis une configuration racine avec le bloc
module - Réutiliser le même module avec des paramètres différents
- Diagnostiquer les erreurs courantes liées aux modules
Ce qu’est un module Terraform
Section intitulée « Ce qu’est un module Terraform »La logique est simple : « un module, c’est un dossier contenant des fichiers .tf ».
En réalité, chaque projet Terraform est déjà un module — le module racine. Quand vous écrivez main.tf, variables.tf et outputs.tf dans un répertoire, vous avez un module. La seule différence avec un « sous-module » est que celui-ci est appelé par un autre module via le bloc module.
Pourquoi créer des modules
Section intitulée « Pourquoi créer des modules »Sans module, un projet qui crée 3 réseaux identiques avec des paramètres différents doit tripler le code de la ressource. Chaque modification — un changement de masque, un ajout d’option DNS — doit être faite 3 fois.
Avec un module :
| Sans module | Avec module |
|---|---|
| 25 lignes × 3 réseaux = 75 lignes | 25 lignes dans le module + 3 appels de 8 lignes = 49 lignes |
| Modifier = 3 endroits à mettre à jour | Modifier = 1 seul endroit |
| Risque d’incohérence entre copies | Comportement garanti identique |
Plus le nombre de réseaux (ou de ressources) augmente, plus l’avantage grandit.
Créer le module
Section intitulée « Créer le module »Un module réseau minimal contient 4 fichiers dans un sous-dossier modules/reseau/ :
mon-projet/├── main.tf # ← configuration racine (appelle le module)├── outputs.tf├── versions.tf└── modules/ └── reseau/ ├── main.tf # ← ressource(s) du module ├── variables.tf ├── outputs.tf └── versions.tf-
Créer
modules/reseau/versions.tf— les contraintes de version du module :terraform {required_version = ">= 1.11.0"required_providers {libvirt = {source = "dmacvicar/libvirt"version = "~> 0.8"}}}Le module déclare ses propres contraintes de provider. C’est la configuration racine qui instancie le provider (
provider "libvirt" { ... }), mais le module indique de quel provider il dépend. -
Créer
modules/reseau/variables.tf— les paramètres du module :variable "nom" {type = stringdescription = "Nom du réseau libvirt"}variable "adresse" {type = stringdescription = "Adresse IP du réseau (ex: 10.10.80.0)"}variable "masque" {type = stringdescription = "Masque de sous-réseau (ex: 255.255.255.0)"default = "255.255.255.0"}variable "dhcp_debut" {type = stringdescription = "Première adresse DHCP"}variable "dhcp_fin" {type = stringdescription = "Dernière adresse DHCP"}Chaque variable a un type, une description et éventuellement une valeur par défaut. Les variables sans
defaultsont obligatoires — l’appelant doit fournir une valeur. -
Créer
modules/reseau/main.tf— la ressource que le module crée :resource "libvirt_network" "this" {name = var.nomautostart = trueforward = {mode = "nat"}dns = {enabled = true}ips = [{address = var.adressenetmask = var.masquedhcp = {ranges = [{start = var.dhcp_debutend = var.dhcp_fin}]}}]}Le code utilise
var.nom,var.adresse, etc. — les valeurs viendront de l’appelant. -
Créer
modules/reseau/outputs.tf— les valeurs que le module expose :output "id" {value = libvirt_network.this.iddescription = "UUID du réseau créé"}output "nom" {value = libvirt_network.this.namedescription = "Nom du réseau créé"}Sans outputs, le module racine ne peut pas accéder aux attributs des ressources créées par le module. Les outputs sont le contrat de sortie du module.
Appeler le module depuis la racine
Section intitulée « Appeler le module depuis la racine »La configuration racine se trouve dans le répertoire parent. Elle instancie le provider et appelle le module avec un bloc module :
versions.tf (racine) :
terraform { required_version = ">= 1.11.0"
required_providers { libvirt = { source = "dmacvicar/libvirt" version = "~> 0.8" } }}
provider "libvirt" { uri = "qemu:///system"}main.tf (racine) :
module "reseau_lab" { source = "./modules/reseau" # ↑ chemin relatif vers le dossier du module
nom = "lab-module" adresse = "10.10.80.0" masque = "255.255.255.0" dhcp_debut = "10.10.80.10" dhcp_fin = "10.10.80.254"}Le bloc module contient deux types d’informations :
source: le chemin vers le dossier du module (ici./modules/reseau)- Les arguments : chaque variable déclarée dans le module (
nom,adresse, etc.) reçoit une valeur
outputs.tf (racine) :
output "reseau_id" { value = module.reseau_lab.id # ↑ module.<NOM_DU_BLOC>.<NOM_OUTPUT>}
output "reseau_nom" { value = module.reseau_lab.nom}Pour lire un output de module, la syntaxe est module.<nom_du_bloc>.<nom_output>.
Initialiser et déployer
Section intitulée « Initialiser et déployer »-
Initialiser — Terraform détecte et installe le module local :
Fenêtre de terminal terraform initInitializing modules...- reseau_lab in modules/reseauTerraform has been successfully initialized!La ligne
reseau_lab in modules/reseauconfirme que Terraform a trouvé le module. -
Vérifier le plan :
Fenêtre de terminal terraform plan# module.reseau_lab.libvirt_network.this will be created+ resource "libvirt_network" "this" {+ autostart = true+ forward = { mode = "nat" }+ ips = [{ address = "10.10.80.0", netmask = "255.255.255.0", ... }]+ name = "lab-module"}Plan: 1 to add, 0 to change, 0 to destroy.Le préfixe
module.reseau_lab.apparaît devant chaque ressource — c’est comme ça que Terraform identifie les ressources créées par un module dans le state. -
Déployer :
Fenêtre de terminal terraform apply -auto-approvemodule.reseau_lab.libvirt_network.this: Creating...module.reseau_lab.libvirt_network.this: Creation complete after 0s [id=6cf43294-...]Apply complete! Resources: 1 added, 0 changed, 0 destroyed.Outputs:reseau_id = "6cf43294-b6c9-40c6-8811-f517fcfbe2e1"reseau_nom = "lab-module" -
Vérifier que le réseau existe bien :
Fenêtre de terminal virsh net-list --all | grep lab-modulelab-module active yes yesLe réseau est actif, en autostart, avec DHCP activé.
Réutiliser le module
Section intitulée « Réutiliser le module »L’intérêt principal d’un module est la réutilisabilité. Un deuxième appel au même module, avec des paramètres différents, crée un deuxième réseau sans toucher au premier :
module "reseau_dev" { source = "./modules/reseau"
nom = "dev-network" adresse = "10.10.81.0" masque = "255.255.255.0" dhcp_debut = "10.10.81.10" dhcp_fin = "10.10.81.254"}Après le terraform init et terraform apply, les deux réseaux coexistent :
virsh net-list --all | grep -E "lab-module|dev-network" dev-network active yes yes lab-module active yes yesLe state montre les deux modules avec leurs préfixes distincts :
terraform state listmodule.reseau_dev.libvirt_network.thismodule.reseau_lab.libvirt_network.thisChaque instance du module a son propre espace dans le state. Les deux réseaux sont indépendants — modifier ou détruire l’un ne touche pas l’autre.
Anatomie du bloc module
Section intitulée « Anatomie du bloc module »Récapitulons la syntaxe du bloc module :
module "nom_unique" { source = "./chemin/vers/module" # ↑ obligatoire : où trouver le code du module
# Arguments = valeurs des variables du module variable_1 = "valeur" variable_2 = 42}| Élément | Rôle | Obligatoire |
|---|---|---|
"nom_unique" | Identifiant du bloc module (libre, doit être unique) | Oui |
source | Chemin vers le dossier du module | Oui |
| Arguments | Valeurs passées aux variable du module | Si la variable n’a pas de default |
La valeur de source peut être :
- Un chemin local :
./modules/reseauou../modules-partages/reseau - Une URL Git :
git::https://gitlab.example.com/infra/modules.git//reseau - Un module du Terraform Registry :
hashicorp/consul/aws
Ce guide couvre les chemins locaux. Les autres sources sont traitées dans les guides suivants.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
Error: Module not installed | Nouveau bloc module sans terraform init | Relancer terraform init |
Error: No value for required variable | Variable sans default non fournie | Ajouter l’argument manquant dans le bloc module |
Error: Unsupported argument | Argument qui ne correspond à aucune variable | Vérifier le nom dans variables.tf du module |
| Ressource non préfixée dans le state | Le code n’est pas dans un module | Vérifier que la ressource est dans le dossier du module, pas à la racine |
À retenir
Section intitulée « À retenir »- Un module est un dossier de fichiers
.tf— chaque projet est déjà un module (le module racine) - Un module standard contient 4 fichiers :
versions.tf,variables.tf,main.tf,outputs.tf - Le bloc
moduledans la configuration racine appelle le module et lui passe des valeurs - L’argument
sourceest obligatoire — il pointe vers le chemin du module - Après chaque ajout de bloc
module, il faut relancerterraform init - Les ressources du module apparaissent dans le state avec le préfixe
module.<nom>. - Réutiliser un module = un nouveau bloc
moduleavec des paramètres différents — zéro duplication de code