Skip to Content
DevOpsTerraform — fondamentaux, état, modules

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 : initplanapplydestroy. L’état (state) est le pont entre le fichier .tf et 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 init

terraform 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" >> .gitignore

Dé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) :

FichierRôle
main.tfRessources principales
variables.tfDéclarations variable
outputs.tfDéclarations output
terraform.tfvarsValeurs par défaut des variables
prod.tfvarsValeurs spécifiques à un environnement
versions.tfBloc terraform (contraintes de version)

⚠️ Terraform fusionne tous les .tf d’un dossier, peu importe le nom. Ne jamais scinder une ressource sur deux fichiers — resource complet 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.out

terraform 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.tfvars

Terraform 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 destroy supprime 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 refresh

Lit 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 rm retire 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.tf

modules/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 12345678

Aprè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émentConventionExemple
Ressource&lt;provider&gt;&lt;type&gt;digitalocean_droplet.web
Variablesnake_case descriptifdroplet_size
Outputdescriptifweb_ip, db_connection
Moduleverbe-nom si actioncreate_network, deploy_app
Dossiers projetsenvironnementprod/, 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èmeCauseSolution
Error: object has been movedMigration de provider ou renommageterraform state mv old.name new.name
Appliquer sans plan visibleBêtise ou scriptToujours terraform plan d’abord, ou terraform apply -auto-approve uniquement dans un pipeline
Secrets dans le .tfstateVariable non déclarée sensitive = trueAjouter sensitive = true sur les variables secret
Corrompre l’état avec 2 terminalsWrite concurrent sans backend avec lockingUtiliser S3+DynamoDB, GCS, ou Terraform Cloud
Import sans écrire le code HCLConfusion entre état et codeImporter = mettre dans l’état. Écrire le .tf = avoir le code. Les deux sont nécessaires.
.terraform et .tfstate commitésOubli dans .gitignoreAjouter .tfstate, .terraform/, *.tfplan au .gitignore
Upgrade Terraform qui casse la configChangement de version du providerLancer terraform init -upgrade après un bump de version
terraform apply sur le mauvais dossierRessources à environnements mélangéesSéparer les états par dossier d’environnement

Ressources