Aller au contenu principal

Projet 1 : VPC Multi-AZ Production


Contexte

Vous êtes DevOps Engineer dans une startup fintech. Votre mission est de créer l'infrastructure réseau de base qui hébergera tous les services de l'entreprise.

Exigences

  • Haute disponibilité sur 3 zones de disponibilité
  • Séparation des réseaux publics et privés
  • Accès Internet sortant pour les subnets privés
  • VPC Flow Logs pour la conformité
  • Endpoints VPC pour les services AWS

Architecture cible


Structure du projet

projet-vpc/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── vpc.tf
├── subnets.tf
├── nat.tf
├── routes.tf
├── endpoints.tf
├── flow-logs.tf
└── terraform.tfvars

Implémentation

versions.tf

terraform {
required_version = ">= 1.5.0"

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

variables.tf

variable "region" {
description = "AWS Region"
type = string
default = "eu-west-1"
}

variable "project_name" {
description = "Project name for resource naming"
type = string
}

variable "environment" {
description = "Environment (dev, staging, production)"
type = string

validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production."
}
}

variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"

validation {
condition = can(cidrhost(var.vpc_cidr, 0))
error_message = "VPC CIDR must be a valid CIDR block."
}
}

variable "availability_zones" {
description = "List of availability zones"
type = list(string)
default = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
}

variable "enable_nat_gateway" {
description = "Enable NAT Gateway for private subnets"
type = bool
default = true
}

variable "single_nat_gateway" {
description = "Use single NAT Gateway (cost saving for non-prod)"
type = bool
default = false
}

variable "enable_flow_logs" {
description = "Enable VPC Flow Logs"
type = bool
default = true
}

variable "enable_vpc_endpoints" {
description = "Enable VPC Endpoints for AWS services"
type = bool
default = true
}

variable "tags" {
description = "Additional tags for resources"
type = map(string)
default = {}
}

main.tf

provider "aws" {
region = var.region

default_tags {
tags = merge(var.tags, {
Project = var.project_name
Environment = var.environment
ManagedBy = "Terraform"
})
}
}

locals {
name_prefix = "${var.project_name}-${var.environment}"
az_count = length(var.availability_zones)

# Calcul des CIDR pour les subnets
public_subnets = [
for i in range(local.az_count) :
cidrsubnet(var.vpc_cidr, 8, i + 1)
]

private_subnets = [
for i in range(local.az_count) :
cidrsubnet(var.vpc_cidr, 8, i + 11)
]

database_subnets = [
for i in range(local.az_count) :
cidrsubnet(var.vpc_cidr, 8, i + 21)
]

# Nombre de NAT Gateways
nat_gateway_count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : local.az_count) : 0
}

vpc.tf

resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true

tags = {
Name = "${local.name_prefix}-vpc"
}
}

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

tags = {
Name = "${local.name_prefix}-igw"
}
}

subnets.tf

# Public Subnets
resource "aws_subnet" "public" {
count = local.az_count

vpc_id = aws_vpc.main.id
cidr_block = local.public_subnets[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true

tags = {
Name = "${local.name_prefix}-public-${count.index + 1}"
Type = "public"
"kubernetes.io/role/elb" = "1"
}
}

# Private Subnets (Application)
resource "aws_subnet" "private" {
count = local.az_count

vpc_id = aws_vpc.main.id
cidr_block = local.private_subnets[count.index]
availability_zone = var.availability_zones[count.index]

tags = {
Name = "${local.name_prefix}-private-${count.index + 1}"
Type = "private"
"kubernetes.io/role/internal-elb" = "1"
}
}

# Database Subnets
resource "aws_subnet" "database" {
count = local.az_count

vpc_id = aws_vpc.main.id
cidr_block = local.database_subnets[count.index]
availability_zone = var.availability_zones[count.index]

tags = {
Name = "${local.name_prefix}-database-${count.index + 1}"
Type = "database"
}
}

# Database Subnet Group (pour RDS)
resource "aws_db_subnet_group" "main" {
name = "${local.name_prefix}-db-subnet-group"
subnet_ids = aws_subnet.database[*].id

tags = {
Name = "${local.name_prefix}-db-subnet-group"
}
}

nat.tf

# Elastic IPs pour NAT Gateways
resource "aws_eip" "nat" {
count = local.nat_gateway_count
domain = "vpc"

tags = {
Name = "${local.name_prefix}-nat-eip-${count.index + 1}"
}

depends_on = [aws_internet_gateway.main]
}

# NAT Gateways
resource "aws_nat_gateway" "main" {
count = local.nat_gateway_count

allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id

tags = {
Name = "${local.name_prefix}-nat-${count.index + 1}"
}

depends_on = [aws_internet_gateway.main]
}

routes.tf

# Route Table Public
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id

route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}

