Similar to public subnets, private subnets are also carved out from the same VPC CIDR block. The key difference lies in the routes that are added to these subnets. Functionally, they are used to host resources that will not be exposed to the internet. If you recall the AMI deployment from the previous chapter, we hosted the EC2 instance directly in the public subnet of the default VPC, by allocating a public IP. In real-life implementations, it’s not a good practice to expose the backend instances directly on the internet as it introduces architectural limitations around security, network ingress traffic inspection, and load balancing capabilities. So, resources such as backend servers, databases, and internal tools get deployed in these subnets:
Resources:
…
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs ” ]
CidrBlock: !Ref PrivateSubnet1CIDR
MapPublicIpOnLaunch: false
Tags:
– Key: Name
Value: !Sub ${EnvironmentName} Private Subnet (AZ1)
PrivateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
– Key: Name
Value: !Sub ${EnvironmentName} Private Routes (AZ1)
DefaultPrivateRoute1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable1
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway1
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation Properties:
RouteTableId: !Ref PrivateRouteTable1 SubnetId: !Ref PrivateSubnet1 …
If you observe the outgoing traffic routes for the private subnet, you will see that we are leveraging another AWS service called a NAT gateway, which we’ll cover next.
NAT gateways
The resources that are deployed in private subnets are not exposed to the internet but still need outgoing internet connectivity to be able to download support patches, third-party hosted libraries, and so on. To support this outbound traffic flow, we must modify network routes in these subnets. Since these resources don’t have a public IP attached to them, they need something to Network Address Translation (NAT) the requests when the packets go out. This is where NAT gateways come into thepicture. They allow resources hosted in private subnets to have outbound internet connectivity. They are hosted in the public subnets, though, mainly because they want to route traffic to the internet via the internet gateway:
Resources:
…
NatGateway1EIP:
Type: AWS::EC2::EIP
DependsOn: InternetGatewayAttachment
Properties:
Domain: vpc
NatGateway1:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGateway1EIP.AllocationId SubnetId: !Ref PublicSubnet1 …
An important CloudFormation construct that we’ll cover here is DependsOn. This is used to mark explicit dependencies between two resources defined in the template.
We also use an Elastic IP (EIP) here, which is a static public IP address whose life cycle is independent of the resource it is attached to. You can move these IPs across several different AWS resources, which means you don’t lose them when underlying resources are deleted. Big enterprises often follow IP whitelisting requirements with their customers. EIPs come in handy in such situations as you don’t want to lose the IPs that have gone through lengthy approval processes already.
Tip
It is now possible to transfer EIPs across multiple accounts. The target account has 7 hours to accept this transfer once initiated. Organizations benefit from this transferability when they go through organizational restructuring and disaster recovery procedures.
Stack outputs
Earlier, we discussed a best practice to keep your CloudFormation templates modular and organize them with ownership in mind. The network stack we just defined can be deployed and managed by the networking team, independent of the application teams. The application owners, however, will need to consume some resource identifiers from this stack’s output (VPC ID, for example) in their own IaC stack definitions. This is where we can leverage stackoutputs and make the outputs from this stack available for use by others:
…
Outputs:
VPC:
Description: A reference to the created VPC
Value: !Ref VPC
Export:
Name: !Join [ “-“, [ !Ref “AWS::StackName”, vpc-id ]]
PublicSubnet1:
Description: A reference to the public subnet in the 1st Availability Zone
Value: !Ref PublicSubnet1
Export:
Name: !Join [ “-“, [ !Ref “AWS::StackName”, public-subnet1 ] ]
PublicSubnet2:
Description: A reference to the public subnet in the 2nd Availability Zone
Value: !Ref PublicSubnet2
Export:
Name: !Join [ “-“, [ !Ref “AWS::StackName”, public-subnet2 ] ]
PrivateSubnet1:
Description: A reference to the private subnet in the 1st Availability Zone
Value: !Ref PrivateSubnet1
Export:
Name: !Join [ “-“, [ !Ref “AWS::StackName”, private subnet1 ] ]
PrivateSubnet2:
Description: A reference to the private subnet in the 2nd Availability Zone
Value: !Ref PrivateSubnet2
Export:
Name: !Join [ “-“, [ !Ref “AWS::StackName”, private subnet2 ] ]
Typically, there should only be a handful of resources that you want to share with others. For example, it does not help much if we expose the internet gateway or the NAT gateway IDs since they will always be managed by the networking team.
Now that we’ve covered all the CloudFormation resource definitions, it is time to roll out the network stack in our AWS account.