Terraform — Fondamentaux, état, modules
Orchestrer l’infrastructure avec Terraform : définir le réseau, les serveurs, les bases de données comme du code, et les déployer de manière reproductible via HCL (HashiCorp Configuration Language).
Terraform gère le cycle complet :
init→plan→apply→destroy. L’état (state) est le pont entre le fichier.tfet les ressources réellement déployées. Perdre cet état, c’est perdre le contrôle de l’infrastructure.
1. Installation et première commande
Télécharger le binaire depuis terraform.io/downloads ou utiliser le gestionnaire de paquets du provider.
# Vérifier l'installation
terraform version
# Initialiser un dossier vide
mkdir ~/infra && cd ~/infra
terraform initterraform init télécharge les providers (AWS, GCP, Azure, DigitalOcean, etc.) spécifiés dans le code. C’est une commande à lancer à chaque ajout de provider ou de module.
2. Configuration de base
Un fichier .tf contient au minimum un bloc terraform (contraintes de version du moteur), un ou plusieurs blocs provider (chez qui on déploye) et un ou plusieurs blocs resource (ce qu’on crée).
Exemple minimal — créer une machine dans DigitalOcean :
# ~/infra/main.tf
terraform {
required_version = ">= 1.5"
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "~> 2.0"
}
}
}
provider "digitalocean" {
token = var.do_token
}
resource "digitalocean_droplet" "web" {
image = "ubuntu-22-04-x64"
name = "web-01"
region = "fra1"
size = "s-1vcpu-1gb"
}Variables (terraform.tfvars)
Ne jamais committer de secrets bruts dans le code. Utiliser des variables définies dans terraform.tfvars.
# Créer le fichier de variables
cat > terraform.tfvars << 'EOF'
do_token = "do-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
EOF
# Ajouter à .gitignore
echo "terraform.tfvars" >> .gitignoreDéclarer le type dans variables.tf :
# ~/infra/variables.tf
variable "do_token" {
description = "Token d'API DigitalOcean"
type = string
sensitive = true
}Nommage des fichiers
Conventions courantes (Terraform les traite tous au même niveau dans un dossier) :
| Fichier | Rôle |
|---|---|
main.tf | Ressources principales |
variables.tf | Déclarations variable |
outputs.tf | Déclarations output |
terraform.tfvars | Valeurs par défaut des variables |
prod.tfvars | Valeurs spécifiques à un environnement |
versions.tf | Bloc terraform (contraintes de version) |
⚠️ Terraform fusionne tous les
.tfd’un dossier, peu importe le nom. Ne jamais scinder une ressource sur deux fichiers —resourcecomplet dans un seul fichier.
3. Cycle de vie
plan — voir ce qui sera créé/modifié/supprimé
# Générer et afficher le plan
terraform plan -var-file=terraform.tfvars
# Générer un fichier binaire de plan (réutilisable, reproductible)
terraform plan -var-file=terraform.tfvars -out=plan.out
# Appliquer un plan précédemment généré
terraform apply plan.outterraform plan sans apply est l’équivalent d’un dry-run. Toujours l’exécuter avant apply.
apply — créer ou modifier l’infrastructure
terraform apply -var-file=terraform.tfvarsTerraform vérifie d’abord si des changements externes (faits hors Terraform) ont affecté les ressources, puis pose les modifications.
destroy — supprimer toutes les ressources gérées
terraform destroy -var-file=terraform.tfvars⚠️
terraform destroysupprime TOUT ce que Terraform a créé dans l’état actuel. Vérifier le plan de destruction. Ne pas l’exécuter sur un état partagé sans coordination.
refresh — resynchroniser l’état avec le réel
terraform refreshLit l’état, interroge les API des providers, met à jour les attributs dans le .tfstate. Utile après une modification manuelle dans le portail cloud.
4. Gestion de l’état (state)
Par défaut, l’état est un fichier local : terraform.tfstate. En équipe ou en production, c’est dangereux :
- Deux apply simultanés = corruption de l’état.
- Le fichier contient souvent des secrets (mots de passe, tokens, adresses IP internes).
Backend S3 (AWS) — exemple
terraform {
backend "s3" {
bucket = "mon-terraform-state"
key = "infra/prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}Backend GCS (Google Cloud) — exemple
terraform {
backend "gcs" {
bucket = "mon-terraform-state"
prefix = "infra/prod"
}
}Blocage et déverrouillage
Un backend avec locking (S3+DynamoDB, GCS avec versioning, Terraform Cloud) évite les writes concurrents. Si un lock est coincé :
# Forcer le déverrouillage (vérifier d'abord qu'aucun apply ne tourne)
terraform force-unlock <LOCK_ID>Manipuler l’état localement
# Voir les ressources dans l'état
terraform state list
# Extraire une ressource de l'état sans la détruire
terraform state rm digitalocean_droplet.web
# Importer une ressource existante dans l'état
terraform import digitalocean_droplet.web 12345678⚠️
terraform state rmretire la ressource du fichier d’état mais ne la supprime pas de l’infrastructure réelle. Faire le contraire (destroy) la supprime des deux côtés.
5. Modules
Un module est un dossier contenant des fichiers .tf réutilisables. Terraform en fournit un grand nombre sur registry.terraform.io .
Utiliser un module de registre
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "mon-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-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"]
}Écrire un module personnalisé
Structure :
modules/
do_droplet/
main.tf
variables.tf
outputs.tfmodules/do_droplet/main.tf :
variable "name" {
type = string
}
variable "size" {
type = string
default = "s-1vcpu-1gb"
}
resource "digitalocean_droplet" "this" {
image = "ubuntu-22-04-x64"
name = var.name
region = "fra1"
size = var.size
}
output "ip" {
value = digitalocean_droplet.this.ipv4_address
}Appel depuis le projet racine :
module "app-server" {
source = "./modules/do_droplet"
name = "app-01"
size = "s-2vcpu-4gb"
}
output "server_ip" {
value = module.app-server.ip
}Un module qui se répète plus de deux fois dans un projet mérite d’être extrait.
6. Importer des ressources existantes
Lorsqu’on découvre un Terraform sur une infrastructure déjà en place, ou qu’on passe d’une méthode manuelle à Terraform, il faut importer les ressources existantes dans l’état.
# Syntaxe : terraform import <RESOURCE.HCL_NAME> <ID_REEL>
terraform import digitalocean_droplet.web 12345678Après l’import, il faut écrire le code HCL correspondant dans le projet. Terraform ne génère pas le code depuis l’existant — il met juste à jour le fichier .tfstate.
7. Bonnes pratiques
Séparer les états par environnement
infra/
├── prod/
│ ├── main.tf
│ ├── terraform.tfvars
│ └── backend.tf
├── staging/
│ ├── main.tf
│ ├── terraform.tfvars
│ └── backend.tf
└── modules/
└── do_droplet/Chaque environnement a son propre état, son propre backend. On ne partage pas un .tfstate entre prod et staging — un apply erroné sur le mauvais état ne crasherait que cette env.
Convention de nommage
| Élément | Convention | Exemple |
|---|---|---|
| Ressource | <provider><type> | digitalocean_droplet.web |
| Variable | snake_case descriptif | droplet_size |
| Output | descriptif | web_ip, db_connection |
| Module | verbe-nom si action | create_network, deploy_app |
| Dossiers projets | environnement | prod/, staging/ |
Commentaires dans le code
Utiliser # (pas // qui n’est pas valide en HCL) pour expliquer le pourquoi, pas le quoi.
# Database MySQL pour l'environnement staging.
# Remplacée par Postgres en production.
resource "digitalocean_database_cluster" "staging_db" {
name = "staging-db"
engine = "pg"
version = "14"
size = "db-s-1vcpu-1gb"
node_count = 1
}Gravité des commandes
Garder la main sur le keyboard pendant un apply. Vérifier le plan. Un terraform plan visible sur un écran séparé avant d’appuyer Entrée sauve des heures de récupération.
Pitfalls
| Problème | Cause | Solution |
|---|---|---|
Error: object has been moved | Migration de provider ou renommage | terraform state mv old.name new.name |
| Appliquer sans plan visible | Bêtise ou script | Toujours terraform plan d’abord, ou terraform apply -auto-approve uniquement dans un pipeline |
Secrets dans le .tfstate | Variable non déclarée sensitive = true | Ajouter sensitive = true sur les variables secret |
| Corrompre l’état avec 2 terminals | Write concurrent sans backend avec locking | Utiliser S3+DynamoDB, GCS, ou Terraform Cloud |
| Import sans écrire le code HCL | Confusion entre état et code | Importer = mettre dans l’état. Écrire le .tf = avoir le code. Les deux sont nécessaires. |
.terraform et .tfstate commités | Oubli dans .gitignore | Ajouter .tfstate, .terraform/, *.tfplan au .gitignore |
| Upgrade Terraform qui casse la config | Changement de version du provider | Lancer terraform init -upgrade après un bump de version |
terraform apply sur le mauvais dossier | Ressources à environnements mélangées | Séparer les états par dossier d’environnement |