DevOps Elastic Hayway
Document

SUBSCRIBE TO GET FULL ACCESS TO THE E-BOOKS FOR FREE 🎁SUBSCRIBE NOW

Professional Dropdown with Icon

SUBSCRIBE NOW TO GET FREE ACCESS TO EBOOKS

TerraformLesson 24 / 2414 min readUpdated September 13, 2026

VPC Terraform : à la main puis module

À la fin de ce tutoriel, vous saurez déclarer un VPC à la main (aws_vpc, subnets public/privé 2 AZ, IGW, route tables, SG), comprendre le coût du NAT Gateway (désactivé en lab), puis utiliser le module terraform-aws-modules/vpc/aws versionné — inputs clés et quand préférer le module. Region ca-central-1, Terraform ≥ 1.9, AWS provider ~> 5.0. Message clé : d’abord le réseau à la main, puis factorisez ; NAT = coût élevé → lab sans NAT + destroy immédiat.

Niveau : Intermédiaire · Temps estimé : 45–55 min (lecture) / +20–35 min si apply · Versions testées : Terraform ≥ 1.9, AWS provider ~> 5.0 · Dernière vérification : 2026-09-10

Slug proposé : terraform-vpc · Série : Terraform · Remplace / fusionne : #1704 (VPC), #1709 (VPC without module), #1714 (VpcModule) — deux parties dans un seul article

Prérequis

  • RDS (terraform-rds) : subnet group, SG, coûts / destroy
  • Idéalement Modules (terraform-modules-bases) et Modules Registry (terraform-modules-registry)
  • Terraform ≥ 1.9, AWS CLI v2, profil lab (aws sts get-caller-identity OK)
  • Notions CIDR / AZ (aws-vpc-sous-reseaux-igw)

Coût estimé : VPC + subnets + IGW ≈ 0 €. NAT Gateway = coût élevé — désactivé en lab. Apply → destroy immédiat. Aucun secret dans le HCL.

Auth sans secrets. AWS_PROFILE / SSO uniquement — jamais de clés dans le provider.

Ce que nous allons construire

Lab terraform vpc (ca-central-1) — NAT OFF
  ├── Partie A : VPC + 2 public + 2 private (2 AZ) + IGW + RT + SG
  ├── Partie B : module terraform-aws-modules/vpc/aws (version pinée)
  └── Lab plan / destroy · checklist · quiz

(Alt : « Terraform VPC : public/privé 2 AZ, IGW, module vpc/aws — ca-central-1 ».)

Fusion #1704, #1709, #1714 en deux parties. Focus : terraform vpc provider 5.x, coût NAT, pont ALB / Autoscaling / RDS.

Partie A — VPC Terraform à la main

Étape 1 — Pourquoi un VPC custom

Le VPC default d’un compte AWS est pratique pour un premier terraform apply, mais il mélange souvent labs, outils personnels et ressources oubliées dans le même CIDR. En équipe, cela complique les Security Groups, les Network ACL et les audits. Un VPC dédié pour chaque environnement (lab, staging, prod) vous donne un périmètre clair : vous savez exactement quelles routes mènent à Internet, quels subnets accueillent l’ALB, et lesquels restent privés pour RDS ou les workers.

Dans un cas réel DevOps (pipeline CI qui déploie une API derrière un ALB + ASG), le modèle mental est toujours le même : public = entrée Internet contrôlée, privé = calcul et données. Terraform ne « invente » pas cette topologie : il la rend reproductible. Si vous codez d’abord le VPC à la main (Partie A), vous comprendrez chaque association de route table ; le module Registry (Partie B) ne sera plus une boîte noire.

Piège prod : réutiliser le VPC default « temporairement » puis y brancher RDS et un NAT. Six mois plus tard, personne n’ose le détruire. Budgétez le réseau dès le premier sprint et taguez Environment / Owner sur chaque subnet.

