Terraform en production : workspaces, import et CI/CD
🎯 Objectifs
- ✅ Gérer plusieurs environnements avec les workspaces
- ✅ Utiliser les expressions avancées :
for_each,count, conditionnels - ✅ Importer des ressources existantes dans Terraform
- ✅ Intégrer Terraform dans un pipeline CI/CD
- ✅ Appliquer les patterns de production et bonnes pratiques sécurité
📋 Prérequis
- Terraform Intermédiaire (variables, state, modules)
🌍 Workspaces
Les workspaces permettent de gérer plusieurs environnements (dev, staging, prod) avec le même code.
Commandes
| Commande | Description |
|---|---|
terraform workspace new dev | Créer un workspace |
terraform workspace list | Lister les workspaces |
terraform workspace select prod | Changer de workspace |
terraform workspace delete dev | Supprimer un workspace |
Utilisation dans le code
locals {
env_config = {
dev = { port = 8080, replicas = 1 }
prod = { port = 80, replicas = 3 }
}
config = local.env_config[terraform.workspace]
}
resource "docker_container" "web" {
name = "app-${terraform.workspace}"
count = local.config.replicas
ports {
internal = 80
external = local.config.port + count.index
}
}⚠️ Chaque workspace a son propre state. Vérifiez toujours le workspace actif avant un
apply.
🧮 Expressions et fonctions
Conditionnels
resource "docker_container" "monitoring" {
count = terraform.workspace == "prod" ? 1 : 0
name = "prometheus"
image = "prom/prometheus:latest"
}for_each - Créer des ressources depuis une map
variable "containers" {
default = {
nginx = { image = "nginx:alpine", port = 8080 }
redis = { image = "redis:alpine", port = 6379 }
}
}
resource "docker_container" "apps" {
for_each = var.containers
name = each.key
image = each.value.image
ports {
internal = each.value.port
external = each.value.port
}
}count vs for_each
count | for_each | |
|---|---|---|
| Identifiant | Index numérique ([0], [1]) | Clé de la map (["nginx"]) |
| Ajout/Suppression | Réindexe tout | Cible uniquement l'élément |
| Usage idéal | N copies identiques | Ressources nommées distinctes |
Fonctions utiles
locals {
names = join(", ", ["a", "b", "c"]) # "a, b, c"
env = lookup(var.config, "env", "dev") # avec valeur par défaut
rendered = format("app-%s-%02d", "web", 1) # "app-web-01"
count = length(var.containers) # nombre d'éléments
}🔄 Lifecycle Rules
Contrôlez le comportement de Terraform lors des mises à jour.
resource "docker_container" "web" {
name = "app"
image = "nginx:alpine"
lifecycle {
create_before_destroy = true # Crée le nouveau avant de détruire l'ancien
prevent_destroy = true # Bloque toute destruction accidentelle
ignore_changes = [image] # Ignore les changements sur ce champ
}
}| Règle | Usage |
|---|---|
create_before_destroy | Zero-downtime deployments |
prevent_destroy | Protéger les bases de données |
ignore_changes | Ignorer les changements manuels externes |
📥 Import de ressources existantes
Intégrez l'infrastructure existante dans Terraform sans la recréer.
Commande import (classique)
# 1. Écrire la ressource dans le code
# 2. Importer dans le state
terraform import docker_container.legacy abc123def456Bloc import (Terraform 1.5+)
import {
to = docker_container.legacy
id = "abc123def456"
}Puis lancez terraform plan - Terraform génère le code correspondant.
💡 L'import ne crée pas le code HCL automatiquement (sauf avec
terraform plan -generate-config-out).
⚙️ Provisioners
Les provisioners exécutent des commandes après la création d'une ressource.
resource "docker_container" "web" {
name = "app"
image = "nginx:alpine"
provisioner "local-exec" {
command = "echo 'Conteneur ${self.name} créé avec IP ${self.network_data[0].ip_address}'"
}
}| Type | Exécution |
|---|---|
local-exec | Sur la machine qui lance Terraform |
remote-exec | Sur la ressource distante (SSH) |
⚠️ Les provisioners sont un dernier recours. Préférez : cloud-init, Ansible, ou des images pré-configurées (Packer).
🚀 Terraform et CI/CD
Automatisez l'infrastructure comme le code applicatif.
Pipeline GitHub Actions
name: Terraform
on:
push:
branches: [main]
pull_request:
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Init
run: terraform init
- name: Format check
run: terraform fmt -check
- name: Validate
run: terraform validate
- name: Plan
run: terraform plan -no-color
if: github.event_name == 'pull_request'
- name: Apply
run: terraform apply -auto-approve
if: github.ref == 'refs/heads/main'Principes CI/CD pour Terraform
terraform plansur chaque Pull Request (visible dans la review)terraform applyuniquement sur merge dans main- Jamais de
applymanuel en production
💡 Atlantis automatise plan/apply directement depuis les PR GitLab/GitHub.
🔒 Sécurité
Variables sensibles
variable "db_password" {
type = string
sensitive = true
}.gitignore obligatoire
*.tfstate
*.tfstate.backup
.terraform/
*.tfvars # si contient des secretsScanning de sécurité
# Checkov - analyse statique de sécurité
pip install checkov
checkov -d .
# tfsec - scanner spécifique Terraform
brew install tfsec
tfsec .🏭 Patterns de production
| Pratique | Description |
|---|---|
| 🗂️ State séparé par env | Un backend/state par environnement |
| 🔒 State distant + lock | S3 + DynamoDB ou Terraform Cloud |
| 📌 Versions épinglées | version = "~> 3.0" sur chaque provider |
| ✅ Validation en CI | terraform fmt -check + terraform validate |
| 👀 Code review | Toute modification d'infra passe en PR |
| 📁 Structure claire | environments/dev/, environments/prod/, modules/ |
| 🏷️ Tags systématiques | project, environment, managed_by sur toute ressource |
Structure recommandée
infrastructure/
├── modules/
│ ├── network/
│ └── app/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ └── prod/
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tfvars🎯 Points clés
- Les workspaces gèrent les environnements - mais les dossiers séparés sont plus sûrs en production
for_eachest préférable àcountpour des ressources distinctes- L'import permet d'adopter Terraform progressivement sur l'existant
- Le CI/CD rend l'infrastructure aussi fiable que le déploiement applicatif
- La sécurité : variables sensibles, scanning,
.gitignoresont non négociables
📚 Ressources
- Terraform Documentation
- Terraform Registry
- HashiCorp Learn
- Checkov - Policy as Code
- Atlantis - Terraform Automation
🚀 Prochaines étapes
Votre infrastructure est gérée en code. Sécurisez maintenant vos pipelines :
- Sécuriser ses pipelines et applications (DevSecOps) - Scanning, secrets, supply chain et bonnes pratiques