tags = {
Name = "${local.name_prefix}-public-rt"
}
}

resource "aws_route_table_association" "public" {
count = local.az_count

subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}

# Route Tables Private
resource "aws_route_table" "private" {
count = local.nat_gateway_count > 0 ? local.az_count : 0

vpc_id = aws_vpc.main.id

tags = {
Name = "${local.name_prefix}-private-rt-${count.index + 1}"
}
}

resource "aws_route" "private_nat" {
count = local.nat_gateway_count > 0 ? local.az_count : 0

route_table_id = aws_route_table.private[count.index].id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = var.single_nat_gateway ? aws_nat_gateway.main[0].id : aws_nat_gateway.main[count.index].id
}

resource "aws_route_table_association" "private" {
count = local.nat_gateway_count > 0 ? local.az_count : 0

subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}

# Route Table Database (pas d'accès Internet)
resource "aws_route_table" "database" {
vpc_id = aws_vpc.main.id

tags = {
Name = "${local.name_prefix}-database-rt"
}
}

resource "aws_route_table_association" "database" {
count = local.az_count

subnet_id = aws_subnet.database[count.index].id
route_table_id = aws_route_table.database.id
}

endpoints.tf

# VPC Endpoints pour les services AWS
resource "aws_vpc_endpoint" "s3" {
count = var.enable_vpc_endpoints ? 1 : 0

vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.s3"
vpc_endpoint_type = "Gateway"

route_table_ids = concat(
[aws_route_table.public.id],
aws_route_table.private[*].id,
[aws_route_table.database.id]
)

tags = {
Name = "${local.name_prefix}-s3-endpoint"
}
}

resource "aws_vpc_endpoint" "dynamodb" {
count = var.enable_vpc_endpoints ? 1 : 0

vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.dynamodb"
vpc_endpoint_type = "Gateway"

route_table_ids = concat(
[aws_route_table.public.id],
aws_route_table.private[*].id
)

tags = {
Name = "${local.name_prefix}-dynamodb-endpoint"
}
}

# Interface Endpoints (pour ECR, Secrets Manager, etc.)
resource "aws_security_group" "vpc_endpoints" {
count = var.enable_vpc_endpoints ? 1 : 0

name = "${local.name_prefix}-vpc-endpoints-sg"
description = "Security group for VPC endpoints"
vpc_id = aws_vpc.main.id

ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [var.vpc_cidr]
}

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

tags = {
Name = "${local.name_prefix}-vpc-endpoints-sg"
}
}

resource "aws_vpc_endpoint" "ecr_api" {
count = var.enable_vpc_endpoints ? 1 : 0

vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.ecr.api"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoints[0].id]
private_dns_enabled = true

tags = {
Name = "${local.name_prefix}-ecr-api-endpoint"
}
}

resource "aws_vpc_endpoint" "ecr_dkr" {
count = var.enable_vpc_endpoints ? 1 : 0

vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.ecr.dkr"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoints[0].id]
private_dns_enabled = true

tags = {
Name = "${local.name_prefix}-ecr-dkr-endpoint"
}
}

