
Pour séparer dev, staging et prod avec Terraform, créez un module commun contenant le code d’infrastructure, puis un répertoire par environnement qui appelle ce module avec ses propres paramètres. Chaque environnement possède son propre state si vous gardez le backend local par répertoire ou si vous configurez un backend distant distinct par environnement. C’est la méthode recommandée par HashiCorp pour les projets professionnels.
Prérequis : avoir lu Les workspaces Terraform et Quand utiliser les workspaces pour comprendre pourquoi cette approche remplace les workspaces en contexte professionnel.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Créer un module Terraform réutilisable
- Configurer un répertoire par environnement avec ses propres paramètres
- Vérifier l’isolation des states selon le backend utilisé
- Prouver qu’un
destroydans un environnement ne touche pas les autres
La structure cible
Section intitulée « La structure cible »Voici l’arborescence que vous allez mettre en place :
mon-projet/├── modules/│ └── reseau/│ ├── versions.tf # Contraintes de providers│ ├── variables.tf # Paramètres du module│ ├── main.tf # Ressources communes│ └── outputs.tf # Valeurs exposées├── envs/│ ├── dev/│ │ ├── backend.tf # Backend local ou distant spécifique à dev│ │ ├── main.tf # Appelle le module avec les paramètres dev│ │ └── outputs.tf # Expose les valeurs dev│ └── prod/│ ├── backend.tf # Backend local ou distant spécifique à prod│ ├── main.tf # Appelle le module avec les paramètres prod│ └── outputs.tf # Expose les valeurs prodChaque répertoire dans envs/ est un projet Terraform indépendant avec son propre terraform init et son propre cycle de vie. Avec le backend local par défaut, chaque répertoire garde naturellement son terraform.tfstate. Avec un backend distant, l’isolation dépend de la configuration du backend : bucket, key, workspace backend ou credentials doivent être distincts selon le niveau d’isolation recherché.
Ajouter un backend par environnement
Section intitulée « Ajouter un backend par environnement »En lab local, vous pouvez ne rien déclarer : chaque répertoire gardera son propre terraform.tfstate local. En contexte professionnel, ajoutez un backend.tf par environnement ou passez des paramètres différents via -backend-config.
terraform { backend "s3" { bucket = "terraform-state-dev" key = "reseau/terraform.tfstate" region = "eu-west-1" }}terraform { backend "s3" { bucket = "terraform-state-prod" key = "reseau/terraform.tfstate" region = "eu-west-1" }}Si vous utilisez le même bucket et la même key pour dev et prod, les deux environnements partageront le même state distant malgré les répertoires séparés.
Créer le module commun
Section intitulée « Créer le module commun »Le module regroupe tout le code d’infrastructure. Il ne contient aucune valeur en dur — tout passe par des variables.
-
Créer le fichier
modules/reseau/versions.tfCe fichier déclare les contraintes de version du provider, sans configurer le provider lui-même (c’est le rôle de l’appelant) :
terraform {required_version = ">= 1.11.0"required_providers {libvirt = {source = "dmacvicar/libvirt"version = "~> 0.8"}}} -
Créer le fichier
modules/reseau/variables.tfChaque paramètre qui varie entre environnements devient une variable :
variable "env_name" {description = "Nom de l'environnement (dev, staging, prod…)"type = string}variable "network_address" {description = "Adresse réseau (ex: 10.10.70.1)"type = string}variable "network_netmask" {description = "Masque réseau"type = stringdefault = "255.255.255.0"}variable "dhcp_start" {description = "Début de la plage DHCP"type = string}variable "dhcp_end" {description = "Fin de la plage DHCP"type = string}variable "disk_size_gb" {description = "Taille du volume en Go"type = number}variable "base_image_path" {description = "Chemin vers l'image de base"type = string} -
Créer le fichier
modules/reseau/main.tfLes ressources utilisent les variables au lieu de valeurs en dur. Le nom de chaque ressource intègre
var.env_namepour éviter les collisions :resource "libvirt_network" "env_net" {name = "${var.env_name}-network"autostart = trueforward = {mode = "nat"}dns = {enabled = true}ips = [{address = var.network_addressnetmask = var.network_netmaskdhcp = {ranges = [{start = var.dhcp_startend = var.dhcp_end}]}}]}resource "libvirt_volume" "env_disk" {name = "${var.env_name}-disk.qcow2"pool = "default"backing_store = {path = var.base_image_pathformat = {type = "qcow2"}}target = {format = {type = "qcow2"}}capacity = var.disk_size_gb * 1024 * 1024 * 1024} -
Créer le fichier
modules/reseau/outputs.tfLes outputs rendent accessibles les identifiants et noms des ressources créées :
output "network_name" {description = "Nom du réseau créé"value = libvirt_network.env_net.name}output "network_id" {description = "ID du réseau créé"value = libvirt_network.env_net.id}output "volume_name" {description = "Nom du volume créé"value = libvirt_volume.env_disk.name}output "volume_id" {description = "ID du volume créé"value = libvirt_volume.env_disk.id}
Le module est prêt. Il ne fait rien seul — il a besoin d’un appelant qui lui fournit les valeurs.
Configurer les environnements
Section intitulée « Configurer les environnements »Chaque environnement appelle le même module avec des paramètres différents.
L’environnement dev
Section intitulée « L’environnement dev »Créez le fichier envs/dev/main.tf :
terraform { required_version = ">= 1.11.0"
required_providers { libvirt = { source = "dmacvicar/libvirt" version = "~> 0.8" } }}
provider "libvirt" { uri = "qemu:///system"}
module "infra" { source = "../../modules/reseau"
env_name = "dev" network_address = "10.10.70.1" dhcp_start = "10.10.70.10" dhcp_end = "10.10.70.100" disk_size_gb = 4 base_image_path = "/chemin/vers/image.qcow2"}Le fichier envs/dev/outputs.tf expose les valeurs du module :
output "network_name" { value = module.infra.network_name}
output "volume_name" { value = module.infra.volume_name}L’environnement prod
Section intitulée « L’environnement prod »Le fichier envs/prod/main.tf appelle le même module mais avec des paramètres production — réseau différent, volume plus gros :
terraform { required_version = ">= 1.11.0"
required_providers { libvirt = { source = "dmacvicar/libvirt" version = "~> 0.8" } }}
provider "libvirt" { uri = "qemu:///system"}
module "infra" { source = "../../modules/reseau"
env_name = "prod" network_address = "10.10.71.1" dhcp_start = "10.10.71.10" dhcp_end = "10.10.71.100" disk_size_gb = 8 base_image_path = "/chemin/vers/image.qcow2"}Déployer et vérifier l’isolation
Section intitulée « Déployer et vérifier l’isolation »-
Initialiser et déployer dev
Fenêtre de terminal cd envs/devterraform initTerraform télécharge le provider et référence le module local :
Initializing modules...- infra in ../../modules/reseauInitializing provider plugins...- Installing dmacvicar/libvirt v0.9.7...Terraform has been successfully initialized!Fenêtre de terminal terraform apply -auto-approvePlan: 2 to add, 0 to change, 0 to destroy.module.infra.libvirt_volume.env_disk: Creation complete after 0smodule.infra.libvirt_network.env_net: Creation complete after 0sApply complete! Resources: 2 added, 0 changed, 0 destroyed.Outputs:network_name = "dev-network"volume_name = "dev-disk.qcow2" -
Initialiser et déployer prod
Sans quitter le répertoire dev, ouvrez un autre terminal ou changez de répertoire :
Fenêtre de terminal cd ../prodterraform initterraform apply -auto-approvePlan: 2 to add, 0 to change, 0 to destroy.Apply complete! Resources: 2 added, 0 changed, 0 destroyed.Outputs:network_name = "prod-network"volume_name = "prod-disk.qcow2" -
Vérifier la coexistence
Les deux environnements coexistent sur la même machine :
Fenêtre de terminal virsh net-list --all | grep -E 'dev|prod'dev-network active yes yesprod-network active yes yesFenêtre de terminal virsh vol-list default | grep -E 'dev|prod'dev-disk.qcow2 /var/lib/libvirt/images/dev-disk.qcow2prod-disk.qcow2 /var/lib/libvirt/images/prod-disk.qcow2 -
Vérifier l’isolation des states
Chaque environnement a son propre fichier state :
Fenêtre de terminal find . -name "*.tfstate" | sort./envs/dev/terraform.tfstate./envs/prod/terraform.tfstateLe state dev ne contient que les ressources dev, le state prod que les ressources prod. Aucun mélange possible.
Prouver l’isolation : détruire dev sans toucher prod
Section intitulée « Prouver l’isolation : détruire dev sans toucher prod »La force de cette approche : un terraform destroy dans un répertoire ne peut pas affecter les autres.
cd envs/devterraform destroy -auto-approvemodule.infra.libvirt_volume.env_disk: Destroying...module.infra.libvirt_network.env_net: Destroying...
Destroy complete! Resources: 2 destroyed.Vérification immédiate — prod est toujours là :
virsh net-list --all | grep -E 'dev|prod' prod-network active yes yesUn terraform plan dans prod confirme zéro changement :
cd ../prodterraform planNo changes. Your infrastructure matches the configuration.Ajouter un environnement staging
Section intitulée « Ajouter un environnement staging »Pour ajouter un nouvel environnement, il suffit de créer un répertoire et d’appeler le module :
mkdir envs/stagingCréez envs/staging/main.tf :
terraform { required_version = ">= 1.11.0"
required_providers { libvirt = { source = "dmacvicar/libvirt" version = "~> 0.8" } }}
provider "libvirt" { uri = "qemu:///system"}
module "infra" { source = "../../modules/reseau"
env_name = "staging" network_address = "10.10.72.1" dhcp_start = "10.10.72.10" dhcp_end = "10.10.72.100" disk_size_gb = 4 base_image_path = "/chemin/vers/image.qcow2"}Puis terraform init && terraform apply. Pas besoin de modifier le module ni les autres environnements.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
Error: Module not found | Mauvais chemin relatif dans source | Vérifier que ../../modules/reseau pointe vers le bon dossier |
Error: No configuration files | terraform init lancé depuis la racine | Se placer dans envs/dev/ ou envs/prod/ avant d’exécuter |
| Collision de noms de ressources | Même env_name dans deux environnements | Chaque répertoire doit avoir un env_name unique |
| Deux environnements partagent le même state distant | Même backend, bucket ou key réutilisés | Définir un backend ou au minimum une key différente par environnement |
| Module modifié mais pas pris en compte | Cache du module périmé | Relancer terraform init -upgrade dans l’environnement concerné |
| Provider version mismatch entre envs | Lock file divergent | Copier le .terraform.lock.hcl à jour ou re-init |
À retenir
Section intitulée « À retenir »- Un module commun contient le code (ressources, variables, outputs) sans aucune valeur en dur
- Un répertoire par environnement appelle le module avec les paramètres adaptés (adresses, tailles, noms)
- Chaque répertoire a son propre cycle de vie ; l’isolation du state est automatique en backend local et doit être configurée explicitement en backend distant
- Détruire un environnement ne peut pas toucher les autres — l’isolation est structurelle
- Ajouter un nouvel environnement prend quelques minutes : un répertoire, un
main.tf, unterraform init - En contexte professionnel, chaque répertoire doit pointer vers un backend adapté avec une
key, un bucket ou des credentials qui évitent tout partage involontaire du state