Skip to content
Learni
Voir tous les tutoriels
Infrastructure as Code

Comment orchestrer une infra multi-cloud avec Terraform en 2026

Read in English

Introduction

Terraform, l'outil open-source d'HashiCorp pour l'Infrastructure as Code (IaC), reste en 2026 le standard pour orchestrer des environnements complexes et multi-cloud. Contrairement aux scripts manuels, Terraform applique une approche déclarative : vous décrivez l'état désiré dans des fichiers HCL, et il gère les diffs via plan et apply.

Pourquoi ce tutoriel avancé ? Les débutants se contentent d'un main.tf basique, mais en prod, vous gérez des modules réutilisables, des states distants pour la collaboration, des workspaces pour dev/staging/prod, et des providers multiples (AWS + GCP ici). Nous couvrons ces patterns avec du code 100% fonctionnel sur une infra VPC/EC2 (AWS) + VPC/VM (GCP), incluant data sources, for_each, et remote backend S3.

À la fin, vous bookmarkederez ce guide pour vos déploiements scalables : zéro downtime, audits traçables, et conformité cloud-native. Temps estimé : 45 min pour un setup complet (128 mots).

Prérequis

  • Terraform CLI v1.9+ installé (terraform version)
  • Comptes AWS et GCP actifs avec IAM/Service Account (clés API)
  • AWS CLI configuré + bucket S3 pour state distant
  • GCP CLI (gcloud auth login)
  • Éditeur VS Code avec extension HashiCorp Terraform
  • Connaissances IaC intermédiaires (providers, resources)

Initialisation du projet et providers

main.tf
terraform {
  required_version = ">= 1.9.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
  }
  backend "s3" {
    bucket         = "your-terraform-state-bucket"
    key            = "multi-cloud/terraform.tfstate"
    region         = "eu-west-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

provider "aws" {
  region = var.aws_region
}

provider "google" {
  project = var.gcp_project_id
  region  = var.gcp_region
}

Ce fichier initialise Terraform avec providers AWS/GCP pinned en version, et un backend S3 pour state distant (avec DynamoDB pour locks). Remplacez your-terraform-state-bucket par votre bucket. Piège : Oublier encrypt=true expose les states ; utilisez terraform init après pour migrer local vers remote.

Définition des variables d'entrée

Avant les ressources, définissons des variables réutilisables pour les environnements. Cela permet de passer dev, prod via CLI ou TF_VARs, évitant les hardcodings.

Variables et outputs

variables.tf
variable "aws_region" {
  description = "Région AWS"
  type        = string
  default     = "eu-west-1"
}

variable "gcp_project_id" {
  description = "ID projet GCP"
  type        = string
}

variable "gcp_region" {
  description = "Région GCP"
  type        = string
  default     = "europe-west1"
}

variable "environment" {
  description = "Environnement (dev/prod)"
  type        = string
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environnement doit être dev, staging ou prod."
  }
}

output "aws_vpc_id" {
  description = "ID VPC AWS créée"
  value       = module.aws_vpc.vpc_id
}

output "gcp_network_name" {
  description = "Nom réseau GCP"
  value       = module.gcp_vpc.network_name
}

Variables avec validation bloquent les inputs invalides (ex: terraform plan -var='environment=foo' échoue). Outputs exposent les IDs pour chaining. Avancé : Utilisez default pour dev, mais forcez via -var en prod pour sécurité.

Création d'un module VPC AWS réutilisable

Passons aux modules : encapsulez la logique VPC en sous-dossier pour réutilisation. Ici, un module AWS crée VPC + subnets avec for_each pour haute disponibilité.

Module AWS VPC

modules/aws_vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
  tags = {
    Name        = "vpc-${var.environment}
    Environment = var.environment
  }
}

resource "aws_subnet" "public" {
  for_each = toset(var.public_subnet_cidrs)

  vpc_id                  = aws_vpc.main.id
  cidr_block              = each.value
  availability_zone       = each.key
  map_public_ip_on_launch = true

  tags = {
    Name        = "public-${each.key}-${var.environment}
    Environment = var.environment
  }
}

resource "aws_internet_gateway" "gw" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "igw-${var.environment}
  }
}

for_each itère sur AZs/CIDRs dynamiquement (clé=valeur), scalable vs count. Ajoutez variables.tf dans module : variable "public_subnet_cidrs" { type = map(string) }. Appel : module "aws_vpc" { source = "./modules/aws_vpc" ... }. Piège : CIDRs overlap causent erreurs plan.