resource "aws_vpc_endpoint" "secretsmanager" {
count = var.enable_vpc_endpoints ? 1 : 0

vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.secretsmanager"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoints[0].id]
private_dns_enabled = true

tags = {
Name = "${local.name_prefix}-secretsmanager-endpoint"
}
}

flow-logs.tf

# CloudWatch Log Group pour Flow Logs
resource "aws_cloudwatch_log_group" "flow_logs" {
count = var.enable_flow_logs ? 1 : 0

name = "/aws/vpc/${local.name_prefix}-flow-logs"
retention_in_days = 30

tags = {
Name = "${local.name_prefix}-flow-logs"
}
}

# IAM Role pour Flow Logs
resource "aws_iam_role" "flow_logs" {
count = var.enable_flow_logs ? 1 : 0

name = "${local.name_prefix}-flow-logs-role"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "vpc-flow-logs.amazonaws.com"
}
}
]
})
}

resource "aws_iam_role_policy" "flow_logs" {
count = var.enable_flow_logs ? 1 : 0

name = "${local.name_prefix}-flow-logs-policy"
role = aws_iam_role.flow_logs[0].id

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
]
Effect = "Allow"
Resource = "*"
}
]
})
}

# VPC Flow Logs
resource "aws_flow_log" "main" {
count = var.enable_flow_logs ? 1 : 0

iam_role_arn = aws_iam_role.flow_logs[0].arn
log_destination = aws_cloudwatch_log_group.flow_logs[0].arn
traffic_type = "ALL"
vpc_id = aws_vpc.main.id

tags = {
Name = "${local.name_prefix}-flow-log"
}
}

outputs.tf

output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}

output "vpc_cidr" {
description = "CIDR block of the VPC"
value = aws_vpc.main.cidr_block
}

output "public_subnet_ids" {
description = "IDs of public subnets"
value = aws_subnet.public[*].id
}

output "private_subnet_ids" {
description = "IDs of private subnets"
value = aws_subnet.private[*].id
}

output "database_subnet_ids" {
description = "IDs of database subnets"
value = aws_subnet.database[*].id
}

output "database_subnet_group_name" {
description = "Name of the database subnet group"
value = aws_db_subnet_group.main.name
}

output "nat_gateway_ips" {
description = "Public IPs of NAT Gateways"
value = aws_eip.nat[*].public_ip
}

output "availability_zones" {
description = "Availability zones used"
value = var.availability_zones
}

terraform.tfvars

project_name = "fintech"
environment = "production"
region = "eu-west-1"

vpc_cidr = "10.0.0.0/16"

availability_zones = [
"eu-west-1a",
"eu-west-1b",
"eu-west-1c"
]

enable_nat_gateway = true
single_nat_gateway = false # HA: un NAT par AZ
enable_flow_logs = true
enable_vpc_endpoints = true

tags = {
CostCenter = "infrastructure"
Team = "platform"
}

Déploiement

# 1. Initialiser
terraform init

# 2. Valider
terraform validate

# 3. Prévisualiser
terraform plan -out=tfplan

# 4. Appliquer
terraform apply tfplan

# 5. Vérifier les outputs
terraform output

Tests de validation

# Vérifier le VPC
aws ec2 describe-vpcs --vpc-ids $(terraform output -raw vpc_id)

# Vérifier les subnets
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$(terraform output -raw vpc_id)"

# Tester la connectivité NAT (lancer une instance dans un subnet privé)
# et vérifier qu'elle peut accéder à Internet

Coûts estimés

RessourceCoût mensuel (estimé)
NAT Gateway (x3)~$100/mois
VPC Endpoints (x4)~$30/mois
Flow Logs (CloudWatch)~$5/mois
Total~$135/mois
Économies pour dev/staging

Utilisez single_nat_gateway = true pour réduire à ~$35/mois.


Améliorations possibles

  1. Transit Gateway : Connecter plusieurs VPCs
  2. VPN : Connexion au datacenter on-premise
  3. PrivateLink : Exposer des services via endpoint
  4. Network Firewall : Filtrage avancé du trafic

← Table des matières | Projet 2: Application Web HA →