Le VPC default suffit pour des labs rapides. Pour terraform vpc et la prod, créez un réseau dédié : CIDR isolé, subnets publics (ALB) et privés (apps, RDS), RT explicites, SG least privilege. Deux AZ = socle HA (ALB, ASG, DB subnet group).

Élément Rôle Ressource
VPC Conteneur réseau aws_vpc
Subnet public / privé IGW vs pas d’IGW (+ NAT coûteux) aws_subnet + RT
Internet Gateway Sortie Internet des publics aws_internet_gateway
NAT Gateway Egress des privés aws_nat_gateway + EIP — cher
Route table / SG Routage + filtrage L4 aws_route_table / aws_security_group

Règle P2 : lab = pas de NAT. Les privés n’ont pas besoin d’egress pour un exercice réseau ; en prod, NAT ou endpoints VPC se budgètent.

Étape 2 — Provider, VPC, subnets 2 AZ

Avant de coller le HCL, fixez trois décisions d’adressage : la taille du CIDR VPC (souvent /16 en lab pour laisser de la marge), le découpage public/privé (ici /24 via cidrsubnet), et le nombre d’AZ. En ca-central-1, deux AZ suffisent pour un lab HA minimal et pour un DB subnet group RDS. Évitez de hardcoder ca-central-1a / 1b : la data source aws_availability_zones suit les AZ réellement disponibles pour votre compte.

Le flag map_public_ip_on_launch = true sur les subnets publics uniquement évite une erreur classique : une instance « privée » qui reçoit quand même une IP publique parce qu’elle a été lancée dans le mauvais tier. Vérifiez toujours le tag Tier et la route table associée avant de brancher un ASG.

# versions.tf + providers.tf
terraform {
  required_version = ">= 1.9.0"
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
}
provider "aws" {
  region = "ca-central-1" # AWS_PROFILE / SSO — jamais de clés
}

variable "project" { type = string }
variable "vpc_cidr" {
  type    = string
  default = "10.42.0.0/16"
}
variable "enable_nat_gateway" {
  type    = bool
  default = false # NAT = coût élevé
}

data "aws_availability_zones" "available" { state = "available" }
locals {
  azs           = slice(data.aws_availability_zones.available.names, 0, 2)
  public_cidrs  = [cidrsubnet(var.vpc_cidr, 8, 0), cidrsubnet(var.vpc_cidr, 8, 1)]
  private_cidrs = [cidrsubnet(var.vpc_cidr, 8, 10), cidrsubnet(var.vpc_cidr, 8, 11)]
}

resource "aws_vpc" "lab" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true
  tags = { Name = "${var.project}-vpc", Lab = "terraform-vpc", Region = "ca-central-1" }
}

resource "aws_subnet" "public" {
  count                   = 2
  vpc_id                  = aws_vpc.lab.id
  cidr_block              = local.public_cidrs[count.index]
  availability_zone       = local.azs[count.index]
  map_public_ip_on_launch = true
  tags = { Name = "${var.project}-public-${local.azs[count.index]}", Tier = "public" }
}

resource "aws_subnet" "private" {
  count             = 2
  vpc_id            = aws_vpc.lab.id
  cidr_block        = local.private_cidrs[count.index]
  availability_zone = local.azs[count.index]
  tags = { Name = "${var.project}-private-${local.azs[count.index]}", Tier = "private" }
}

Points durs : cidrsubnet ; count = 2 + data AZ ; map_public_ip_on_launch sur les publics seulement. Branchez ensuite ALB (publics), ASG et RDS (terraform-rds) en privé.

Étape 3 — IGW, route tables, NAT optionnel (coût !)

L’Internet Gateway n’est pas « magique » : sans route 0.0.0.0/0 vers l’IGW et association de la route table aux subnets publics, vos instances n’atteignent pas Internet même si elles ont une IP publique. Les subnets privés du lab n’ont volontairement pas de route Internet : cela force à comprendre la différence public/privé sans payer un NAT Gateway.

