
What is a NAT Gateway and how does it work?
NAT Gateway is an essential service that enables instances in a private subnet to connect to the Internet. It is designed to protect your private resources by preventing inbound connections while allowing outbound connections. This article will delve into NAT Gateway's architecture, features, and deployment strategies.
Private vs Public subnet
Before we dive into the details of NAT Gateway, it's important to understand the difference between a private and a public subnet. Instances in a public subnet can send outbound traffic directly to the Internet via an Internet Gateway (IGW). On the other hand, instances in a private subnet cannot send outbound traffic directly to the Internet via IGW. Instead, instances in a private subnet can access the Internet by using a NAT gateway that resides in the public subnet.
A typical example of this scenario is a public-facing web application in a public subnet where inbound/outbound traffic is routed via the IGW. Back-end servers, on the other hand, are not publicly accessible. However, they can still reach out to the Internet for downloading packages or updating the OS.
How does NAT gateway work?
A NAT gateway is a highly available, managed network address translation (NAT) service that enables instances in a private subnet to access the Internet while preventing inbound traffic from the Internet. When an instance in a private subnet sends a request to the Internet, the request is routed to the NAT gateway, which replaces the source IP address of the instance with the IP address of the NAT gateway. When the response comes back, the NAT gateway replaces the destination IP address of the response with the IP address of the original instance. In this way, the NAT gateway enables instances in a private subnet to access the Internet securely.
Public NAT Gateway
To create a NAT gateway, you must specify the public subnet in which the NAT gateway should reside. When creating a public NAT gateway, you must associate an elastic IP address with it. The NAT gateway is created in a specific Availability Zone and implemented with redundancy in that zone. 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 has removed NAT Gateway’s dependence on an Internet Gateway for Private Communications. This means 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 an elastic IP, and you do not need to attach an IGW to 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.

NAT Gateway High Availability Scenario
By default, NAT Gateway is only highly available within a single Availability Zone. If you have resources across multiple Availability Zones that share one NAT Gateway and the NAT Gateway's Availability Zone goes down, resources in other Availability Zones will lose their internet access. For instance, if AZ 1a fails, instances in other AZs will no longer have internet access. To ensure fault-tolerance, it is recommended to create NAT Gateways in at least two Availability Zones.
In the example above, we have created NAT Gateways in each AZ, which provides fault tolerance against AZ failures. Below is an example of how to deploy NAT Gateway High Availability using Terraform.
NAT Gateway High Availability Deployment Using Terraform
The Terraform script creates a VPC with public and private subnets across multiple Availability Zones. The public subnets contain NAT Gateways, and the private subnets contain resources that require internet access. The script also creates an internet gateway and routing tables for each subnet. The NAT Gateway is assigned a unique Elastic IP address and allocated to a specific public subnet.
The following files are used in this deployment:
.
|-- main.tf
|-- terraform.tfstate
|-- terraform.tfstate.backup
|-- vars.tf
`-- vpc-subnets.tf
The main.tf file defines the AWS provider, region, and variables:
provider "aws" {
region = var.region
profile = "suresh-stage"
}
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"]
}
The vars.tf file defines variables used in the deployment:
#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)
}
The script starts by creating a VPC and and using the same as defined variables var.public-subnets, var.private-subnets, and var.azs which are lists of the public subnets, private subnets, and availability zones, respectively.
The script also focuses on intricate details of creating the following resources:
aws_internet_gateway: creates an internet gateway and attaches it to the VPC specified by aws_vpc.test-vpc.id.
aws_route_table: creates a route table for the public subnets and associates it with the VPC specified by aws_vpc.test-vpc.id.
aws_route: adds a default route to the route table created above to allow traffic to the internet via the IGW created earlier.
aws_route_table_association: associates the public subnets with the route table created above.
aws_route_table: creates multiple private route tables for each private subnet in the VPC.
aws_route_table_association: associates each private subnet with a corresponding private route table.
aws_eip: creates three Elastic IP addresses (EIP) that can be used by NAT gateways.
aws_nat_gateway: creates three NAT gateways and assigns them to each public subnet via the corresponding EIP and subnet ID.
aws_route: adds a default route to each private route table to route traffic to the internet via the corresponding NAT gateway.
Note: that the script uses count to create multiple resources. The count parameter is set to the length of the var.azs list, which assumes that there are the same number of availability zones as NAT gateways and EIPs. The element() function is used to select the correct subnet or resource based on the current count index.
We hope this article meets your expectations and provides you with the high-quality content on Cloud. If you have any questions or concerns, please feel free to reach out to our experts.
Comments