In this post I’m going to transfer the functionality of a couple of native NAT gateways to Aviatrix while preserving the NAT GWs IP addresses. If you need a refresh on AVX egress capabilities please take a look at:
AVX spoke gateways can be used as egress in a distributed model customizing the snat functionality.
Elastic IP (EIP)
An Elastic IP address is a static IPv4 address which is reachable from the internet. An Elastic IP address is allocated to your AWS account, and is yours until you release it.
NAT Gateways
A NAT gateway is a Network Address Translation (NAT) service providing internet access to instances in a private subnet. The NAT gateway replaces the source IP address of the instances with the IP address of the NAT gateway.
Aviatrix EIPs
Aviatrix Controller creates EIPs when creating gateways and applies tags as show below:
- Backup
- Aviatrix-Created-Resource
- Type
- Name
- Controller

The gw EIP is used in several places such as security rules, syslog, netflow among others. The controller is responsible to manage it.
Using terraform:
resource "aws_eip" "aws_eip-nat" {
count = 2
lifecycle {
ignore_changes = [tags, ]
}
}
NAT Gateways Deployment
Two GWs are deployed on different AZs:

Using terraform:
resource "aws_nat_gateway" "aws_nat_gateway" {
count = var.aws_nat == true ? 2 : 0
allocation_id = aws_eip.aws_eip-nat["${count.index}"].id
subnet_id = data.aws_subnet.aws_subnet_prefix["${count.index}"].id
}
Route table (private):

Using terraform:
resource "aws_route" "aws_route-nat" {
count = var.aws_nat == true ? 2 : 0
route_table_id = data.aws_route_table.aws_route_table-private["${count.index}"].id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.aws_nat_gateway["${count.index}"].id
depends_on = [
aws_nat_gateway.aws_nat_gateway
]
}
Aviatrix Gateways Deployment
resource "aviatrix_spoke_gateway" "aviatrix_spoke_gateway-gateway" {
depends_on = [
aws_nat_gateway.aws_nat_gateway
]
count = var.aws_nat == true ? 0 : 1
cloud_type = "1"
account_name = var.account
gw_name = var.vpc_name
vpc_id = data.aws_vpc.vpc_name.id
vpc_reg = var.region
gw_size = var.gw_size
subnet = data.aws_subnet.aws_subnet_public-prefix[0].cidr_block
ha_subnet = data.aws_subnet.aws_subnet_public-prefix[1].cidr_block
ha_gw_size = var.gw_size
manage_transit_gateway_attachment = false
allocate_new_eip = false
eip = aws_eip.aws_eip-nat[0].public_ip
ha_eip = aws_eip.aws_eip-nat[1].public_ip
}
Once the gateways are provisioned we need to create the custom snat policy:
resource "aviatrix_gateway_snat" "aviatrix_gateway_snat-egress" {
depends_on = [
aviatrix_spoke_gateway.aviatrix_spoke_gateway-gateway
]
count = var.aws_nat == true ? 0 : 1
gw_name = aviatrix_spoke_gateway.aviatrix_spoke_gateway-gateway[0].gw_name
snat_mode = "customized_snat"
snat_policy {
src_cidr = data.aws_vpc.vpc_name.cidr_block
src_port = ""
dst_cidr = ""
dst_port = ""
protocol = "all"
interface = "eth0"
connection = "None"
mark = ""
snat_ips = aviatrix_spoke_gateway.aviatrix_spoke_gateway-gateway[0].private_ip
snat_port = ""
exclude_rtb = ""
}
}
resource "aviatrix_gateway_snat" "aviatrix_gateway_snat-egress-ha" {
depends_on = [
aviatrix_spoke_gateway.aviatrix_spoke_gateway-gateway
]
count = var.aws_nat == true ? 0 : 1
gw_name = aviatrix_spoke_gateway.aviatrix_spoke_gateway-gateway[0].ha_gw_name
snat_mode = "customized_snat"
snat_policy {
src_cidr = data.aws_vpc.vpc_name.cidr_block
src_port = ""
dst_cidr = ""
dst_port = ""
protocol = "all"
interface = "eth0"
connection = "None"
mark = ""
snat_ips = aviatrix_spoke_gateway.aviatrix_spoke_gateway-gateway[0].ha_private_ip
snat_port = ""
exclude_rtb = ""
}
}
The flat “apply route” takes care of creating the proper route to bring the egress traffic to the gateway:

The last step is to attach them to the transit:
resource "aviatrix_spoke_transit_attachment" "aviatrix_spoke_transit_attachment-gateway" {
depends_on = [
aviatrix_spoke_gateway.aviatrix_spoke_gateway-gateway
]
count = var.aws_nat == true ? 0 : 1
spoke_gw_name = aviatrix_spoke_gateway.aviatrix_spoke_gateway-gateway[0].gw_name
transit_gw_name = var.transit_gw
}
Extras
Data:
data "aws_vpc" "vpc_name" {
filter {
name = "tag:Name"
values = [var.vpc_name]
}
}
data "aws_subnets" "aws_subnets_public" {
filter {
name = "vpc-id"
values = [data.aws_vpc.vpc_name.id]
}
tags = {
Name = "*public*"
}
}
data "aws_subnet" "aws_subnet_public-prefix" {
count = length(data.aws_subnets.aws_subnets_public.ids)
id = tolist(data.aws_subnets.aws_subnets_public.ids)[count.index]
}
data "aws_subnets" "aws_subnets_private" {
filter {
name = "vpc-id"
values = [data.aws_vpc.vpc_name.id]
}
tags = {
Name = "*private*"
}
}
data "aws_subnet" "aws_subnet_private-prefix" {
count = length(data.aws_subnets.aws_subnets_private.ids)
id = tolist(data.aws_subnets.aws_subnets_private.ids)[count.index]
}
data "aws_route_table" "aws_route_table-private" {
count = length(data.aws_subnets.aws_subnets_private.ids)
subnet_id = tolist(data.aws_subnets.aws_subnets_private.ids)[count.index]
}
The process is controlled by a variable (aws_nat) defined on the terraform.tfvars file:
controller_ip = ":)"
username = "admin"
password = ":)"
account = ":)"
region = "us-east-1"
vpc_name = "other-apps-vpc"
transit_gw = "aws-us-east-1-transit"
gw_size = "t3.small"
aws_nat = false
Setting aws_nat to false triggers the migration while setting it to false fails back to the native NAT gateways. Because EIPs can be allocated only during the gateway creation, setting to aws_nat true will destroy the AVX gws. If EIPs don’t need to be repurposed the spoke gateways do not need to be destroyed:
resource "aviatrix_spoke_gateway" "aviatrix_spoke_gateway-gateway" {
cloud_type = "1"
account_name = var.account
gw_name = var.vpc_name
vpc_id = data.aws_vpc.vpc_name.id
vpc_reg = var.region
gw_size = var.gw_size
subnet = data.aws_subnet.aws_subnet_public-prefix[0].cidr_block
ha_subnet = data.aws_subnet.aws_subnet_public-prefix[1].cidr_block
ha_gw_size = var.gw_size
manage_transit_gateway_attachment = false
allocate_new_eip = true
}
and can be attached independently of the NAT gateways existence:
resource "aviatrix_spoke_transit_attachment" "aviatrix_spoke_transit_attachment-gateway" {
depends_on = [
aviatrix_spoke_gateway.aviatrix_spoke_gateway-gateway
]
spoke_gw_name = aviatrix_spoke_gateway.aviatrix_spoke_gateway-gateway[0].gw_name
transit_gw_name = var.transit_gw
}
References
https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html