
Un module Terraform fonctionne avec un seul fichier — mais personne ne fait ça. La convention universelle consiste à découper le module en 4 fichiers distincts, chacun ayant un rôle précis. Ce découpage ne change rien au comportement de Terraform (qui charge tous les .tf d’un dossier), mais il change tout pour les humains qui lisent et maintiennent le code.
Ce guide détaille chaque fichier, son rôle, et montre concrètement la différence entre un module monolithique et un module correctement structuré.
Prérequis : avoir lu Créer un module Terraform et compris le bloc module.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Nommer les 4 fichiers standards d’un module et leur rôle
- Comparer un module monolithique vs un module découpé
- Reconnaître la structure d’un module au premier coup d’oeil
- Appliquer les conventions de nommage utilisées dans l’écosystème Terraform
Pourquoi découper un module en plusieurs fichiers
Section intitulée « Pourquoi découper un module en plusieurs fichiers »Terraform traite chaque dossier comme un module. Il lit tous les fichiers .tf du dossier et les fusionne. Que vous ayez 1 fichier de 70 lignes ou 4 fichiers de 20 lignes chacun, le résultat est strictement identique.
Alors pourquoi découper ? Pour la lisibilité. Quand un collègue ouvre votre module, il doit pouvoir répondre en 5 secondes à ces questions :
| Question | Fichier à ouvrir |
|---|---|
| Quels providers le module utilise-t-il ? | versions.tf |
| Quels paramètres dois-je fournir ? | variables.tf |
| Que fait concrètement le module ? | main.tf |
| Quelles valeurs le module expose-t-il ? | outputs.tf |
Avec un seul fichier, il faut parcourir 70 lignes pour trouver ces réponses. Avec 4 fichiers, le nom du fichier est la réponse.
Les 4 fichiers standards
Section intitulée « Les 4 fichiers standards »versions.tf — contraintes de version
Section intitulée « versions.tf — contraintes de version »terraform { required_version = ">= 1.11.0" # ↑ version minimale de Terraform pour utiliser ce module
required_providers { libvirt = { source = "dmacvicar/libvirt" version = "~> 0.8" # ↑ contrainte de version du provider } }}Ce fichier déclare deux choses :
- La version minimale de Terraform requise
- Les providers dont le module dépend, avec leurs contraintes de version
Le module ne configure pas le provider (pas de bloc provider "libvirt" { ... }). C’est la configuration racine qui le fait. Le module dit simplement « j’ai besoin de ce provider, dans cette plage de versions ».
variables.tf — les entrées du module
Section intitulée « variables.tf — les entrées du module »variable "nom" { type = string description = "Nom du réseau" # ↑ pas de default → variable obligatoire}
variable "adresse" { type = string description = "Adresse du réseau (ex: 10.10.80.0)"}
variable "masque" { type = string description = "Masque de sous-réseau" default = "255.255.255.0" # ↑ default présent → variable optionnelle}
variable "dhcp_debut" { type = string description = "Première adresse DHCP"}
variable "dhcp_fin" { type = string description = "Dernière adresse DHCP"}Ce fichier rassemble toutes les variables du module. Pour chaque variable :
type: le type de donnée attendu (string,number,bool,list,map,object)description: ce à quoi sert la variable — indispensable pour la documentationdefault(optionnel) : si présent, la variable est optionnelle ; sinon, l’appelant doit fournir une valeur
main.tf — la logique principale
Section intitulée « main.tf — la logique principale »resource "libvirt_network" "this" { name = var.nom autostart = true
forward = { mode = "nat" }
dns = { enabled = true }
ips = [{ address = var.adresse netmask = var.masque dhcp = { ranges = [{ start = var.dhcp_debut end = var.dhcp_fin }] } }]}Ce fichier contient les ressources que le module crée. C’est la logique métier. Les valeurs dynamiques viennent des variables (var.nom, var.adresse, etc.).
Pour un module simple, un seul main.tf suffit. Pour un module complexe (réseau + volume + VM), on peut découper en fichiers supplémentaires par domaine : network.tf, storage.tf, compute.tf.
outputs.tf — les sorties du module
Section intitulée « outputs.tf — les sorties du module »output "id" { value = libvirt_network.this.id description = "UUID du réseau créé"}
output "nom" { value = libvirt_network.this.name description = "Nom du réseau créé"}Les outputs sont le contrat de sortie du module. Sans eux, la configuration racine ne peut pas accéder aux attributs des ressources créées par le module. C’est la seule façon de « passer des informations » d’un module vers son appelant.
Comparaison : monolithique vs structuré
Section intitulée « Comparaison : monolithique vs structuré »Pour rendre la différence concrète, voici le même module dans les deux approches.
Approche monolithique — 1 fichier
Section intitulée « Approche monolithique — 1 fichier »modules/ └── reseau/ └── main.tf ← 69 lignes (tout dedans)En ouvrant main.tf, on trouve dans l’ordre : le bloc terraform, 5 blocs variable, 1 bloc resource, 2 blocs output. Pour trouver les variables, il faut scroller. Pour trouver les outputs, il faut aller à la fin.
Approche structurée — 4 fichiers
Section intitulée « Approche structurée — 4 fichiers »modules/ └── reseau/ ├── versions.tf ← 10 lignes ├── variables.tf ← 25 lignes ├── main.tf ← 23 lignes └── outputs.tf ← 9 lignesChaque fichier a une seule responsabilité. Le développeur qui veut connaître les paramètres ouvre variables.tf — il n’a pas besoin de lire le reste.
Le résultat pour Terraform est identique
Section intitulée « Le résultat pour Terraform est identique »Les deux approches passent terraform validate et produisent le même plan. Terraform n’impose aucune convention de nommage de fichiers. Le découpage est une convention humaine, pas une contrainte technique.
terraform validate# → Success! The configuration is valid.Fichiers optionnels
Section intitulée « Fichiers optionnels »Au-delà des 4 fichiers standards, certains modules incluent :
| Fichier | Rôle | Quand l’ajouter |
|---|---|---|
README.md | Documentation du module (paramètres, exemples) | Toujours pour un module partagé |
locals.tf | Variables locales calculées | Quand le module a des expressions complexes |
data.tf | Sources de données (data blocks) | Quand le module lit des ressources existantes |
network.tf, compute.tf | Découpage de main.tf par domaine | Module complexe (> 5 ressources) |
Convention de nommage des ressources dans un module
Section intitulée « Convention de nommage des ressources dans un module »Dans un module, la ressource principale porte souvent le nom this :
resource "libvirt_network" "this" { ... }Pourquoi this ? Parce que le module est déjà nommé (module "reseau_lab"). L’identifiant complet dans le state sera module.reseau_lab.libvirt_network.this — le contexte est suffisant. Appeler la ressource reseau serait redondant (module.reseau_lab.libvirt_network.reseau).
Si le module crée plusieurs ressources, utilisez des noms descriptifs pour les distinguer :
resource "libvirt_volume" "disque_systeme" { ... }resource "libvirt_volume" "disque_donnees" { ... }Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
Terraform ignore un fichier .tf | Fichier dans un sous-dossier non référencé | Les .tf doivent être à la racine du dossier module, pas dans un sous-dossier |
Variable non trouvée dans main.tf | Variable déclarée dans un autre fichier | Normal — Terraform charge tous les .tf du dossier. Vérifier l’orthographe |
Error: provider configuration not present | Bloc provider manquant | Le provider se configure dans la racine, pas dans le module |
À retenir
Section intitulée « À retenir »- Un module Terraform standard contient 4 fichiers :
versions.tf,variables.tf,main.tf,outputs.tf - Terraform charge tous les
.tfd’un dossier — le découpage est une convention, pas une obligation - Le découpage permet de répondre en 5 secondes à « quels paramètres ? », « que fait-il ? », « que renvoie-t-il ? »
- Ne jamais mettre de bloc
providerdans un module réutilisable - La ressource principale d’un module se nomme souvent
this - Pour un module complexe, découper
main.tfen fichiers par domaine (network.tf,compute.tf)