En production, le choix NAT vs VPC endpoints (S3, ECR, Secrets Manager, etc.) est un arbitrage coût / simplicité. Un NAT multi-AZ coûte cher 24×7 ; des endpoints d’interface aussi, mais ciblés. Pour ce tutoriel P2, gardez enable_nat_gateway = false et documentez dans le README du repo quand le NAT devient nécessaire (egress packagers, appels API tiers depuis le privé).

resource "aws_internet_gateway" "lab" {
  vpc_id = aws_vpc.lab.id
  tags   = { Name = "${var.project}-igw", Lab = "terraform-vpc" }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.lab.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.lab.id
  }
  tags = { Name = "${var.project}-rt-public" }
}

resource "aws_route_table_association" "public" {
  count          = 2
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

# Privés lab : pas de route Internet (prod = NAT / endpoints)
resource "aws_route_table" "private" {
  vpc_id = aws_vpc.lab.id
  tags   = { Name = "${var.project}-rt-private" }
}
resource "aws_route_table_association" "private" {
  count          = 2
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private.id
}
# NAT optionnel (coût !) : aws_eip + aws_nat_gateway si enable_nat_gateway
Composant Lab P2 Prod
IGW Oui Oui
NAT Gateway false 1/AZ ou endpoints (coût)
Route privée 0.0.0.0/0 Absente Via NAT / firewall

Alerte coût : NAT = horaire + EIP + data → lab enable_nat_gateway = false ; test = destroy immédiat.

Étape 4 — Security Group basique + outputs

Le Security Group de lab ouvre HTTP depuis le CIDR du VPC uniquement : c’est volontairement plus strict que 0.0.0.0/0. En prod, vous séparerez typiquement un SG ALB (80/443 depuis Internet ou CloudFront) et un SG app (port applicatif uniquement depuis le SG ALB). Les outputs public_subnet_ids / private_subnet_ids / web_sg_id existent pour enchaîner sans copier-coller des IDs fragiles entre modules.

resource "aws_security_group" "lab_web" {
  name        = "${var.project}-web-sg"
  description = "Lab P2 : HTTP depuis CIDR VPC"
  vpc_id      = aws_vpc.lab.id
  ingress {
    from_port = 80
    to_port   = 80
    protocol  = "tcp"
    cidr_blocks = ["10.42.0.0/16"]
  }
  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = { Name = "${var.project}-web-sg", Lab = "terraform-vpc" }
}

output "vpc_id"             { value = aws_vpc.lab.id }
output "public_subnet_ids"  { value = aws_subnet.public[*].id }
output "private_subnet_ids" { value = aws_subnet.private[*].id }
output "web_sg_id"          { value = aws_security_group.lab_web.id }

Ensuite : ALB (terraform-alb) sur publics, RDS sur privés, Autoscaling sur le tier app. En prod : SG ALB distinct ; cibles = SG ALB uniquement.

Partie B — Module terraform-aws-modules/vpc/aws

Étape 5 — Même réseau via le module versionné

Passer au module terraform-aws-modules/vpc/aws n’efface pas ce que vous avez appris : il encapsule les mêmes briques (VPC, subnets, IGW, NAT optionnel, tags). Le gain apparaît dès que vous répétez le pattern sur trois comptes ou trois workspaces. Pinez toujours version : un latest implicite peut changer le comportement NAT ou les tags EKS lors d’un terraform init -upgrade imprévu.

Ne mélangez pas Partie A et Partie B dans le même state : vous créeriez deux VPC (coût + confusion CIDR). Choisissez un chemin, migrez explicitement si besoin (import / recreate planifié).

Le HCL nu est pédagogique ; le répéter partout est du bruit. Le module terraform-aws-modules/vpc/aws encapsule VPC, subnets, IGW, NAT, tags. Pinnez la version.

# module-vpc.tf — Partie B (NAT OFF)
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0" # pinnez la version

  name = "${var.project}-vpc"
  cidr = var.vpc_cidr
  azs             = local.azs
  public_subnets  = local.public_cidrs
  private_subnets = local.private_cidrs

  enable_nat_gateway      = false # LAB : coût élevé si true
  single_nat_gateway      = true
  enable_dns_hostnames    = true
  map_public_ip_on_launch = true
  tags = { Lab = "terraform-vpc", Region = "ca-central-1" }
  public_subnet_tags  = { Tier = "public" }
  private_subnet_tags = { Tier = "private" }
}

