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 moduleterraform-aws-modules/vpc/awsversionné — inputs clés et quand préférer le module. Regionca-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-identityOK) - 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
- VPC / public vs privé / IGW / RT / NAT.
aws_vpc+ 2 publics + 2 privés sur 2 AZ (ca-central-1).- IGW + associations RT ; NAT off en lab.
- SG least privilege + outputs (ALB, RDS, ASG).
- Module
terraform-aws-modules/vpc/awsversion pinée — savoir quand l’utiliser. - 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
- Doc :
aws_vpc,aws_nat_gateway, module VPC, pricing NAT - Site :
aws-vpc-sous-reseaux-igw·terraform-modules-registry
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.



