VPC is the topic that flies under the radar of many Software Developers, despite being present in every AWS account (well, maybe not for accounts created before 2009...but that's unlikely). There are a few reasons for this I can think of:
Despite those somewhat justified arguments, most developers that deal with AWS would benefit from digging deeper into VPC setup and its configuration. Here are my counterpoints:
So let's take a careful look into AWS networking from the software engineering perspective. This post requires from the reader a very basic level of familiarity with AWS terminology and networking: you need to understand things like regions and availability zones, and have an idea of what an IP address is.
To make this post a bit less abstract, we will configure a VPC for a common real-world scenario: distributed application running in two availability zones in a single region consisting of public EC2 instances serving a web-application and private EC2 instances hosting a database. You can improve on the architecture by using RDS, adding a loadbalancer, configuring VPC peering with other regions, etc., but we will leave those topics out of the scope of this post - the topic at hand is complicated enough.
VPC stands for Virtual Private Cloud, which is a very apt name for a service, unlike some other AWS services I can think of...
In other words, the idea of VPC is to give the user a granular set of controls over what networks requests are allowed to reach its services from the outside, and how to route the requests inside the cloud infrastructure.
VPC operates on the regional level, meaning that if you want your application to span multiple regions you will need to perform VPC setup for each region separately and configure a VPC peering between them.
Fres AWS accounts come with the default VPC preconfigured. You can’t really start, for example, an EC2 instance without any VPC at all, so default VPC is where the new instances get launched into if nothing else is configured (hence the „default“ name).
This way amazon manages to balance user-friendliness with flexibility, and that’s why launching an EC2 instance on a „fresh“ account works out of the box. The problem is, however, that the default VPC is not tailored in any way to the needs of your application, so it will allow all incoming traffic to all existing instances.
Correctly configured Security Groups help somewhat protect your resources from completely exposing their ports to the public, and to be frank they are often completely sufficient to protect a simple app. However, as the infrastructure grows to accommodate more distinct components and services with complicated access patterns between them, you might want to invest in a more delicate access configuration.
When creating a custom VPC, there are two important decisions to make:
Technically, nothing stops you from choosing any IP range you‘d like; however, if your private IP matches the IP of anything on the public internet you won’t be able to access it from within the VPC. For this reason, internal networks are configured to use the subset of one of the following reserved IP ranges:
- 16 million private IP's in total, the default choice for cloud hosting
- almost 1 million IP's
172.16.0.0 – 172.31.255.255
- 65536 IP‘s, commonly seen on home routers
192.168.0.0 – 192.168.255.255
For our VPC we want to have around 4000 IP's available in
range. We can’t enter those numbers directly into the VPC configuration, we need to translate them into a CIDR notation first. CIDR is just a special format to describe an IP sub-range concisely. You can use IP subnet calculators like cidr.xyz (for IPv4) and cidrv6.xyz (for IPv6) to get an intuition into how CIDR works. CIDR value for our example is
We will also enable IPv6 on our VPC. AWS VPC's use IPv4 as a default addressing format, IPv6 configuration is completely optional. The approach AWS takes to IPv6 routing is quite different than what we used to for IPv4. IPv6 spec defines address range
to be private but its used much less frequently than
in IPv4. IPv6 address space is just so large, that AWS simply allocates /56 public address range from his own address pool to your VPC and calls it a day.
For this reason, there are no "private" IPv6 addresses in AWS. IPv6 range allocated to every VPC in AWS contains 2^72 addresses, which will be more than enough for any VPC ever. Just to put the number into perspective, it's enough to assign an IPv6 address to each grain of sand on the planet. The ability to assign globally unique addresses to each of your resources makes network architecture in many ways simpler, but at the same time, it makes secure routing configuration even more important.
We'll discuss the reasons for it in more detail in a subnet section.
As a summary, here's what our initial VPC configuration looks like:
Internet Gateway is an abstract VPC component that represents internet access. The only purpose of the Internet Gateway is to serve as a source/target in the VPC routing configuration. You can’t really configure it in any meaningful way, apart from attaching it to VPC. Internet Gateway is present from the start in the default VPC, but it won't be created for any custom VPC you create. Since we want to be able to access some resources in our VPC from the Internet, we need to create a new Internet Gateway and attach it to our VPC.
The cornerstone of understanding VPC is understanding the concept of subnets. In short, subnet is a way of splitting your VPC into small manageable chunks, each of which contains instances and services with a similar type of network access. The „type of network access“ here is up to you to define, but in our case, we want a „private“ subnets for database instances and a public subnet for our webserver. An important detail to remember is that subnets can't span multiple availability zones. In our case we want our application to work in two different availability zones, this means we need "private" and "public" subnet in each of them, 4 subnets in total. Fortunately, it's easy to configure functionally similar subnets by attaching them to the same routing table (more about them in the next section).
Subnets are defined in CIDR notation, which we already discussed in VPC section. AWS makes sure that you will not define two overlapping subnets, because that would break our routing logic. For each subnet, AWS reserves first four and last one IP's for its internal tasks, so it's common to configure the size of the subnet of at least /24. This size makes our IP addresses look "cleaner": we can distinguish subnet based on the value of the second-to-last octet in the IP address. You can play around with a subnet calculator to understand why that's the case. For IPv6, AWS set subnet size to /64 (not configurable).
You can configure the subnet to automatically map a public IPv4 address for all instances launched into it - we will need to do this for our public subnet.
Considering anything above, our 4 new subnets would look something like this:
Route tables fill the role of the router for your VPC. They allow you to configure destinations for packets depending on their destination IP.
When you first create a VPC, a single "default" route table is created for you. It contains two rules needed to route private traffic internally within our VPC:
All subnets that we created are by default associated with this route table. It's a good security practice to make defaults as restricted as possible, so we will keep default route table private. For our public subnet we need to make a new table, and add routes to Internet Gateway there:
Now we just associate our public subnet with the new route table to make them really public.
nACL's are an additional level of protection that sits in front of your subnets. You can configure them by adding firewall rules operating on OSI layer 4. Default NACL configuration allows all traffic through, and it suits our use case. If you need to block a particular source IP or destination port in one of your subnets - NACL's would be a suitable tool to fulfill the task.
Security groups are the last level of defence that define which ports are open on your instance for TCP and/or UDP traffic, and from which sources to allow traffic in. Keep in mind that you need to configure inbound (targeted to instance) and outbound (coming from instance) rules separately. You should only allow the traffic on ports you know your app needs to operate, from sources you expect the requests to come. You can define the source as a CIDR or a reference to another security group. Let's say our webserver serves HTTP traffic on port 80, and it communicates with database through TCP connection on port 12345. In that case, we need two security groups:
At this point everything is ready and we can place new instances in our VPC, we just need to provide a correct VPC, subnet and security group during when starting:
Graphically, the VPC looks more or less like this:
There are quite a few moving parts in AWS VPC's, but all of them have distinct responsibilities and fit well into the whole picture once you get to know them. If you want some additional challenge, you can:
Previously published at https://scorpil.com/post/aws-vpc/
Create your free account to unlock your custom reading experience.