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.id
101 destination_cidr_block = "0.0.0.0/0"
102 nat_gateway_id = aws_nat_gateway.ngw.id
103}
104 
105# Public Route to Public Route Table for Public Subnets
106resource "aws_route_table_association" "public" {
107 for_each = aws_subnet.public
108 subnet_id = aws_subnet.public[each.key].id
109 
110 route_table_id = aws_route_table.public.id
111}
112 
113# Private Route to Private Route Table for Private Subnets
114resource "aws_route_table_association" "private" {
115 for_each = aws_subnet.private
116 subnet_id = aws_subnet.private[each.key].id
117 
118 route_table_id = aws_route_table.private.id
119}

Don't miss out

Sign up to learn when new content is released! Courses are in production now.