VPC Architecture
VPC Architecture
VPC Design
Production VPC (10.0.0.0/16 = 65,536 IPs)
┌─────────────────────────────────────────────────────┐
│ VPC 10.0.0.0/16 │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Public Subnet│ │ Public Subnet│ │ Public Subnet│ │
│ │ 10.0.1.0/24│ │ 10.0.2.0/24│ │ 10.0.3.0/24│ │
│ │ AZ-1a │ │ AZ-1b │ │ AZ-1c │ │
│ │ ALB, NAT GW│ │ ALB, NAT GW│ │ ALB, NAT GW│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Private Subnet│ │Private Subnet│ │Private Subnet│ │
│ │ 10.0.10.0/24│ │ 10.0.20.0/24│ │ 10.0.30.0/24│ │
│ │ AZ-1a │ │ AZ-1b │ │ AZ-1c │ │
│ │ App Servers │ │ App Servers │ │ App Servers │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Private Subnet│ │Private Subnet│ │Private Subnet│ │
│ │ 10.0.100.0/24│ │ 10.0.200.0/24│ │ 10.0.250.0/24│ │
│ │ AZ-1a │ │ AZ-1b │ │ AZ-1c │ │
│ │ Databases │ │ Databases │ │ Databases │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────┘
Public subnets: Internet-facing (ALB, Bastion, NAT)
Private subnets: No direct internet (App servers, Databases)
Terraform VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = { Name = "production-vpc" }
}
resource "aws_subnet" "public" {
count = 3
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index + 1)
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = { Name = "public-${count.index + 1}" }
}
resource "aws_subnet" "private" {
count = 3
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index + 10)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = { Name = "private-${count.index + 1}" }
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "main-igw" }
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id
tags = { Name = "main-nat" }
}
resource "aws_eip" "nat" {
domain = "vpc"
}
Subnet Routing
# Public route table
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 = "public-rt" }
}
# Private route table (through NAT)
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = { Name = "private-rt" }
}
# Associate subnets
resource "aws_route_table_association" "public" {
count = 3
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private" {
count = 3
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private.id
}
VPC Peering
# Peering between two VPCs
aws ec2 create-vpc-peering-connection \
--vpc-id vpc-abc123 \
--peer-vpc-id vpc-def456
# Accept peering
aws ec2 accept-vpc-peering-connection --vpc-peering-connection-id pcx-abc
# Add route to peered VPC
aws ec2 create-route \
--route-table-id rtb-abc \
--destination-cidr-block 10.1.0.0/16 \
--vpc-peering-connection-id pcx-abc
Load Balancing
Load Balancing
Application Load Balancer (ALB)
resource "aws_lb" "web" {
name = "web-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = aws_subnet.public[*].id
enable_deletion_protection = true
access_logs {
bucket = aws_s3_bucket.alb_logs.id
prefix = "alb"
enabled = true
}
}
resource "aws_lb_target_group" "web" {
name = "web-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
path = "/health"
port = "traffic-port"
healthy_threshold = 3
unhealthy_threshold = 3
timeout = 5
interval = 30
matcher = "200"
}
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.web.arn
port = 80
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.web.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = aws_acm_certificate.web.arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web.arn
}
}
# Path-based routing
resource "aws_lb_listener_rule" "api" {
listener_arn = aws_lb_listener.https.arn
priority = 100
action {
type = "forward"
target_group_arn = aws_lb_target_group.api.arn
}
condition {
path_pattern {
values = ["/api/*"]
}
}
}
Network Load Balancer (NLB)
# NLB for TCP/UDP workloads (databases, game servers)
resource "aws_lb" "db" {
name = "db-nlb"
internal = true
load_balancer_type = "network"
subnets = aws_subnet.private[*].id
}
resource "aws_lb_target_group" "db" {
name = "db-tg"
port = 5432
protocol = "TCP"
vpc_id = aws_vpc.main.id
}
ALB vs NLB
| Feature | ALB | NLB |
|---|---|---|
| Layer | L7 (HTTP/HTTPS) | L4 (TCP/UDP) |
| Content routing | Yes | No |
| WebSocket | Yes | Yes |
| Static IP | No | Yes |
| Latency | Higher | Lower |
| Cost | Lower | Higher |
| Use case | Web apps, APIs | Databases, game servers |
DNS and CDN
DNS and CDN
Route 53
# Create hosted zone
aws route53 create-hosted-zone --name example.com --caller-reference $(date +%s)
# Create A record
aws route53 change-resource-record-sets --hosted-zone-id Z123 --change-batch '{
"Changes": [{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"AliasTarget": {
"DNSName": "alb-123.us-east-1.elb.amazonaws.com",
"HostedZoneId": "Z35SXDOTRQ7X7K",
"EvaluateTargetHealth": true
}
}
}]
}'
# Health-checked DNS (failover)
aws route53 change-resource-record-sets --hosted-zone-id Z123 --change-batch '{
"Changes": [{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"SetIdentifier": "primary",
"Failover": "PRIMARY",
"TTL": 60,
"ResourceRecords": [{"Value": "1.2.3.4"}]
}
}, {
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"SetIdentifier": "secondary",
"Failover": "SECONDARY",
"TTL": 60,
"ResourceRecords": [{"Value": "5.6.7.8"}]
}
}]
}'
CloudFront (CDN)
resource "aws_cloudfront_distribution" "web" {
origin {
domain_name = aws_lb.web.dns_name
origin_id = "alb"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
enabled = true
default_root_object = "index.html"
aliases = ["example.com", "www.example.com"]
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "alb"
viewer_protocol_policy = "redirect-to-https"
compress = true
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 86400 # 1 day
max_ttl = 31536000 # 1 year
}
# Cache static assets longer
ordered_cache_behavior {
path_pattern = "/static/*"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "alb"
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.web.arn
ssl_support_method = "sni-only"
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
}
Best Practices
# 1. Use multiple AZs for high availability
# 2. Put databases in private subnets
# 3. Use NAT gateways for private subnet internet access
# 4. Use VPC endpoints for AWS services (avoid NAT costs)
# 5. Use security groups (stateful) not NACLs (stateless)
# 6. Enable VPC flow logs for debugging
# 7. Use SSM Session Manager instead of bastion hosts
# 8. Use CloudFront for static assets and caching