Aller au contenu
Infrastructure as Code medium
🔐 Alerte sécurité — Incident supply chain Trivy : lire mon analyse de l'attaque

Bonnes pratiques pour les modules Terraform

9 min de lecture

logo terraform

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.

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.

ModuleBonne portéeTrop large
reseauCrée un réseau avec DHCP et DNSCrée un réseau et des VMs et des volumes
vmCrée une VM avec son disqueCrée une VM et configure le firewall et installe des packages
stockageCrée un volume avec son backing storeCré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.

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 sorties

Cette 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.

Le module déclare les contraintes sur les providers (required_providers) mais ne les configure jamais :

modules/reseau/versions.tf — CORRECT
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "~> 0.8"
}
}
}
modules/reseau/versions.tf — INTERDIT
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).

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é.

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.

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.

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.

Chaque version stable d’un module doit être marquée par un tag Git sémantique et documentée dans un CHANGELOG :

Fenêtre de terminal
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.

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)
Fenêtre de terminal
terraform test
Success! 2 passed, 0 failed.

Les détails sont dans Tester un module.

#PratiqueVérifié ?
1Le module fait une seule chose
2Structure standard (4 fichiers)
3Pas de bloc provider dans le module
4Variables avec type + description
5Defaults sensibles pour les variables optionnelles
6Validations sur les entrées critiques
7Outputs pour toutes les données utiles
8Ressource principale nommée this
9Tag Git sémantique + CHANGELOG
10Tests (defaults + custom)
  1. Un module = une responsabilité — si la description contient “et”, coupez en deux
  2. Structure 4 fichiers : versions.tf, variables.tf, main.tf, outputs.tf
  3. Le bloc provider n’a pas sa place dans un module réutilisable
  4. Types + descriptions + validations = interface publique documentée et sécurisée
  5. Les outputs sont l’interface de sortie — exposez tout ce qui est utile
  6. Versionner + tester avant de partager — c’est le minimum professionnel

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn