Aller au contenu principal
TerraformInfrastructure as CodeFormation

Modules : structurer son code

30 min de lecture Terraform — Chapitre 6

Crée des modules Terraform réutilisables, adopte les bonnes pratiques de structure de projet et prépare ton code pour la production.

Tu sais créer des ressources, gérer le state, utiliser des variables. Mais ton projet grossit et ton main.tf fait 500 lignes. C’est le moment de découper ton code en modules — des blocs réutilisables qui transforment ton Terraform artisanal en infrastructure industrielle.

Pourquoi les modules changent tout

Sans modules, tout vit dans un seul répertoire : le root module. Pour 10 ressources, ça passe. À 50, c’est ingérable. Les modules résolvent ça en apportant :

  • L’encapsulation — un réseau, un cluster, une app = un module autonome
  • La réutilisabilité — même module pour dev, staging et prod
  • L’abstraction — tes collègues consomment le module sans connaître les détails
  • Le versioning — chaque composant évolue indépendamment

💡 Pense “fonction” : un module prend des inputs (variables), crée des ressources, et expose des outputs. Exactement comme une fonction dans n’importe quel langage.

Anatomie d’un module

Un module Terraform, c’est simplement un répertoire avec des fichiers .tf. La convention standard :

modules/
└── web-app/
    ├── main.tf          # Ressources principales
    ├── variables.tf     # Variables d'entrée
    ├── outputs.tf       # Valeurs de sortie
    ├── versions.tf      # Contraintes providers
    └── README.md        # Documentation

Les variables d’entrée

C’est le contrat du module — ce que le consommateur doit (ou peut) fournir :

variable "app_name" {
  description = "Nom de l'application"
  type        = string

  validation {
    condition     = can(regex("^[a-z][a-z0-9-]{2,24}$", var.app_name))
    error_message = "Minuscules, 3-25 chars, alphanumérique avec tirets."
  }
}

variable "environment" {
  description = "Environnement cible"
  type        = string
  default     = "dev"

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Doit être dev, staging ou prod."
  }
}

variable "instance_count" {
  description = "Nombre d'instances à provisionner"
  type        = number
  default     = 1
}

🔥 Les validations sont ton filet de sécurité. Elles attrapent les erreurs au plan, pas après un déploiement raté en prod.

Les outputs

Ce que le module expose aux appelants — les données nécessaires pour câbler les modules entre eux :

output "instance_ids" {
  description = "IDs des instances créées"
  value       = openstack_compute_instance_v2.app[*].id
}

output "instance_ips" {
  description = "Adresses IP des instances"
  value       = openstack_compute_instance_v2.app[*].access_ip_v4
}

Appeler et composer des modules

Depuis ton root module, tu appelles un module avec le bloc module :

module "api" {
  source         = "./modules/web-app"
  app_name       = "api"
  environment    = "prod"
  instance_count = 3
}

module "backoffice" {
  source         = "./modules/web-app"
  app_name       = "backoffice"
  environment    = "prod"
  instance_count = 1
}

# Câbler les outputs
output "api_ips" {
  value = module.api.instance_ips
}

La vraie puissance arrive avec la composition : un module app-stack qui orchestre un module network, un module compute et un module database. Les outputs du réseau deviennent les inputs du compute. C’est comme ça que les grandes organisations structurent leur infra.

⚠️ Ne sur-abstrais pas trop tôt. Commence par dupliquer, identifie les patterns récurrents, puis extrais en module. Un module prématuré est pire que du code dupliqué.

Sources distantes et Registry

Les modules locaux (source = "./modules/xxx") sont parfaits pour démarrer. Quand plusieurs équipes partagent les mêmes modules, passe à des sources distantes :

# Depuis un repo Git (bonne pratique : toujours pinner la version)
module "network" {
  source = "git::https://gitlab.com/mon-org/tf-modules.git//network?ref=v2.1.0"
}

# Depuis le Terraform Registry (modules publics communautaires)
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name            = "mon-vpc"
  cidr            = "10.0.0.0/16"
  azs             = ["eu-west-1a", "eu-west-1b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]
}

Versioning sémantique

Applique le semver sur tes modules internes :

  • MAJOR (v2.0.0) — breaking change : variable renommée, output supprimé
  • MINOR (v1.1.0) — nouvelle feature rétrocompatible
  • PATCH (v1.0.1) — bug fix

🎯 Règle d’or en prod : toujours pinner une version. Jamais de ref=main ou version = ">= 1.0". Un terraform init un lundi matin qui tire une version non testée, c’est la garantie d’un incident.

Cas entreprise : structurer un vrai projet

Voici comment une équipe de 5 devs organise un projet Terraform mature :

infra/
├── modules/              # Modules internes réutilisables
│   ├── network/
│   ├── compute/
│   └── database/
├── environments/         # Un dossier par env
│   ├── dev/
│   │   ├── main.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   └── prod/
└── global/               # Ressources partagées (DNS, IAM)

Mono-repo vs multi-repo ? Commence en mono-repo. C’est plus simple pour le refactoring et la CI/CD. Migre vers du multi-repo uniquement quand plusieurs équipes se marchent dessus ou quand le blast radius devient critique.

Les conventions qui sauvent la vie à 6 mois :

  • Nommage cloud : <projet>-<env>-<composant>monapp-prod-api-sg
  • Variables : snake_case, description obligatoire, pas de défaut qui correspond à la prod
  • Fichiers : un par responsabilité (main.tf, variables.tf, outputs.tf), pas un par ressource
  • Code review : terraform plan joint à chaque PR, terraform fmt en pre-commit

💡 Automatise le formatting avec pre-commit :

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.96.0
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terraform_tflint

Les pièges classiques

Module trop générique. Tu veux un module qui fait tout — réseau, compute, storage, monitoring. Résultat : 40 variables, des conditions partout, impossible à maintenir. Mieux vaut 3 modules simples qu’un monstre configurable.

Oublier les outputs. Un module sans outputs est une boîte noire. Les modules en aval ne peuvent pas se câbler. Expose systématiquement les IDs et les attributs réseau.

Modules circulaires. Le module A dépend de B qui dépend de A. Terraform ne sait pas résoudre ça. Solution : extrais la dépendance commune dans un module C.

Pas de moved block après un refactoring. Tu renommes un module ou une ressource et Terraform veut détruire/recréer. Utilise le bloc moved pour migrer proprement :

moved {
  from = module.old_name
  to   = module.new_name
}

⚠️ Le Registry n’est pas un gage de qualité. Avant d’utiliser un module public, vérifie : dernière activité, nombre de contributeurs, présence de tests, et lis le code. Un module abandonné avec des failles, c’est de la dette technique importée.

Ce qu’on retient

🎯 Un module Terraform = un répertoire avec main.tf, variables.tf, outputs.tf. C’est une fonction pour ton infrastructure.

Les essentiels :

  • Encapsule par domaine — réseau, compute, data, pas par ressource individuelle
  • Valide les inputs — les blocs validation évitent les erreurs en prod
  • Expose les outputs — c’est le contrat de ton module avec le monde extérieur
  • Pine les versions — en prod, toujours une version exacte ou contrainte serrée
  • Commence simple — duplique d’abord, module ensuite quand le pattern est clair
  • Documenteterraform-docs génère la doc automatiquement depuis ton code

Tu maîtrises maintenant la structuration de projets Terraform. Dans le prochain chapitre, on passe à FastAPI pour construire des APIs modernes en Python — le pont entre ton infra et tes applications. 🚀

🖥️ Pratique sur ton propre serveur

Pour suivre Terraform en conditions réelles, tu as besoin d'un VPS. DigitalOcean offre 200$ de crédit gratuit pour démarrer.

Obtenir 200$

Articles liés