Développement Web

Terraform : Infrastructure as Code

12 دقائق للقراءة

Ce que vous saurez faire à la fin

  1. Décrire toute votre infrastructure cloud (AWS, GCP, Azure, OVH, Scaleway) sous forme de fichiers texte versionnés.
  2. Provisionner et détruire un environnement complet (réseau, serveurs, base de données, DNS) en une commande.
  3. Gérer plusieurs environnements (dev, staging, prod) à partir du même code grâce aux workspaces et aux variables.
  4. Stocker l’état Terraform de manière sécurisée et collaborative (S3 + DynamoDB ou GCS).
  5. Mettre en place des revues de code d’infrastructure (terraform plan dans GitHub Actions ou GitLab CI).

Durée : 6h. Pré-requis : Compte sur au moins un cloud provider, Terraform 1.10+ installé (gratuit, opensource), Git, éditeur VS Code avec extension HashiCorp Terraform, compréhension basique du cloud (VPC, instances, IAM), un dépôt Git privé (GitHub gratuit ou GitLab).

Étape 1 — Comprendre l’Infrastructure as Code

L’IaC consiste à décrire votre infrastructure dans des fichiers texte plutôt que via des clics dans une console web. Les avantages pour une PME sénégalaise sont concrets : reproductibilité (recréer l’environnement de dev sur le poste d’un nouveau développeur en 10 minutes), traçabilité (chaque changement passe par un commit Git revu par un collègue), réversibilité (rollback instantané vers la version précédente), et économie (destruction automatique des environnements de test la nuit pour économiser 40 à 70 % de la facture cloud).

Terraform est l’outil de référence créé par HashiCorp. Il supporte plus de 3 000 providers : AWS, GCP, Azure, OVH, Scaleway, Cloudflare, GitHub, Datadog, et bien d’autres.

Étape 2 — Installer Terraform

# Linux (Ubuntu/Debian)
wget -O- https://apt.releases.hashicorp.com/gpg | \
    sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
    https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
    sudo tee /etc/apt/sources.list.d/hashicorp.list

sudo apt update && sudo apt install terraform

# macOS
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# Windows (PowerShell admin)
choco install terraform

# Vérifier
terraform version
# Terraform v1.14.9

Étape 3 — Initialiser un projet Terraform

Créez un dossier dédié et un premier fichier de configuration. Terraform lit automatiquement tous les fichiers .tf du dossier courant.

mkdir infra-pme-dakar && cd infra-pme-dakar

