AWS Partner Network (APN) Blog

Taking NAT to the Next Level in AWS CloudFormation Templates

Santiago Cardenas is a Partner Solutions Architect (SA) at AWS.  

So you’re already creating your cloud infrastructure using AWS CloudFormation to automate your deployments through infrastructure-as-code. As you design the virtual network, you’re probably already using public and private subnets within your Amazon Virtual Private Cloud (Amazon VPC), given the best practices of subnet isolation for front-end and back-end resources. A public subnet includes a direct route to the Internet through the Internet gateway that is attached to the VPC. A private subnet, on the other hand, has no such route and must hop through another device, such as a Network Address Translation (NAT) instance, to get to the Internet. Typically, you’ll use a Linux instance that is configured to act as a NAT device by configuring IPv4 forwarding and using iptables for IP masquerading. The challenge, however, is ensuring that these instances are highly available, scale with traffic, and don’t become the bottleneck or single point of failure.

Cue in NAT gateways!

A managed NAT gateway has built-in redundancy at the Availability Zone level and a simple management console and API. In addition to this and other benefits, it can be easily configured through AWS CloudFormation, making it a great choice for new and existing templates.

A typical CloudFormation template that uses NAT instances will have the following resources of interest:

{
    ...
    "Resources" : {
        ...
        "NAT1EIP" : {
            "Type" : "AWS::EC2::EIP",
            "Properties" : {
                "Domain" : "vpc",
                "InstanceId" : {
                    "Ref" : "NAT1"
                }
            }
        },
        "NAT1" : {
            "Type" : "AWS::EC2::Instance",
            "DependsOn" : "VPCGatewayAttachment",
            "Properties" : {
                "ImageId" : {
                    "Fn::FindInMap" : [
                        "AWSRegionArchNatAMI",
                        {
                            "Ref" : "AWS::Region"
                        },
                        {
                            "Fn::FindInMap" : [
                                 "AWSInstanceType2Arch",
                                 {
                                     "Ref" : "NATInstanceType"
                                 },
                                 "Arch"
                            ]
                        }
                    ]
                },
                "InstanceType" : {
                    "Ref" : "NATInstanceType"
                },
                "Tags" : [
                    {
                        "Key" : "Name",
                        "Value" : "NAT1"
                    }
                ],
                "NetworkInterfaces" : [
                    {
                        "GroupSet" : [
                            {
                                "Ref" : "NATSecurityGroup"
                            }
                        ],
                        "AssociatePublicIpAddress" : "true",
                        "DeviceIndex" : "0",
                        "DeleteOnTermination" : "true",
                        "SubnetId" : {
                            "Ref" : "PublicSubnetAZ1"
                        }
                    }
                ],
                "KeyName"           : {
                    "Ref" : "KeyPairName"
                },
                "SourceDestCheck"   : "false"
           }
       },
       "PrivateRoute1" : {
           "Type" : "AWS::EC2::Route",
           "Properties" : {
               "RouteTableId" : {
                   "Ref" : "PrivateRouteTable"
               },
               "DestinationCidrBlock" : "0.0.0.0/0",
               "InstanceId" : {
                   "Ref" : "NAT1"
               }
           }
       },
       ...
   }
   ...
}

This example snippet includes the following resources that are directly involved with a NAT instance deployment. These are things like:

  • The Elastic IP address that is to be attached to the NAT instance.
  • The NAT instance itself, coming from an Amazon Linux NAT AMI (Amazon Machine Image).
  • The route to the Internet via the NAT instance. This route is later added to the route table associated with the private subnets in the same Availability Zone.

We also recommend that your architecture include at least two Availability Zones, which means that you would include the code above at least twice in your CloudFormation template (once per Availability Zone).

Modifying your CloudFormation template to discontinue the use of NAT instances and consume NAT gateways is straightforward. You would:

  • Allocate an Elastic IP address. However, it would not be directly assigned to an instance.
  • Create a NAT gateway resource.
  • Create a route to the Internet, but via the NAT gateway instead of going through a NAT instance. As in the code for NAT instances, this route would then be associated with the route table for the private subnets in the same Availability Zone.

The updated example would look something like this:

{
    ...
    "Resources" : {
        ...
        "NATGateway1EIP" : {
            "Type" : "AWS::EC2::EIP",
            "Properties" : {
                "Domain" : "vpc"
            }
        },
        "NATGateway1" : {
            "Type" : "AWS::EC2::NatGateway",
            "DependsOn" : "VPCGatewayAttachment",
            "Properties" : {
                "AllocationId" : {
                    "Fn::GetAtt" : [
                        "NATGateway1EIP",
                        "AllocationId"
                    ]
                },
                "SubnetId" : {
                    "Ref" : "PublicSubnetAZ1"
                }
            }
        },
        "PrivateRoute1" : {
            "Type" : "AWS::EC2::Route",
            "Properties" : {
                "RouteTableId" : {
                    "Ref" : "PrivateRouteTable1"
                },
                "DestinationCidrBlock" : "0.0.0.0/0",
                "NatGatewayId" : {
                    "Ref" : "NATGateway1"
                }
            }
        },
        ...
    }
    ...
}

As you can observe from the updated example, updating your CloudFormation templates to utilize a NAT gateway is a modest change that improves the architecture of the VPC while removing the burden of configuring, monitoring, and scaling the NAT instances. For more information about the NAT gateway resource type, see the AWS CloudFormation User Guide.

There are a few additional things to keep in mind about NAT gateways at this time:

  • They are available in most (but not all) regions. For supported regions, see the NAT gateway section of the Amazon VPC Pricing
  • They only handle outbound traffic.
  • They don’t support complex iptables rules.
  • If you used the NAT instance as a bastion, you would need to stand up a separate bastion now. This is a better practice anyway, because it properly separates the instance roles and duties, therefore allowing finer grained security control.

For further reading, please see:

Have you read our recent posts on Amazon VPC for on-premises network engineers? Check out part one and part two.