
Un bon module Terraform est petit, fait une seule chose, est documenté et testé. Ces bonnes pratiques sont le résultat des erreurs les plus courantes observées dans les projets Terraform. Les appliquer dès le départ évite des refactorisations coûteuses quand le projet grandit.
Prérequis : avoir lu les guides de la section Modules Terraform.
1. Un module = une responsabilité
Section intitulée « 1. Un module = une responsabilité »Un module doit créer un ensemble cohérent de ressources autour d’un seul concept. Si vous pouvez décrire le module en une phrase sans utiliser “et”, c’est bon signe.
| Module | Bonne portée | Trop large |
|---|---|---|
reseau | Crée un réseau avec DHCP et DNS | Crée un réseau et des VMs et des volumes |
vm | Crée une VM avec son disque | Crée une VM et configure le firewall et installe des packages |
stockage | Crée un volume avec son backing store | Crée un volume et l’attache à une VM |
Si votre module crée des ressources qui peuvent exister indépendamment, découpez-le en modules séparés et utilisez le chaînage via les outputs.
2. Structure standard à 4 fichiers
Section intitulée « 2. Structure standard à 4 fichiers »Adoptez la convention universelle pour la structure :
modules/reseau/├── versions.tf ← contraintes terraform + providers├── variables.tf ← toutes les variables├── main.tf ← les ressources└── outputs.tf ← toutes les sortiesCette structure est reconnue par tous les outils de l’écosystème Terraform (documentation automatique, linters, Registry). Les détails sont dans le guide Structure d’un module.
3. Jamais de bloc provider dans un module
Section intitulée « 3. Jamais de bloc provider dans un module »Le module déclare les contraintes sur les providers (required_providers) mais ne les configure jamais :
terraform { required_providers { libvirt = { source = "dmacvicar/libvirt" version = "~> 0.8" } }}provider "libvirt" { uri = "qemu:///system" # ↑ NE PAS FAIRE — le provider est configuré par l'appelant}Le bloc provider appartient à la configuration racine. Le module reçoit le provider par héritage automatique. Cela permet à un même module d’être utilisé avec des providers configurés différemment (différentes URIs, différentes régions).
4. Variables typées avec descriptions
Section intitulée « 4. Variables typées avec descriptions »Chaque variable doit avoir un type explicite et une description :
variable "nom" { type = string description = "Nom du réseau libvirt (3-30 caractères)" # ↑ type + description = autogenerated documentation}Les descriptions apparaissent dans la documentation générée automatiquement et dans les messages d’erreur de Terraform. Elles sont la documentation de l’interface publique du module.
5. Defaults sensibles pour les variables optionnelles
Section intitulée « 5. Defaults sensibles pour les variables optionnelles »Les variables optionnelles doivent avoir des valeurs par défaut raisonnables — des valeurs qui fonctionnent sans configuration supplémentaire :
variable "activer_dns" { type = bool default = true # ↑ Valeur par défaut sensible : la plupart des réseaux ont besoin du DNS}
variable "mode_forward" { type = string default = "nat" # ↑ NAT est le mode le plus courant et le plus sûr pour un lab}La règle pratique est la suivante : une variable optionnelle mérite un default si la valeur proposée est sûre, prévisible et pertinente pour le cas nominal. Un bon default permet d’utiliser le module sans configuration inutile, sans masquer un choix risqué.
6. Validations sur les entrées critiques
Section intitulée « 6. Validations sur les entrées critiques »Ajoutez un bloc validation pour chaque variable dont les valeurs possibles sont connues :
variable "mode_forward" { type = string default = "nat"
validation { condition = contains(["nat", "route", "bridge", "none"], var.mode_forward) error_message = "Le mode doit être nat, route, bridge ou none." }}Les validations détectent les erreurs avant terraform plan. Un message d’erreur clair évite de longues sessions de débogage. Les détails sont dans Variables et outputs.
7. Outputs pour chaque donnée utile
Section intitulée « 7. Outputs pour chaque donnée utile »Exposez via output tout ce dont un module consommateur pourrait avoir besoin :
- Identifiants : UUID, IDs de ressources
- Noms : noms réels créés (pour vérification)
- Attributs calculés : adresses IP attribuées, URLs générées
output "id" { value = libvirt_network.this.id description = "UUID du réseau créé"}Un output absent oblige l’appelant à contourner le problème (data sources, parsing du state). C’est un signe que l’interface du module est incomplète.
8. Nommer la ressource principale this
Section intitulée « 8. Nommer la ressource principale this »Quand un module ne crée qu’une seule ressource d’un type donné, nommez-la this :
resource "libvirt_network" "this" { # ↑ convention : "this" pour la ressource principale name = var.nom}Cette convention est utilisée par les modules du Terraform Registry et par l’outil de documentation terraform-docs. Elle est immédiatement reconnaissable.
9. Versionner et documenter les changements
Section intitulée « 9. Versionner et documenter les changements »Chaque version stable d’un module doit être marquée par un tag Git sémantique et documentée dans un CHANGELOG :
git tag -a v1.1.0 -m "Ajout de la variable tags"Le versionnement protège les projets consommateurs des changements non prévus. Les détails sont dans Versionner ses modules.
10. Tester avant de publier
Section intitulée « 10. Tester avant de publier »Chaque module doit avoir au minimum :
- Un test des valeurs par défaut (le module fonctionne “out of the box”)
- Un test des paramètres personnalisés (les variables sont bien transmises)
terraform test
Success! 2 passed, 0 failed.Les détails sont dans Tester un module.
Checklist récapitulative
Section intitulée « Checklist récapitulative »| # | Pratique | Vérifié ? |
|---|---|---|
| 1 | Le module fait une seule chose | ☐ |
| 2 | Structure standard (4 fichiers) | ☐ |
| 3 | Pas de bloc provider dans le module | ☐ |
| 4 | Variables avec type + description | ☐ |
| 5 | Defaults sensibles pour les variables optionnelles | ☐ |
| 6 | Validations sur les entrées critiques | ☐ |
| 7 | Outputs pour toutes les données utiles | ☐ |
| 8 | Ressource principale nommée this | ☐ |
| 9 | Tag Git sémantique + CHANGELOG | ☐ |
| 10 | Tests (defaults + custom) | ☐ |
À retenir
Section intitulée « À retenir »- Un module = une responsabilité — si la description contient “et”, coupez en deux
- Structure 4 fichiers : versions.tf, variables.tf, main.tf, outputs.tf
- Le bloc
providern’a pas sa place dans un module réutilisable - Types + descriptions + validations = interface publique documentée et sécurisée
- Les outputs sont l’interface de sortie — exposez tout ce qui est utile
- Versionner + tester avant de partager — c’est le minimum professionnel