VPC Gateways
The subnets within a VPC each need a route table, and each route table needs at least one route defined within them. These tell the VPC how to route traffic.
For example, traffic headed to an IP address in the private network gets routed "locally" (stays in the private network), while traffic headed for the outside internet needs routing through an Internet Gateway or a NAT Gateway.
Internet Gateways (IGW)
All public subnets should be assigned an IGW. IGW's are free, and allow the instance to be connected to by the public internet (and for the instance to reach out to the public internet).
Note: If you have an instance in a public subnet but don't assign it a public IP address, it won't be able to reach the outside internet.
NAT Gateways (NGW)
Private subnets that need internet connectivity (including talking to other AWS services) need a NAT gateway. NAT Gateways cost money (potentially a lot of money). They are technically a managed service (you can create your own EC2 instances that do this for you!), and have an hourly cost on top of a bandwidth charge.
It's possible to assign everything a public IP (and use IGW's) and use security groups to block external access as a way to save money. This is a totally valid strategy.
Updating our VPC Module
Let's create an IGW and a NGW for our subnets.
We'll create a new file to handle this - gateways.tf
:
1### 2# IGW and NGW 3## 4resource "aws_internet_gateway" "igw" { 5 vpc_id = aws_vpc.vpc.id 6 7 tags = { 8 Name = "cloudcasts-${var.infra_env}-vpc" 9 Project = "cloudcasts.io" 10 Environment = var.infra_env 11 VPC = aws_vpc.vpc.id 12 ManagedBy = "terraform" 13 } 14} 15 16resource "aws_eip" "nat" { 17 vpc = true 18 19 lifecycle { 20 # prevent_destroy = true 21 } 22 23 tags = { 24 Name = "cloudcasts-${var.infra_env}-eip" 25 Project = "cloudcasts.io" 26 Environment = var.infra_env 27 VPC = aws_vpc.vpc.id 28 ManagedBy = "terraform" 29 Role = "private" 30 } 31} 32 33# Note: We're only creating one NAT Gateway, potential single point of failure 34# Each NGW has a base cost per hour to run, roughly $32/mo per NGW. You'll often see 35# one NGW per AZ created, and sometimes one per subnet. 36# Note: Cross-AZ bandwidth is an extra charge, so having a NAT per AZ could be cheaper 37# than a single NGW depending on your usage 38resource "aws_nat_gateway" "ngw" { 39 allocation_id = aws_eip.nat.id 40 41 # Whichever the first public subnet happens to be 42 # (because NGW needs to be on a public subnet with an IGW) 43 # keys(): https://www.terraform.io/docs/configuration/functions/keys.html 44 # element(): https://www.terraform.io/docs/configuration/functions/element.html 45 subnet_id = aws_subnet.public[element(keys(aws_subnet.public), 0)].id 46 47 tags = { 48 Name = "cloudcasts-${var.infra_env}-ngw" 49 Project = "cloudcasts.io" 50 VPC = aws_vpc.vpc.id 51 Environment = var.infra_env 52 ManagedBy = "terraform" 53 Role = "private" 54 } 55} 56 57 58### 59# Route Tables, Routes and Associations 60## 61 62# Public Route Table (Subnets with IGW) 63resource "aws_route_table" "public" { 64 vpc_id = aws_vpc.vpc.id 65 66 tags = { 67 Name = "cloudcasts-${var.infra_env}-public-rt" 68 Environment = var.infra_env 69 Project = "cloudcasts.io" 70 Role = "public" 71 VPC = aws_vpc.vpc.id 72 ManagedBy = "terraform" 73 } 74} 75 76# Private Route Tables (Subnets with NGW) 77resource "aws_route_table" "private" { 78 vpc_id = aws_vpc.vpc.id 79 80 tags = { 81 Name = "cloudcasts-${var.infra_env}-private-rt" 82 Environment = var.infra_env 83 Project = "cloudcasts.io" 84 Role = "private" 85 VPC = aws_vpc.vpc.id 86 ManagedBy = "terraform" 87 } 88} 89 90 91# Public Route 92resource "aws_route" "public" { 93 route_table_id = aws_route_table.public.id 94 destination_cidr_block = "0.0.0.0/0" 95 gateway_id = aws_internet_gateway.igw.id 96} 97 98# Private Route 99resource "aws_route" "private" {100 route_table_id = aws_route_table.private.id101 destination_cidr_block = "0.0.0.0/0"102 nat_gateway_id = aws_nat_gateway.ngw.id103}104 105# Public Route to Public Route Table for Public Subnets106resource "aws_route_table_association" "public" {107 for_each = aws_subnet.public108 subnet_id = aws_subnet.public[each.key].id109 110 route_table_id = aws_route_table.public.id111}112 113# Private Route to Private Route Table for Private Subnets114resource "aws_route_table_association" "private" {115 for_each = aws_subnet.private116 subnet_id = aws_subnet.private[each.key].id117 118 route_table_id = aws_route_table.private.id119}