output "module_vpc_id"         { value = module.vpc.vpc_id }
output "module_public_subnets" { value = module.vpc.public_subnets }
output "module_private_subnets"{ value = module.vpc.private_subnets }

Inputs clés

Input Intérêt
name / cidr Identité + adressage
azs / public_subnets / private_subnets Topologie multi-AZ
enable_nat_gateway Levier coût n°1
single_nat_gateway 1 NAT (économie) vs 1/AZ (HA)
tags / *_subnet_tags Discovery ALB / ASG / EKS

Voir Modules Registry (terraform-modules-registry) et Modules bases pour un wrapper maison.

Étape 6 — Quand préférer le module

Préférez le HCL nu pour former une équipe ou pour un réseau vraiment atypique (NACL complexes, peering multiple, inspection firewall). Préférez le module versionné dès que la topologie est standard (public/privé multi-AZ, tags pour Kubernetes ou ASG) et que la revue de code doit rester courte. Dans les deux cas, le levier coût n°1 reste enable_nat_gateway.

Situation Préférer
Comprendre IGW / RT / NAT À la main (Partie A)
Stacks répétées, tags EKS Module versionné
Réseau atypique / formation HCL nu d’abord, puis wrapper

Stratégie P2 : A = modèle mental ; B = productivité. Un seul chemin par state.

Étape 7 — Lab : plan puis destroy (sans NAT)

Le mode plan-only valide la syntaxe et le graphe sans créer de ressources. Si vous appliquez, limitez la durée de vie du VPC lab à quelques minutes : même sans NAT, des SG ou EIP orphelins peuvent rester. Le filet CLI en fin de lab (describe-vpcs / describe-nat-gateways filtrés sur le tag Lab) fait partie du métier, pas d’un bonus.

Mode A — Recommandé : validate / plan (quasi 0 €)

mkdir -p ~/terraform-vpc-lab && cd ~/terraform-vpc-lab
# Collez les fichiers Partie A
export AWS_PROFILE=lab AWS_REGION=ca-central-1
PROJECT="votreprenom20260910"
terraform init && terraform fmt && terraform validate
terraform plan -var="project=${PROJECT}" -var="enable_nat_gateway=false"
# Attendu : 0 NAT / 0 EIP — STOP ou apply court

Mode B — Apply minimal puis destroy immédiat

terraform apply -auto-approve 
  -var="project=${PROJECT}" -var="enable_nat_gateway=false"
terraform output vpc_id
terraform output public_subnet_ids

terraform destroy -auto-approve 
  -var="project=${PROJECT}" -var="enable_nat_gateway=false"
cd ~ && rm -rf ~/terraform-vpc-lab

aws ec2 describe-vpcs --region ca-central-1 
  --filters Name=tag:Lab,Values=terraform-vpc --query 'Vpcs[].VpcId'
# Attendu : []

NAT activé par erreur → destroy immédiat + check EIP / NAT Gateways.

Étape 8 — Checklist

  1. VPC / public vs privé / IGW / RT / NAT.
  2. aws_vpc + 2 publics + 2 privés sur 2 AZ (ca-central-1).
  3. IGW + associations RT ; NAT off en lab.
  4. SG least privilege + outputs (ALB, RDS, ASG).
  5. Module terraform-aws-modules/vpc/aws version pinée — savoir quand l’utiliser.
  6. TF ≥ 1.9, provider ~> 5.0, destroy — pas de VPC/NAT idle.

Nettoyage

