1# ============================================================================= 2# AI Software Factory — Network (VPC, 2 AZs, public/private subnets, IGW, NAT) 3# 4# Internet 5# └── IGW 6# ├── public subnets (10.42.0.0/20, 10.42.16.0/20) 7# │ └── web ALB (+ NAT gateway EIP for outbound from private) 8# └── private subnets (10.42.32.0/20, 10.42.48.0/20) 9# └── app EC2 ASGs + RDS + ElastiCache + OpenSearch 10# 11# Private subnets have NO internet route; outbound (npm, apt, image pulls, 12# S3 via NAT) goes through the NAT gateway in the first public subnet. 13# ============================================================================= 14 15data "aws_availability_zones" "available" { 16 state = "available" 17} 18 19locals { 20 # Pin the first two AZs for deterministic subnet placement. 21 azs = slice(data.aws_availability_zones.available.names, 0, local.az_count) 22} 23 24resource "aws_vpc" "main" { 25 cidr_block = var.vpc_cidr 26 enable_dns_support = true 27 enable_dns_hostnames = true 28 29 tags = { 30 Name = local.names.vpc 31 } 32} 33 34# --- Internet gateway -------------------------------------------------------- 35 36resource "aws_internet_gateway" "main" { 37 vpc_id = aws_vpc.main.id 38 39 tags = { 40 Name = local.names.igw 41 } 42} 43 44# --- Public subnets ----------------------------------------------------------- 45 46resource "aws_subnet" "public" { 47 count = local.az_count 48 49 vpc_id = aws_vpc.main.id 50 cidr_block = var.public_subnet_cidrs[count.index] 51 availability_zone = local.azs[count.index] 52 53 # Auto-assign public IPs so the ALB (and NAT) get IPs without extra config. 54 map_public_ip_on_launch = true 55 56 tags = { 57 Name = "${local.names.vpc}-public-${local.azs[count.index]}" 58 Tier = "public" 59 } 60} 61 62# --- Private subnets ---------------------------------------------------------- 63 64resource "aws_subnet" "private" { 65 count = local.az_count 66 67 vpc_id = aws_vpc.main.id 68 cidr_block = var.private_subnet_cidrs[count.index] 69 availability_zone = local.azs[count.index] 70 71 tags = { 72 Name = "${local.names.vpc}-private-${local.azs[count.index]}" 73 Tier = "private" 74 } 75} 76 77# --- NAT gateway (outbound for private subnets) -------------------------------- 78 79resource "aws_eip" "nat" { 80 domain = "vpc" 81 82 tags = { 83 Name = local.names.eip 84 } 85} 86 87resource "aws_nat_gateway" "main" { 88 allocation_id = aws_eip.nat.id 89 subnet_id = aws_subnet.public[0].id 90 91 tags = { 92 Name = local.names.nat 93 } 94 95 depends_on = [aws_internet_gateway.main] 96} 97 98# --- Route tables --------------------------------------------------------------- 99 100resource "aws_route_table" "public" { 101 vpc_id = aws_vpc.main.id 102 103 tags = { 104 Name = "${local.names.vpc}-public" 105 } 106} 107 108resource "aws_route" "public_internet" { 109 route_table_id = aws_route_table.public.id 110 destination_cidr_block = "0.0.0.0/0" 111 gateway_id = aws_internet_gateway.main.id 112} 113 114resource "aws_route_table_association" "public" { 115 count = local.az_count 116 117 subnet_id = aws_subnet.public[count.index].id 118 route_table_id = aws_route_table.public.id 119} 120 121resource "aws_route_table" "private" { 122 vpc_id = aws_vpc.main.id 123 124 tags = { 125 Name = "${local.names.vpc}-private" 126 } 127} 128 129resource "aws_route" "private_nat" { 130 route_table_id = aws_route_table.private.id 131 destination_cidr_block = "0.0.0.0/0" 132 nat_gateway_id = aws_nat_gateway.main.id 133} 134 135resource "aws_route_table_association" "private" { 136 count = local.az_count 137 138 subnet_id = aws_subnet.private[count.index].id 139 route_table_id = aws_route_table.private.id 140} 141 142# --- VPC endpoints (optional hardening, cheap, no NAT needed for these) -------- 143# Private access to S3 without leaving the VPC (and without NAT charges). 144# OpenSearch is placed in the VPC itself, so no endpoint needed for it. 145 146resource "aws_vpc_endpoint" "s3" { 147 vpc_id = aws_vpc.main.id 148 service_name = "com.amazonaws.${var.aws_region}.s3" 149 150 tags = { 151 Name = "${local.names.vpc}-s3-endpoint" 152 } 153} 154