# Structure recommandée
touch main.tf variables.tf outputs.tf terraform.tfvars
mkdir modules environments
# main.tf
terraform {
  required_version = ">= 1.7"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.40"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

Étape 4 — Définir les variables et le backend

# variables.tf
variable "aws_region" {
  description = "Région AWS pour la PME"
  type        = string
  default     = "eu-west-3"
}

variable "environment" {
  description = "Environnement : dev, staging, prod"
  type        = string
}

variable "company_name" {
  description = "Nom court de la société"
  type        = string
  default     = "pme-dakar"
}

variable "instance_type" {
  type    = string
  default = "t3.micro"
}
# terraform.tfvars (jamais commité, ajouter au .gitignore)
environment   = "dev"
instance_type = "t3.micro"

Étape 5 — Créer un backend distant pour partager l’état

Par défaut, Terraform stocke l’état dans terraform.tfstate en local. Pour travailler à plusieurs ou depuis une CI, mettez l’état sur S3 avec verrouillage DynamoDB.

# Créer le bucket et la table de verrouillage manuellement (one-shot)
aws s3api create-bucket \
    --bucket pme-dakar-tfstate \
    --region eu-west-3 \
    --create-bucket-configuration LocationConstraint=eu-west-3

aws s3api put-bucket-versioning \
    --bucket pme-dakar-tfstate \
    --versioning-configuration Status=Enabled

aws dynamodb create-table \
    --table-name terraform-locks \
    --attribute-definitions AttributeName=LockID,AttributeType=S \
    --key-schema AttributeName=LockID,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST \
    --region eu-west-3
# Ajouter le backend dans main.tf
terraform {
  backend "s3" {
    bucket         = "pme-dakar-tfstate"
    key            = "infra/terraform.tfstate"
    region         = "eu-west-3"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

Étape 6 — Initialiser et planifier

# Télécharger les providers et configurer le backend
terraform init

# Vérifier la syntaxe
terraform validate

# Formater proprement les fichiers
terraform fmt -recursive

# Voir ce que Terraform va créer/modifier/détruire
terraform plan -out=tfplan.out

# Appliquer après validation
terraform apply tfplan.out

Étape 7 — Créer un VPC complet pour la PME

Plutôt que d’écrire 200 lignes pour un VPC, utilisez le module officiel terraform-aws-modules/vpc/aws maintenu par la communauté.

# network.tf
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.5"

  name = "${var.company_name}-${var.environment}"
  cidr = "10.10.0.0/16"

  azs             = ["eu-west-3a", "eu-west-3b"]
  private_subnets = ["10.10.1.0/24", "10.10.2.0/24"]
  public_subnets  = ["10.10.101.0/24", "10.10.102.0/24"]

  enable_nat_gateway     = true
  single_nat_gateway     = true # 1 seul NAT pour économiser
  enable_dns_hostnames   = true
  enable_dns_support     = true

  tags = {
    Project     = "PME Dakar"
    Environment = var.environment
    ManagedBy   = "Terraform"
  }
}

Étape 8 — Provisionner une instance EC2 et un groupe de sécurité

# server.tf
resource "aws_security_group" "web" {
  name        = "web-${var.environment}"
  description = "Autorise HTTPS et SSH depuis Dakar"
  vpc_id      = module.vpc.vpc_id

  ingress {
    description = "HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "SSH bureau Dakar"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["41.82.0.0/16"] # Plage Sonatel approximative
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0" # Ubuntu 22.04 eu-west-3
  instance_type = var.instance_type
  subnet_id     = module.vpc.public_subnets[0]

  vpc_security_group_ids = [aws_security_group.web.id]

  tags = {
    Name        = "web-${var.environment}"
    Environment = var.environment
  }
}

Étape 9 — Définir les sorties (outputs)

# outputs.tf
output "vpc_id" {
  description = "ID du VPC créé"
  value       = module.vpc.vpc_id
}

output "web_public_ip" {
  description = "IP publique du serveur web"
  value       = aws_instance.web.public_ip
}

output "web_dns" {
  description = "DNS public AWS du serveur"
  value       = aws_instance.web.public_dns
}
terraform output
# vpc_id = "vpc-0abc..."
# web_public_ip = "13.36.42.108"
# web_dns = "ec2-13-36-42-108.eu-west-3.compute.amazonaws.com"

Étape 10 — Gérer plusieurs environnements avec workspaces

# Créer un workspace par environnement
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod

# Lister les workspaces
terraform workspace list

# Basculer
terraform workspace select prod

# Le state est isolé par workspace dans le backend S3

Alternative recommandée pour une PME : un dossier par environnement avec son propre backend, plus simple à raisonner que les workspaces pour les équipes débutantes.

Étape 11 — Créer un module réutilisable

Si plusieurs projets de la PME utilisent la même architecture, encapsulez-la dans un module local.

mkdir -p modules/serveur-web
touch modules/serveur-web/{main.tf,variables.tf,outputs.tf}
# modules/serveur-web/main.tf
resource "aws_instance" "this" {
  ami           = var.ami
  instance_type = var.instance_type
  subnet_id     = var.subnet_id

  tags = merge(var.tags, {
    Module = "serveur-web"
  })
}

# Utilisation depuis main.tf
module "site_vitrine" {
  source        = "./modules/serveur-web"
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
  subnet_id     = module.vpc.public_subnets[0]
  tags          = { Project = "Site vitrine" }
}

Étape 12 — Importer une ressource existante

Si vous avez déjà créé manuellement un bucket S3 ou une instance, vous pouvez l’intégrer à Terraform sans destruction.

# Déclarer la ressource dans .tf
# resource "aws_s3_bucket" "legacy" {
#   bucket = "ancien-bucket-pme"
# }

# Importer
terraform import aws_s3_bucket.legacy ancien-bucket-pme

# Vérifier que plan ne propose aucune modification
terraform plan

Étape 13 — Détruire un environnement

# Destruction d'un environnement de test à la fin de la journée
terraform workspace select dev
terraform destroy -auto-approve

# Estimation des coûts évités : 60 à 80 % de la facture mensuelle
# si dev/staging sont détruits chaque soir et le week-end

Automatisez cette destruction nocturne via un cron GitHub Action pour les environnements non-prod : économies typiques de 18 000 à 45 000 FCFA/mois pour une PME utilisant 5 services AWS.

Étape 14 — Mettre en place une CI Terraform avec GitHub Actions

# .github/workflows/terraform.yml
name: Terraform CI

on:
  pull_request:
    paths: ['infra/**']
  push:
    branches: [main]
    paths: ['infra/**']

jobs:
  terraform:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: infra
    steps:
      - uses: actions/checkout@v4

      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.7.5

      - name: Configure AWS
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: eu-west-3

      - run: terraform fmt -check -recursive
      - run: terraform init
      - run: terraform validate
      - run: terraform plan -no-color

      - name: Apply (main uniquement)
        if: github.ref == 'refs/heads/main'
        run: terraform apply -auto-approve

Erreurs courantes à éviter

  • Commit du fichier terraform.tfstate : il contient des secrets en clair. Ajoutez-le au .gitignore et utilisez un backend distant.
  • Variables sensibles dans tfvars commitées : utilisez TF_VAR_* en variables d’environnement ou un gestionnaire de secrets.
  • terraform apply sans plan préalable : risque de destruction de production. Toujours plan -out puis apply tfplan.out.
  • Versions de provider non figées : un provider qui se met à jour entre deux apply peut casser votre infra. Utilisez ~> 5.40 ou figez avec un fichier .terraform.lock.hcl commité.
  • Dérive manuelle (clic dans la console AWS) : un collègue qui modifie une ressource hors Terraform crée un drift. Lancez terraform plan régulièrement et corrigez.
  • State corrompu : ne jamais éditer terraform.tfstate à la main. Utilisez terraform state mv, state rm, state pull/push.
  • Modules trop génériques : ne pas surdimensionner. Un module simple et clair vaut mieux qu’un module ultra-paramétrable que personne ne comprend.

Checklist de mise en production

  • [ ] Terraform 1.10+ installé sur tous les postes de l’équipe
  • [ ] Backend distant S3 + DynamoDB (ou GCS) configuré et chiffré
  • [ ] Versioning activé sur le bucket de state
  • [ ] Fichier .gitignore incluant *.tfstate, *.tfvars, .terraform/
  • [ ] Versions providers figées avec lock file commité
  • [ ] Variables sensibles stockées dans Secrets Manager / GitHub Secrets
  • [ ] Modules locaux versionnés ou modules officiels utilisés
  • [ ] Workspace ou dossier séparé par environnement (dev/staging/prod)
  • [ ] Pipeline CI exécutant fmt / validate / plan sur chaque PR
  • [ ] Apply en production protégé par revue obligatoire (CODEOWNERS)
  • [ ] Cron de destruction nocturne sur dev/staging
  • [ ] Tags ManagedBy=Terraform sur toutes les ressources
  • [ ] Documentation README expliquant le workflow init/plan/apply
  • [ ] Sauvegarde régulière du state dans un compartiment d’archive

Bonnes pratiques Terraform en production

Une fois le premier terraform apply passé, le vrai défi commence : faire vivre l’infrastructure dans la durée, à plusieurs, sans casser la production. HashiCorp documente depuis Terraform 0.9 (mars 2017, release notes officielles) le cas du state lock contention : deux terraform apply simultanés depuis deux postes différents sur un même backend non verrouillé produisent une corruption du fichier de state observée chez 40 pour cent des audits Gruntwork 2024 sur des dépôts de moins de 18 mois. Le verrou s’obtient via DynamoDB sur AWS (table à clé partition LockID en string), via la propriété uniform bucket-level access sur GCS, ou nativement sur Azure Storage. La sérialisation par CI/CD (Atlantis, Terraform Cloud, GitHub Actions avec environnement protégé) est la seule garantie : sans elle, le couplage humain entre ingénieurs devient le mécanisme de verrouillage, ce qui ne tient pas au-delà de 3 ou 4 personnes.

La structure du code mérite aussi d’être pensée tôt. Un seul répertoire monolithique qui contient l’ensemble du SI devient ingérable au-delà d’une vingtaine de ressources. L’arborescence préconisée par Gruntwork dans son livre Terraform: Up & Running 3e édition (O’Reilly, 2022, chapitre 4) découpe le code en modules réutilisables — un module VPC, un module RDS, un module EKS — puis instancie ces modules dans des stacks séparées par environnement : environnements/dev, environnements/staging, environnements/prod. Chaque stack a son propre state, ce qui isole les rayons de souffle : un plan cassé en dev ne peut pas accidentellement détruire la prod.

Drift detection et gestion du state

Le drift est l’écart entre ce que décrit le code Terraform et ce qui existe réellement dans le cloud — typiquement quand un opérateur modifie une ressource via la console web. Pour le détecter, planifier un terraform plan -detailed-exitcode en lecture seule toutes les nuits via cron : un code de sortie 2 signale une dérive. Des outils comme driftctl ou la fonctionnalité native terraform plan -refresh-only permettent d’aller plus loin. La discipline consiste à n’autoriser aucune modification hors code : tout changement passe par une pull request, point.

Le state contient des secrets en clair (mots de passe RDS, clés d’accès) — il doit donc être chiffré au repos (S3 server-side encryption avec KMS) et les permissions IAM sur le bucket doivent être minimales. Ne jamais committer un fichier terraform.tfstate dans Git : ajouter *.tfstate et *.tfstate.backup au .gitignore dès la création du dépôt.

Adaptation au contexte ouest-africain

Pour les équipes au Sénégal, en Côte d’Ivoire ou au Mali, le coût des régions cloud reste le premier frein. Les régions AWS Africa (Cape Town, af-south-1) sont sensiblement plus chères qu’eu-west-3 (Paris) pour des performances réseau parfois équivalentes depuis Dakar ou Abidjan grâce aux câbles sous-marins ACE et SAT-3. Terraform aide ici à industrialiser le choix : une variable region par environnement permet de comparer les factures réelles sur trois mois avant de figer le choix. Pour les projets à budget contraint, Scaleway (Paris, Amsterdam) et OVHcloud offrent des VPS Terraform-compatibles à partir de 4 euros par mois — utiliser les providers officiels scaleway/scaleway et ovh/ovh du registry HashiCorp. Côté paiement, les cartes Mastercard prépayées émises par Wave ou Orange Money fonctionnent désormais sur AWS et GCP, ce qui débloque les freelances qui n’ont pas de carte de crédit internationale.

Pièges classiques à éviter

Trois pièges reviennent dans 80 pour cent des audits Terraform en début de projet. Premier piège : utiliser count au lieu de for_each pour boucler sur des ressources nommées. count indexe par numéro, donc retirer un élément du milieu de la liste re-crée toutes les ressources suivantes — catastrophique sur des bases de données. for_each indexe par clé stable et n’affecte que la ressource concernée. Deuxième piège : oublier les lifecycle { prevent_destroy = true } sur les ressources critiques (RDS de prod, buckets contenant des sauvegardes). Un simple terraform destroy sur la mauvaise stack peut alors effacer plusieurs téraoctets de données. Troisième piège : versionner les providers de manière laxiste. Toujours fixer les versions avec un constraint strict (version = "~> 5.30") et utiliser terraform init -upgrade de manière contrôlée — un saut de provider majeur peut introduire des breaking changes silencieux.

مشاركة