cd ~/terraform-vpc-lab
terraform destroy -auto-approve 
  -var="project=${PROJECT}" -var="enable_nat_gateway=false"
cd ~ && rm -rf ~/terraform-vpc-lab
# Filet : VPC Lab=terraform-vpc, EIP, NAT Gateways, SG orphelins
aws ec2 describe-nat-gateways --region ca-central-1 
  --filter Name=tag:Lab,Values=terraform-vpc 
  --query 'NatGateways[?State!=`deleted`].NatGatewayId'

Erreurs fréquentes

Symptôme Cause Correction
Instance « privée » a une IP publique Subnet public / map_public_ip Tier privé + RT sans IGW
Pas d’Internet depuis public IGW ou RT manquante Route 0.0.0.0/0 → IGW + association
Facture surprise NAT + EIP oubliés enable_nat_gateway=false ; destroy
AZ collision 2 subnets même AZ data.aws_availability_zones
Module drift version non pinée version = "~> 5.0"
CIDR overlap / double VPC Même plage ou main+module Adressage unique ; un chemin/state
RDS / ALB échoue Subnets insuffisants ≥ 2 AZ ; privés DB, publics ALB
Plan crée un NAT malgré le lab Variable oubliée / module true Forcer -var=enable_nat_gateway=false et relire le plan
Peering / CIDR collision plus tard CIDR lab trop « classique » (10.0.0.0/16) Réserver une plage lab documentée (ex. 10.42.0.0/16)
DNS privé KO pour RDS enable_dns_hostnames/support false Les laisser à true sur le VPC lab/prod

Quiz (3 questions)

1. En lab terraform vpc P2, le NAT Gateway ?

  • A. Toujours 1 NAT / AZ
  • B. Désactivé (false) — coût élevé
  • C. Obligatoire même sans egress

2. AZ minimum pour ALB + RDS ?

  • A. 1 AZ
  • B. 2 AZ (publics + privés)
  • C. 6 AZ partout

3. Quand préférer le module VPC Registry ?

  • A. Jamais
  • B. Stacks répétées — après le modèle à la main
  • C. Sans version (latest)

4. Pourquoi taguer Tier = public|private sur les subnets ?

  • A. Décoratif uniquement
  • B. Facilite discovery (ALB/ASG/EKS) et revues
  • C. Obligatoire pour créer un IGW

5. Que faire si plan montre un aws_nat_gateway en lab P2 ?

  • A. Appliquer quand même
  • B. Corriger le flag / inputs puis re-plan — NAT = coût
  • C. Ignorer : le NAT est Free Tier

Réponses : 1‑B · 2‑B · 3‑B · 4‑B · 5‑B

FAQ

Faut-il un NAT pour un lab RDS + ASG privés ? Non pour un exercice purement réseau. Oui dès que les instances privées doivent télécharger des paquets ou appeler des API Internet sans endpoints VPC. Budgétez avant d’activer.

Puis-je mettre l’ALB en subnet privé ? Un ALB internet-facing exige des subnets publics (route IGW). Un ALB internal utilise des subnets privés et n’est joignable que depuis le VPC / le réseau d’entreprise.

Le module VPC remplace-t-il terraform-vpc « à la main » ? Il le factorise. Apprenez d’abord les ressources natives, puis adoptez le module pour la productivité — jamais l’inverse si vous déboguez un routage cassé.

Pour aller plus loin

Maillage série Terraform (P2)

← Précédent RDS (terraform-rds)
→ Suivant Autoscaling (terraform-autoscaling)
Aussi ALB (terraform-alb) · Modules Registry (terraform-modules-registry) · VPC AWS (aws-vpc-sous-reseaux-igw)
Carte Overview · Install · Workflow · Blocks · Providers · Variables · Locals · State · Modules · Workspaces · Labs AWS

Retour parcours Terraform — hub de la série et leçons sœurs.

Share your love

Leave a Reply

Your email address will not be published. Required fields are marked *