Creating our VPC Module

How you divide up the VPC into subnets can vary quite a lot, and should be driven by your needs.

In our case, we'll build a VPC with 2 subnets per availability zone. One subnet will be a "private" subnet, and the second will be a "public" subnet.

The private subnets will only allow network traffic among servers and resources within the private network. They'll (eventually) use a NAT gateway so the servers can talk to the outside internet, without the outside internet being able to reach those servers.

The public subnets will be able to communicate through both the private network and the public network. Instances in public subnets are reachable from the outside (public) internet.

VPC IP Address Ranges

We'll use this tool to see how we can split up networks to see how we'll create a smaller VPC (using half the possible IP addresses we can possibly use), which can help with VPC peering in the future.

We can also use that tool to see how we'll split our VPC available IP addresses into subnets.

A VPC's possible IP address range (in our example) is 10.0.0.0/16 We can split that in half using /17 to create these 2 halves:

  1. 10.0.0.0/17 (10.0.0.1 - 10.0.127.254)
  2. 10.0.128.0/17 (10.0.128.1 - 10.0.255.254)

Availability Zones

We'll create 2 subnets per AZ (one public, one private).

To see the availability zones that exist for your account in any given region, use the following command:

1aws --profile=cloudcasts ec2 describe-availability-zones --region us-east-2

For bonus points, you can use the --query flag (which uses jmespath under the hood) to return only the AZ names:

1aws --profile=cloudcasts ec2 describe-availability-zones \
2 --region us-east-2 \
3 --query 'AvailabilityZones[].ZoneName'
4 
5# Output:
6[
7 "us-east-2a",
8 "us-east-2b",
9 "us-east-2c"
10]

The Module

The following shows the variables.tf, main.tf, and outputs.tf files that we create for the new vpc module.

File variables.tf:

1variable "public_subnet_numbers" {
2 type = map(number)
3 
4 description = "Map of AZ to a number that should be used for public subnets"
5 
6 default = {
7 "us-east-2a" = 1
8 "us-east-2b" = 2
9 "us-east-2c" = 3
10 }
11}
12 
13variable "private_subnet_numbers" {
14 type = map(number)
15 
16 description = "Map of AZ to a number that should be used for private subnets"
17 
18 default = {
19 "us-east-2a" = 4
20 "us-east-2b" = 5
21 "us-east-2c" = 6
22 }
23}
24 
25variable "vpc_cidr" {
26 type = string
27 description = "The IP range to use for the VPC"
28 default = "10.0.0.0/16"
29}
30 
31variable "infra_env" {
32 type = string
33 description = "infrastructure environment"
34}

File main.tf:

1# Create a VPC for the region associated with the AZ
2resource "aws_vpc" "vpc" {
3 cidr_block = var.vpc_cidr
4 
5 tags = {
6 Name = "cloudcasts-${var.infra_env}-vpc"
7 Project = "cloudcasts.io"
8 Environment = var.infra_env
9 ManagedBy = "terraform"
10 }
11}
12 
13# Create 1 public subnets for each AZ within the regional VPC
14resource "aws_subnet" "public" {
15 for_each = var.public_subnet_numbers
16 
17 vpc_id = aws_vpc.vpc.id
18 availability_zone = each.key
19 
20 # 2,048 IP addresses each
21 cidr_block = cidrsubnet(aws_vpc.vpc.cidr_block, 4, each.value)
22 
23 tags = {
24 Name = "cloudcasts-${var.infra_env}-public-subnet"
25 Project = "cloudcasts.io"
26 Role = "public"
27 Environment = var.infra_env
28 ManagedBy = "terraform"
29 Subnet = "${each.key}-${each.value}"
30 }
31}
32 
33# Create 1 private subnets for each AZ within the regional VPC
34resource "aws_subnet" "private" {
35 for_each = var.private_subnet_numbers
36 
37 vpc_id = aws_vpc.vpc.id
38 availability_zone = each.key
39 
40 # 2,048 IP addresses each
41 cidr_block = cidrsubnet(aws_vpc.vpc.cidr_block, 4, each.value)
42 
43 tags = {
44 Name = "cloudcasts-${var.infra_env}-private-subnet"
45 Project = "cloudcasts.io"
46 Role = "private"
47 Environment = var.infra_env
48 ManagedBy = "terraform"
49 Subnet = "${each.key}-${each.value}"
50 }
51}

File outputs.tf:

1output "vpc_id" {
2 value = aws_vpc.vpc.id
3}
4 
5output "vpc_cidr" {
6 value = aws_vpc.vpc.cidr_block
7}
8 
9output "vpc_public_subnets" {
10 # Result is a map of subnet id to cidr block, e.g.
11 # { "subnet_1234" => "10.0.1.0/4", ...}
12 value = {
13 for subnet in aws_subnet.public :
14 subnet.id => subnet.cidr_block
15 }
16}
17 
18output "vpc_private_subnets" {
19 # Result is a map of subnet id to cidr block, e.g.
20 # { "subnet_1234" => "10.0.1.0/4", ...}
21 value = {
22 for subnet in aws_subnet.private :
23 subnet.id => subnet.cidr_block
24 }
25}

File cloudcasts.tf:

Finally, we can put everything together and reference this module in our cloudcasts.tf file.

1module "vpc" {
2 source = "./modules/vpc"
3 
4 infra_env = var.infra_env
5 
6 # Note we are /17, not /16
7 # So we're only using half of the available
8 vpc_cidr = "10.0.0.0/17"
9}

Then we can init and plan to see our changes:

1terraform init
2terraform plan -var-file variables.tfvars

Don't miss out

Sign up to learn when new content is released! Courses are in production now.