Network with Public and Private Subnets
- CloudFormation template: yaml
AWSTemplateFormatVersion: 2010-09-09 Description: > A VPC with two public subnets and two private subnets. Each public subnet includes a NAT Gateway which provides outbound connectivity from the private subnets to the internet.
Overview
This CloudFormation template creates the "standard" AWS network: a VPC with two public subnets and two private subnets. AWS documents this architecture here, and AWS provides a CloudFormation example here.
This is the go-to VPC architecture; it is a good starting place for AWS network design. For example, when using the AWS CDK, this is the infrastrucure that is created when you use the VPC construct with its default options.
All that having been said, using NAT Gateways is expensive, and there are often ways around it. Some possibilities are:
- Use only public subnets
- Even with private subnets, you can access AWS services through VPC Endpoints.
Parameters
Parameters:
Param | Value |
---|---|
DeploymentName | test |
DeploymentName
DeploymentName: Type: String Description: A name for this deployment
A deployment is a deployed application, potentially comprised of many
CloudFormation stacks. This is sometimes called an "environment", but that is an
overloaded and confusing term. Use the DeploymentName
to indicate which
logical deployment a stack belongs to.
If a deployment is completely specified by exactly one CloudFormation template,
the DeploymentName
and the AWS::StackName refer to the same things. In that
case, consider not using a DeploymentName
parameter.
VPCCIDR: Description: CIDR range for this VPC Type: String Default: 10.192.0.0/16 PublicSubnet1CIDR: Description: CIDR range for public subnet in 1st AZ Type: String Default: 10.192.10.0/24 PublicSubnet2CIDR: Description: CIDR range for public subnet in 2nd AZ Type: String Default: 10.192.11.0/24 PrivateSubnet1CIDR: Description: CIDR range for private subnet in 1st AZ Type: String Default: 10.192.20.0/24 PrivateSubnet2CIDR: Description: CIDR range for private subnet in 2nd AZ Type: String Default: 10.192.21.0/24
Resources
Resources:
VPC
VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VPCCIDR EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Ref DeploymentName
Internet Gateway
InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Ref DeploymentName InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC
Public Subnets
PublicSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [0, !GetAZs ""] CidrBlock: !Ref PublicSubnet1CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub "${DeploymentName} Public (AZ1)" PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet1
PublicSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [1, !GetAZs ""] CidrBlock: !Ref PublicSubnet2CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub "${DeploymentName} Public (AZ2)" PublicSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet2
Private Subnets
There are two differences between "public" and "private" subnets:
Public subnets must have a route to an Internet Gateway.
This allows internet traffic to flow between the internet and the instance. Note: this isn't actually a quality of the subnet itself; rather, it's a matter of what routes are in the Route Table that the subnet is associated with.
Public subnets often specify
MapPublicIpOnLaunch: true
.This tells AWS to assign public IP addresses to EC2 instances in the subnet. This is required if you want to be able to
ssh user@$INSTANCE_IP
. Which is usually what you want, but one could imagine a situation when public IP addresses wouldn't be necessary.Private subnets can also specify
MapPublicIpOnLaunch: true
. Internet-valid IP addresses would be assigned to instances, but there would be no route from the internet to those intstances. That could be useful if, say, you want to be able to make a subnet temporarily public by temporarily adding a route to the Internet Gateway.
PrivateSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [0, !GetAZs ""] CidrBlock: !Ref PrivateSubnet1CIDR Tags: - Key: Name Value: !Sub "${DeploymentName} Private (AZ1)" PrivateSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable1 SubnetId: !Ref PrivateSubnet1
PrivateSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [1, !GetAZs ""] CidrBlock: !Ref PrivateSubnet2CIDR Tags: - Key: Name Value: !Sub "${DeploymentName} Private (AZ2)" PrivateSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable2 SubnetId: !Ref PrivateSubnet2
Public Route Table
PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${DeploymentName} Public"
DefaultPublicRoute: Type: AWS::EC2::Route DependsOn: InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway
Private Route Tables
PrivateRouteTable1: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${DeploymentName} Private (AZ1)"
DefaultPrivateRoute1: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable1 DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway1
PrivateRouteTable2: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${DeploymentName} Private (AZ1)"
DefaultPrivateRoute2: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable2 DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway2
NAT Gateways
NatGateway1EIP: Type: AWS::EC2::EIP DependsOn: InternetGatewayAttachment Properties: Domain: vpc NatGateway1: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NatGateway1EIP.AllocationId SubnetId: !Ref PublicSubnet1
NatGateway2EIP: Type: AWS::EC2::EIP DependsOn: InternetGatewayAttachment Properties: Domain: vpc NatGateway2: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NatGateway2EIP.AllocationId SubnetId: !Ref PublicSubnet2
Outputs
Outputs: VPCID: Description: A reference to the created VPC Value: !Ref VPC Export: Name: !Sub "${DeploymentName}-VPCID" VPCCIDR: Description: The VPC CIDR range Value: !GetAtt VPC.CidrBlock Export: Name: !Sub "${DeploymentName}-VPCCIDR" PublicSubnet1: Description: The public subnet in the 1st AZ Value: !Ref PublicSubnet1 Export: Name: !Sub "${DeploymentName}-PublicSubnet1" PublicSubnet2: Description: The public subnet in the 2nd AZ Value: !Ref PublicSubnet2 Export: Name: !Sub "${DeploymentName}-PublicSubnet2" PrivateSubnet1: Description: The private subnet in the 1st AZ Value: !Ref PrivateSubnet1 Export: Name: !Sub "${DeploymentName}-PrivateSubnet1" PrivateSubnet2: Description: The private subnet in the 2nd AZ Value: !Ref PrivateSubnet2 Export: Name: !Sub "${DeploymentName}-PrivateSubnet2"