When starting to operate in Amazon Web Services (AWS) you have to decide whether to utilize the Default Virtual Private Cloud (VPC) or define your own VPC. The Default VPC typically sets itself up using the 172.31.0.0/16 CIDR block and a single tier of available subnets in all Availability Zones (AZ) with the region. This is fine for general use but to setup a proper tiered security model you’ll want to define your own VPC. Another factor is if you wish to connect your VPC with your on-premise network either by VPC VPN Connection or Direct Connect (DX) you’ll want to control what CIDR block your VPC assigns from to avoid IP collisions.
Many VPC setups will go with a 3-tier solution, one that provides a ‘Public’ tier in which to deploy Elastic Load Balancers (ELB/ALB/NLB), a ‘Private’ tier in which to deploy their application servers that the load balancers sit in front of, and finally a ‘Data’ tier to place peristent data such as Relational Database Servers (RDS), ElastiCache (EC), ElasticSearch (ES) or even Elastic File Systems (EFS) that can be reached by the application servers in the ‘Private’ tier but not directly from the internet or anything in the ‘Public’ tier. The next decision is how many AZs you’re going to make use. Within AWS a given region has multiple AZs which are independent data centers with high-speed cross-connections. These multiple AZs in a region along with the multiple regions available give you high-availability if deployed and utilized properly. For many services you will require at least 2 AZs and any region must have at least 2 AZs to become a region.
For simplicity sake I decided to work with a 2-tier solution and forego the ‘Data’ tier but have left room to expand and add that tier at a later date. I also opted to go with 3 AZs. While some most regions have at least 3 AZs and some even have as many as 6, going with 3 ensured that my CloudFormation template would be able to function in any region I picked. I went with a /16 CIDR block under the available 10-net RFC1918 private IP space. For each of my 6 subnets then I decided to make use of a /20 CIDR blocks which provides for 4094 IP addresses in each which would be more than enough for my usage. You may find an online subnet calculator handy when making this decision if it’s not something you’re particularly strong at. For my uses you can see the results here using one such calculator. In another post I’ll cover how to make use of the CloudFormation Fn::Cidr function to simplify this even further in your templates.
So for my 3-tier VPC template I kept it simple with only 2 parameters. The first to pick the Class-B second octet of the VPC /16 CIDR block and the second to decide if the VPC would be dual-stacked and support IPv6 addresses within. Below you will find the template I used so it can be referenced to as we go deeper. You can also find it on GitHub in my aws-infra repository.
As an astute observer you may have noticed looking through the template that while there is an InternetGateway resource created on line 54 it is only used in the template as a property of the VPCGatewayAttachment on line 65 and then again as the GatewayId for the ‘Public’ routetables on lines 247, 255 & 263. This leaves the ‘Private’ subnets with no default route when this template is completed. This is because you have 2 options available to you. The first of which is to setup a NAT Gateway in each AZ that would be placed in the ‘Public’ subnet and then serve as the default route for the AZs ‘Private’ subnet. The second of which is to setup a VPC VPN Connection and route all ‘Private’ subnets out it as the default route. I went with the second option as the cost of a single VPN Connection worked out to just under $38/month while each NAT Gateway would work out to around $34/month or in my case over $100/month with 3 AZs. You could run only 1 NAT GW and route all 3 AZs out it but if you had an AZ outage you could potentially lose all external connectivity for your ‘Private’ tier. I do have my VPN Connection configured via a CloudFormation template which can be found in the GitHub repo that I will discuss deeper in another post at a later date.
Finally at the end of the template starting at line 421 we start the Outputs. These are all the relevant resources that you might need to later reference in other CloudFormation stacks so they all provide an export name that can be used to import them when needed. A few other added pieces of note would be the VPC Endpoints found on lines 265 & 275 for both Simple Storage Service (S3) and DynamoDB which allow your ‘Private’ tier to connect to both services without the need to traverse your external routing via NAT Gateway or VPN Connection.
When the stack is fully deployed you’ll have a 2-tier VPC with 3 ‘Public’ subnets in separate AZs and 3 ‘Private’ subnets in the same AZs. Each subnet has it’s own route table with only the ‘Public’ route tables having a default route at this time. There are also 2 Network ACLs setup for the ‘Public’ and ‘Private’ tiers which can be modified later as needed but by default allow all traffic unhindered. As I use the CloudFormation stack name (AWS::StackName) when exporting resources I find it important to follow a naming schema so my VPC stack name is prefixed with ‘vpc-‘ followed by an environment identifier. So my demo environment stack might be called ‘vpc-demo’ which I can use later for importing values into other stack templates that will deploy in that environment.
With this as a common base we’ll proceed in my next post on setting up the VPN Connection and then deploying an ECS cluster. I hope you find this insightful and look forward to hearing your thoughts. If you think you have a better solution then I welcome pull requests, and if you run into any problems feel free to open a bug report.