Plan Your VPC Usage
AWS is complex. Sign up for free, useful lessons like this.
A VPC is a "Virtual Private Cloud", which is just a private network. You create a private network so that your servers (and other stuff) within that network can communicate with each other securely.
Stuff residing within different VPCs can't talk to each other, unless you want them to. Herein lie the caveats and foot guns that are the topic of this article.
The Default VPC
Your account comes with a default VPC so that you can create resources (servers and stuff) without the extra friction of having to think about networking.
This is convenient, but there's a few reasons why not to use the default VPC:
- It's not using the IP address ranges you likely want to use
- It only sets up "public subnets", and you probably want private ones
You can adjust the default VPC (or even delete it), but there are better options.
Keeping Your Options Open
The tl;dr of caring about your VPC boils down to optionality. If you aren't doing any planning, you may run into some foot-guns. Here's what you we want to avoid:
- Restricting our ability to peer VPC's together
- Having no idea what IP address ranges are available to use
You may think "I'll never need VPC peering!". That's possible! But for "serious" AWS usage (wherein "serious" is something along the lines of "we pay staff to manage cloud stuff"), peering often becomes convenient or even necessary.
Point 2 follows from point 1, so if you're in the "I don't use AWS like this" camp, you can throw this article into your bookmarks (/dev/null
) for later.
VPC Peering
VPC's are often used as a way to segment parts of your "stuff", for example development vs production workloads, or, say, the finance bean-counters step functions vs the application bean-spenders servers.
Here's the deal tho - some day, finance is going to want to query your application database. But they can't! Their stuff lives in another VPC!
This solution to this is VPC Peering, which allows you to connect those private networks. When used, resources within the peered VPC's can talk to each other over the private network.
Enter foot-gun, stage right: If your VPC's are using the same IP address range, they can't be peered together. Imagine some code querying a database at 10.1.12.34
when that IP address exists in both VPCs. Pandemonium!
Since we can't predict the future, and future is actively trying to screw us, we want to plan ahead!
The objective: Don't create VPCs with overlapping IP addresses without a damn good reason.
Tracking IP Usage
If you make a lot of VPC's for different business units (or whatever), it's pretty easy to lose track of which VPC ranges are available.
This is especially true if you're not consistent with your VPC sizes. For example, you might create a VPC of size 10.0.0.0/16
(~56k IP addresses), and then later decide the finance team only needs a few IP's and create something like 10.1.0.0/24
(256 IPs).
Inconsistently sized VPCs make it very hard to track what IP address ranges are still available for use, and makes it less efficient, as you might leave "gaps" of unused addresses. Gaps are both hard to find, and may not be easily used later.
Having a plan and a way to track IP address usage ahead of time means you'll be able to avoid painful issues later.
So, lets see what our options are.
Deciding on IP Addresses
There are 3 IP ranges we can use in our VPCs (Docs here). The tl;dr is that these are available to you:
Total CIDR | IP Range | Total IPs |
---|---|---|
10.0.0.0/8 | 10.0.0.0 - 10.255.255.255 | 16,777,216 |
172.16.0.0/12† | 172.16.0.0 - 172.31.255.255 | 1,048,576 |
192.168.0.0/16†† | 192.168.0.0 - 192.168.255.255 | 65,536 |
†AWS doesn't want you to use 172.17.0.0/16
because some internal services use it. This forbidden range is, annoyingly, in the middle of the 172.16.0.0/12
block. They won't stop you from using it though, so good luck with that, hope you randomly read that section of the docs ✌️.
††The largest IPv4 size you can create of any one VPC is /16
, which is 65,536 IPs (docs). That means 192.168.0.0/16
is kinda lame - it can be used for one VPC or maybe special cases where you want to create smaller VPCs.
The Verdict:
Use the 10.0.0.0/8
range as your "go to" for VPC creation. It gives us the most optionality - there's a LOT of IP addresses, and there's no caveats.
Carving Out VPCs
So, knowing all of that, we want to take that large range 10.0.0.0/8
and decide how to carve it out. The goal is to be able to create as many VPCs as possible, and decide how to divide them up for our "business model" (or whatever).
This likely means segmenting environments and perhaps business units (looking at you, Finance).
But first, Math
Luckily, all we need to do is divide by 2, and we don't really need to understand IP addresses.
Here's the bare minimum you need to know - it'll get you pretty far.
Facts:
- When using CIDR notation, there's an IP address, and a
/
denoting a "prefix length":
<ip-address>/<prefix-length
, e.g. 10.0.0.0/8
.
- The smaller the prefix length (
/8
), the larger the IP address range it denotes. - Every time you add
1
to the prefix length, the IP address range is cut in half
CIDR | Range | Number of IPs |
---|---|---|
10.0.0.0/8 | 10.0.0.0 - 10.255.255.255 | 16,777,216 |
10.0.0.0/9 | 10.0.0.0 - 10.127.255.255 | 8,388,608 |
10.0.0.0/10 | 10.0.0.0 - 10.63.255.255 | 4,194,304 |
10.0.0.0/11 | 10.0.0.0 - 10.31.255.255 | 2,097,152 |
10.0.0.0/12 | 10.0.0.0 - 10.15.255.255 | 1,048,576 |
10.0.0.0/13 | 10.0.0.0 - 10.7.255.255 | 524,288 |
10.0.0.0/14 | 10.0.0.0 - 10.3.255.255 | 262,144 |
10.0.0.0/15 | 10.0.0.0 - 10.1.255.255 | 131,072 |
10.0.0.0/16 | 10.0.0.0 - 10.0.255.255 | 65,536 |
So, great - the math isn't too bad…except…
We don't know yet how to make contiguous blocks of IP addresses. For example, 10.0.0.0/9
is half of 10.0.0.0/8
, so we should get 2 IP address ranges out of that. What are the 2 (contiguous) halves?
Rather than do math, let's use some tools. My go-to is actually Terraform. It has a function cidrsubnets
that you can use to calculate contiguous IP address blocks (subnets). It works like this:
1#> terraform console2cidrsubnets("10.0.0.0/8", 1, 1)3 4tolist([5 "10.0.0.0/9",6 "10.128.0.0/9",7])
We got 2 /9
CIDR blocks there - each is one half of 10.0.0.0/8
.
- First half:
10.0.0.0 - 10.127.255.255
- Second half:
10.128.0.0 - 10.255.255.255
But what did we do there? The arguments to the function are:
- The CIDR you're splitting up into subnets
- An arbitrary set of numbers. For each number, you'll get a new subnet. The value of the number is how many "bits" you add to the base CIDR prefix - so if we started with
/8
, and foreach subnet we want, we add 1 bit to get subnets of/9
.
To firm that up, let's see another example - let's divide a /9
block into /16
subnets:
1#> terraform console 2cidrsubnets("10.0.0.0/9", 7, 7, 7, 7, 7, 7, 7, 7) 3 4tolist([ 5 "10.0.0.0/16", 6 "10.1.0.0/16", 7 "10.2.0.0/16", 8 "10.3.0.0/16", 9 "10.4.0.0/16",10 "10.5.0.0/16",11 "10.6.0.0/16",12 "10.7.0.0/16",13])
To get subnets of size /16
, we take the base bit /9
and add 7 (9+7=16). We told cidrsubnets
it to calculate 8 contiguous /16
subnets by passing it eight arguments of value 7
.
We could create 128 subnets of size /16
out of this /9
range! If we tried to create 129 subnets, we'd get an error (since that's out of range of the base CIDR 10.0.0.0/9
).
Typing in 7
128 times sounds exhausting. Luckily it's easy to see the pattern there - the last subnet our example would allow is 10.127.0.0/16
. That's not to say that 10.128.0.0/16
doesn't exist - it does! It's just not within the 10.0.0.0/9
range. Instead it's within the 10.128.0.0/9
CIDR range.
So, we are gonna do math?
Not really - we just need to know enough to divide a CIDR a few times and try to cram in as many /16
CIDR's as possible. Why /16
? Well, remember, the largest IPv4 VPC size you can create is /16
, which is 65,536 IPs (docs).
An Example VPC Plan
Planning out your VPC usage does not need to be hard - we'll make a simple plan.
First, we'll say that we may operate out of 2 regions - us-east-2
and eu-central-1
.
Then we'll say that within each region, we want to support three environments - dev, staging, and production. To keep things simpler, we'll leave poor Finance alone and pretend we don't care about other business units.
Here's what an IP address breakdown might look like.
We start with the most IP addresses we can get (10.0.0.0/8
) and devise a strategy that lets us plan out how we want to get to a bunch of /16
subnets (each of which will be a VPC).
We have 2 regions we might operate in. CIDR blocks conveniently split themselves into two by just adding a bit to the prefix, so we'll use a /9
per region. Then we can divide each region into our 3 environments.
Having 3 environments is awkward in terms of IP addressing (continuously dividing by 2 is easiest!). What I did here is divide each /9
into 4 blocks and allocated 2 of them to production. In my case, Production was the most likely environment to need more VPCs.
So, we have a plan! We have plenty of VPCs to play with (for most use cases) and so we can utilize them knowing that we can peer any VPC's together across any environment. If all else fails, we also have the "escape hatch" of the other 2 private network IP ranges.
Lastly, we have the neat ability to look at any given IP address and say "Oh, that's in us-east-2
in the dev
environment".
IPAM
You can take this a step farther and use AWS's IPAM service to plan our your VPC's. You create "pools" of IP address blocks, and when you create a VPC, you can allocate it from one of these pools.
This helps you track what VPC's are "compliant" with your rules and what CIDR's are (and are not) available to use.
The service works across multi-account AWS organizations as well! This video does a decent job of showing it off.
An Individual VPC
There's a course here on VPC Basics already, but it doesn't go into how you may want to generate subnets within your VPC.
Typically I use this Terraform VPC module, which has some guidance - essentially dividing a VPC into subnets like "public", "private", "intra", "database", and more. It manages creating internet gateways, NAT gateways, and route tables - pretty handy!
What you need to decide on is how large a subnet will be. What I've usually done is create a set of Public, a set of Private, and set of Database subnets.
I'll typically make each subnet size /20
, which is 4,096
addresses. This means we can fit 16 subnets into a VPC of size /16
(4096*16 = 65,536).
You typically want to spread your subnets across the availability zones within a region so that your resources (servers) can be created in all available AZ's, thus reducing the chance that issues in one AZ can bring down all of your infrastructure.
Some regions only have 3 AZ's, which doesn't divide evenly into 16. That's generally fine - you can decide to "over-index" into one AZ or just waste a few IP addresses. For example, in us-east-2
, I'll setup a VPC like this:
So, we've seen how we might divide the available IP address space into many VPC's (without overlapping IPs). This gives us the most flexibility and protection against future issues, and gives us guidance on how we "spend" IP addresses.
Even if the plan is just a spreadsheet (rather than something enforced in code), it's worth thinking about how you want to structure your private networks when going in production on AWS.