AWS

AWS NAT Gateway High Availability

In: AWS, IaC

What is a NAT Gateway?

NAT Gateway enables instances in a private subnet to connect to the Internet but external services cannot initiate a connection with those instances.

Private vs Public subnet

The instances in the public subnet can send outbound traffic directly to the Internet via Internet Gateway (IGW), whereas the instances in the private subnet can't. Instead, the instances in the private subnet can access the Internet by using a NAT gateway that resides in the public subnet.

A typical example is a public-facing web application (public subnet) where the inbound/outbound traffic is routed via the IGW. Back-end servers on the other hand are not publically accessible however, they can still reach out to the Internet for downloading packages or updating OS.

diagram - public vs private subnet

As per the above diagram, instances in the public subnet have routes to the Internet via IGW. Instances in the private subnet have routes to the Internet via NAT gateway. Users on the Internet can also initiate inbound connections to the instances in the public subnet using their public/elastic IP.

How does NAT gateway work?

Public NAT Gateway
To create a NAT gateway, you must specify the public subnet in which the NAT gateway should reside. An elastic IP address has to be associated with a NAT gateway when it is created. Each NAT gateway is created in a specific Availability Zone and implemented with redundancy in that zone. As you can see above, the NAT gateway was created in AZ-1/public subnet 1a.

The NAT gateway replaces the source IP address of the instances with the IP address of the NAT gateway. For a public NAT gateway, this is the elastic IP address of the NAT gateway.

đź’ˇ
As of June 2021, AWS Removes NAT Gateway’s dependence on Internet Gateway for Private Communications. You can now launch NAT Gateways in your VPC without associating an IGW to your VPC.

Private NAT Gateway
A private NAT Gateway does not require Elastic IP and you do not need to attach an IGW with your VPC. Instances in private subnets can connect to other VPCs or on-premises networks through a private NAT gateway.

You can route traffic from the NAT gateway through a transit gateway or a virtual private gateway. You cannot associate an elastic IP address with a private NAT gateway. You can attach an Internet gateway to a VPC with a private NAT gateway, but if you route traffic from the private NAT gateway to the internet gateway, the Internet gateway will drop the traffic.

public vs private NAT gateway

NAT Gateway HA scenario

NAT Gateway is Highly Available in one Availability Zone, If you have resources in multiple Availability Zones and they share one NAT gateway, and if the NAT gateway’s Availability Zone is down, resources in the other Availability Zones lose Internet access.

In our example above, if AZ 1a goes down,  instances in other AZs lose Internet Access.

Depending on your business requirement and fault-tolerant architecture, make sure to create NAT Gateways in at least two Availability Zones.

3 x NAT Gateway

As you can see above, now we have NAT Gateways in each AZ which provide fault-tolerance against AZ failures.

NAT Gateway HA deployment using Terraform

Suresh-MacBook:NAT HA suresh$ tree
.
|-- main.tf
|-- terraform.tfstate
|-- terraform.tfstate.backup
|-- vars.tf
`-- vpc-subnets.tf

0 directories, 5 files
provider "aws" {
  region  = var.region
  profile = "suresh-stage"
}
main.tf
variable "region" {
  default = "eu-west-2"
}

variable "vpc-cidr" {
  default = "10.10.0.0/16"
}

variable "azs" {
  type = list
  default = ["eu-west-2a" , "eu-west-2b", "eu-west-2c"]
}

variable "private-subnets" {
  type = list
  default = ["10.10.1.0/24" , "10.10.2.0/24" , "10.10.3.0/24"]
}
variable "public-subnets" {
  type = list
  default = ["10.10.20.0/24" , "10.10.21.0/24" , "10.10.22.0/24"]
}
vars.tf
#Create VPC
resource "aws_vpc" "test-vpc" {
  cidr_block       = var.vpc-cidr

  tags = {
    Name = "test-vpc"
  }
}

#Create private subnets
resource "aws_subnet" "private-subnets" {
  vpc_id = aws_vpc.test-vpc.id
  count = length(var.azs)
  cidr_block = element(var.private-subnets , count.index)
  availability_zone = element(var.azs , count.index)

  tags = {
    Name = "private-subnet-${count.index+1}"
  }
}

#Create public subnets
resource "aws_subnet" "public-subnets" {
  vpc_id = aws_vpc.test-vpc.id
  count = length(var.azs)
  cidr_block = element(var.public-subnets , count.index)
  availability_zone = element(var.azs , count.index)

  tags = {
    Name = "public-subnet-${count.index+1}"
  }
}


#Create IGW
resource "aws_internet_gateway" "test-igw" {
  vpc_id = aws_vpc.test-vpc.id

  tags = {
    Name = "test-igw"
  }
}

#Single route table for public subnet
resource "aws_route_table" "public-rtable" {
  vpc_id = aws_vpc.test-vpc.id

  tags = {
    Name = "public-rtable"
  }
}

#add routes to public-rtable
resource "aws_route" "public-rtable" {
  route_table_id            = aws_route_table.public-rtable.id
  destination_cidr_block    = "0.0.0.0/0"
  gateway_id                = aws_internet_gateway.test-igw.id
}

#route table association public subnets
resource "aws_route_table_association" "public-subnet-association" {
  count          = length(var.public-subnets)
  subnet_id      = element(aws_subnet.public-subnets.*.id , count.index)
  route_table_id = aws_route_table.public-rtable.id
}

#private route tables
resource "aws_route_table" "private-rtable" {
  count          = length(var.private-subnets)
  vpc_id         = aws_vpc.test-vpc.id

  tags = {
    Name = "private-rtable-${count.index+1}"
  }
}

#route table association private subnets
resource "aws_route_table_association" "private-subnet-association" {
  count          = length(var.private-subnets)
  subnet_id      = element(aws_subnet.private-subnets.*.id , count.index)
  route_table_id = element(aws_route_table.private-rtable.*.id , count.index)
}

#3 x EIP
resource "aws_eip" "nat-eip" {
  count    = length(var.azs)
  vpc      = true

  tags = {
    Name = "EIP--${count.index+1}"
  }
}

#3 x NAT gateways
resource "aws_nat_gateway" "prod-nat-gateway" {
  count         = length(var.azs)
  allocation_id = element(aws_eip.nat-eip.*.id , count.index)
  subnet_id     = element(aws_subnet.public-subnets.*.id , count.index)

  tags = {
    Name = "NAT-GW--${count.index+1}"
  }
}

#add routes to private-rtable
resource "aws_route" "subnets-private-rtable" {
  count                     = length(var.azs)
  route_table_id            = element(aws_route_table.private-rtable.*.id , count.index)
  destination_cidr_block    = "0.0.0.0/0"
  nat_gateway_id            = element(aws_nat_gateway.prod-nat-gateway.*.id, count.index)
}
vpc-subnets.tf

Verification

private and public subnets
route tables
3 x NAT Gateways
public route table

NAT GW 1

As you can see above, private subnet in AZ-1 has route to the Internet via the NAT Gateway in the same AZ.

NAT GW 2
NAT GW 3

Reference

NAT gateways - Amazon Virtual Private Cloud
Use a NAT gateway in a public VPC subnet to enable outbound internet traffic from instances in a private subnet.

Thanks for reading

As always, your feedback and comments are more than welcome.

Written by
Suresh Vinasiththamby
I'm very excited to start blogging and share with you insights about my favourite Networking, Cloud and Automation topics. Simple guy with simple taste and lots of love for Networking and Automation.
Comments
More from Packetswitch
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Packetswitch.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.