# AWS VPC with public/private subnets across 3 AZs
data "aws_availability_zones" "available" {
state = "available"
}
locals {
azs = slice(data.aws_availability_zones.available.names, 0, 3)
environment = var.environment
name_prefix = "${var.project}-${local.environment}"
}
# === VPC ===
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr # e.g. "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${local.name_prefix}-vpc"
Environment = local.environment
}
}
# === Internet Gateway (for public subnets) ===
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${local.name_prefix}-igw"
}
}
# === Public Subnets ===
resource "aws_subnet" "public" {
count = length(local.azs)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index) # /24 subnets
availability_zone = local.azs[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${local.name_prefix}-public-${local.azs[count.index]}"
Tier = "public"
"kubernetes.io/role/elb" = "1" # For K8s ALB controller
}
}
# === Private Subnets ===
resource "aws_subnet" "private" {
count = length(local.azs)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 100) # /24 subnets
availability_zone = local.azs[count.index]
tags = {
Name = "${local.name_prefix}-private-${local.azs[count.index]}"
Tier = "private"
"kubernetes.io/role/internal-elb" = "1"
}
}
# === NAT Gateway (one per AZ for HA) ===
resource "aws_eip" "nat" {
count = length(local.azs)
domain = "vpc"
tags = {
Name = "${local.name_prefix}-nat-eip-${local.azs[count.index]}"
}
}
resource "aws_nat_gateway" "main" {
count = length(local.azs)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "${local.name_prefix}-nat-${local.azs[count.index]}"
}
depends_on = [aws_internet_gateway.main]
}
# === Route Tables ===
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 = length(local.azs)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
count = length(local.azs)
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
tags = {
Name = "${local.name_prefix}-private-rt-${local.azs[count.index]}"
}
}
resource "aws_route_table_association" "private" {
count = length(local.azs)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
# === VPC Flow Logs ===
resource "aws_flow_log" "main" {
vpc_id = aws_vpc.main.id
traffic_type = "ALL"
log_destination = aws_cloudwatch_log_group.flow_logs.arn
log_destination_type = "cloud-watch-logs"
iam_role_arn = aws_iam_role.flow_logs.arn
tags = {
Name = "${local.name_prefix}-flow-logs"
}
}
resource "aws_cloudwatch_log_group" "flow_logs" {
name = "/aws/vpc/flow-logs/${local.name_prefix}"
retention_in_days = 30
}
resource "aws_iam_role" "flow_logs" {
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"
}
}]
})
}
# === Outputs ===
output "vpc_id" {
value = aws_vpc.main.id
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
value = aws_subnet.private[*].id
}
output "nat_gateway_ips" {
value = aws_eip.nat[*].public_ip
}
Build production-ready AWS VPC infrastructure using Terraform. Create public and private subnets across availability zones, configure NAT gateways, internet gateways, and route tables. Implement network ACLs and VPC flow logs for security and observability.