Appel module AWS et instance EC2

aws.tf
module "aws_vpc" {
  source = "./modules/aws_vpc"

  environment           = var.environment
  public_subnet_cidrs = {
    "a" = "10.0.1.0/24"
    "b" = "10.0.2.0/24"
    "c" = "10.0.3.0/24"
  }
}

resource "aws_instance" "web" {
  for_each = module.aws_vpc.public_subnet_ids

  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t3.micro"
  subnet_id     = each.value
  vpc_security_group_ids = [aws_security_group.web.id]

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

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

resource "aws_security_group" "web" {
  vpc_id = module.aws_vpc.vpc_id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

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

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

Data source fetch AMI dynamique (évite hardcode). for_each sur subnets module output (ajoutez output "public_subnet_ids" { value = {for k,v in aws_subnet.public : k => v.id} }). SG basique pour HTTP. Piège : Oublier data source = AMI introuvable.

Module GCP équivalent et data sources

Symétrique pour GCP : module VPC + VM. Utilisez data sources pour fetch zones existantes, rendant le module portable.

Module GCP VPC et appel

gcp.tf
module "gcp_vpc" {
  source = "./modules/gcp_vpc"

  environment = var.environment
  project_id  = var.gcp_project_id
}

resource "google_compute_instance" "web" {
  for_each = data.google_compute_zones.available.names

  name         = "web-${each.key}-${var.environment}
  machine_type = "e2-micro"
  zone         = each.value

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-11"
    }
  }

  network_interface {
    network = module.gcp_vpc.network_name
    access_config {}
  }

  tags = ["http-server", "${var.environment}"]
}

Data google_compute_zones (définie plus bas) pour HA multi-zone. Instance Debian avec tag firewall. Créez modules/gcp_vpc/main.tf similaire : VPC 10.0.0.0/16, subnet, output network_name. Piège : Tags manquants = firewall KO.

Data sources GCP et script init

data.tf
data "google_compute_zones" "available" {
  region = var.gcp_region
  status = "UP"
}

resource "google_compute_firewall" "web" {
  name    = "allow-http-${var.environment}
  network = module.gcp_vpc.network_name

  allow {
    protocol = "tcp"
    ports    = ["80"]
  }

  source_ranges = ["0.0.0.0/0"]
  target_tags   = ["http-server"]
}

Data zones filtrées UP pour résilience. Firewall par tags. Ajoutez à modules/gcp_vpc/outputs.tf : output "network_name" { value = google_compute_network.main.name }. Fonctionnel : terraform apply expose HTTP.

Workspaces et apply bash

deploy.sh
#!/bin/bash

ENV=${1:-dev}

terraform workspace new ${ENV} || terraform workspace select ${ENV}

terraform init \
  -backend-config="bucket=your-terraform-state-bucket" \
  -backend-config="key=multi-cloud/${ENV}/terraform.tfstate" \
  -backend-config="region=eu-west-1"

terraform plan -var="environment=${ENV}" \
  -var="gcp_project_id=your-gcp-project"

terraform apply -auto-approve -var="environment=${ENV}" \
  -var="gcp_project_id=your-gcp-project"

terraform output -json > outputs-${ENV}.json

echo "Infra ${ENV} déployée ! Outputs dans outputs-${ENV}.json"

Script bash pour workspaces par env (isole states). backend-config override key par env. Lancez ./deploy.sh prod. Piège : Sans -auto-approve, review manuelle ; toujours plan avant.

Bonnes pratiques

  • Modularisez tout : Un main.tf < 300 lignes ; source Git pour modules privés.
  • State remote obligatoire : S3 + DynamoDB en team ; terraform state mv pour migrations.
  • Variables validées + secrets : Utilisez sensitive=true, Terraform Cloud Vault ou AWS SSM.
  • Plan en CI/CD : GitHub Actions avec terraform plan -out=tfplan + apply sur merge.
  • Destroy safe : terraform destroy -target=module.xxx pour rollback granulaire.

Erreurs courantes à éviter

  • State drift : Ne modifiez jamais manuellement en console cloud ; terraform refresh avant plan.
  • Provider version drift : Pinnez ~> 5.0 ; terraform providers lock génère .terraform.lock.hcl.
  • Cycles de dépendances : Utilisez depends_on explicitement si implicit échoue.
  • Workspaces oubliés : terraform workspace list ; delete avec rm -rf .terraform si corrompu.

Pour aller plus loin