Community VPC Module

The community EC2 instance is nice, but the VPC community module really helps us in an obvious way - it simplifies our own configuration.

First, the community VPC module creates an IGW and NAT Gateway for us, so we can go ahead and delete the gateways.tf file completely. This module intelligently creates route tables for public vs private subnets as well.

Second, we can use our "sub-module" pattern to still create security groups in our own vpc module while simplifying the rest (by using the community module).

File variables.tf

We first update (rewrite) the module/vpc/variables.tf file to add a variable for the availability zones we need, and to simplify the variables for subnets.

1variable "infra_env" {
2 type = string
3 description = "infrastructure environment"
4}
5 
6variable "vpc_cidr" {
7 type = string
8 description = "The IP range to use for the VPC"
9 default = "10.0.0.0/16"
10}
11 
12# New variable
13variable "azs" {
14 type = list(string)
15 description = "AZs to create subnets into"
16}
17 
18# Simplified Variable
19variable "public_subnets" {
20 type = list(string)
21 description = "subnets to create for public network traffic, one per AZ"
22}
23 
24# Simplified Variable
25variable "private_subnets" {
26 type = list(string)
27 description = "subnets to create for private network traffic, one per AZ"
28}

File main.tf

We'll next update (rewrite) file modules/vpc/main.tf to use the VPC community module.

1module "vpc" {
2 source = "terraform-aws-modules/vpc/aws"
3 version = "2.77.0"
4 
5 name = "cloudcasts-${var.infra_env}-vpc"
6 cidr = var.vpc_cidr
7 
8 azs = var.azs
9 
10 # Single NAT Gateway, see docs linked above
11 enable_nat_gateway = true
12 single_nat_gateway = true
13 one_nat_gateway_per_az = false
14 
15 private_subnets = var.private_subnets
16 public_subnets = var.public_subnets
17 
18 tags = {
19 Name = "cloudcasts-${var.infra_env}-vpc"
20 Project = "cloudcasts.io"
21 Environment = var.infra_env
22 ManagedBy = "terraform"
23 }
24}

This setup follows the same conventions we used, including creating a single NAT gateway (one option of a few strategies the module provides).

File outputs.tf

We'll next update (rewrite) file modules/vpc/outputs.tf to use outputs from the new module.

1output "vpc_id" {
2 value = module.vpc.vpc_id
3}
4 
5output "vpc_cidr" {
6 value = module.vpc.vpc_cidr_block
7}
8 
9output "vpc_public_subnets" {
10 value = module.vpc.public_subnets
11}
12 
13output "vpc_private_subnets" {
14 value = module.vpc.private_subnets
15}
16 
17output "security_group_public" {
18 value = aws_security_group.public.id
19}
20 
21output "security_group_private" {
22 value = aws_security_group.private.id
23}

Note te use of the "module.vpc" syntax and how we simplified the output a bit.

File security.tf

We update file modules/vpc/security.tf as needed to use the module output to get some needed values.

1 ###
2 # Public Security Group
3 ##
4  
5 resource "aws_security_group" "public" {
6 name = "cloudcasts-${var.infra_env}-public-sg"
7 description = "Public internet access"
8- vpc_id = aws_vpc.vpc.id
9+ vpc_id = module.vpc.vpc_id
10  
11 tags = {
12 Name = "cloudcasts-${var.infra_env}-public-sg"
13 Role = "public"
14 Project = "cloudcasts.io"
15 Environment = var.infra_env
16 ManagedBy = "terraform"
17 }
18 }
19  
20 resource "aws_security_group_rule" "public_out" { ...
21 type = "egress"
22 from_port = 0
23 to_port = 0
24 protocol = "-1"
25 cidr_blocks = ["0.0.0.0/0"]
26  
27 security_group_id = aws_security_group.public.id
28 }
29  
30 resource "aws_security_group_rule" "public_in_ssh" {
31 type = "ingress"
32 from_port = 22
33 to_port = 22
34 protocol = "tcp"
35 cidr_blocks = ["0.0.0.0/0"]
36 security_group_id = aws_security_group.public.id
37 }
38  
39 resource "aws_security_group_rule" "public_in_http" {
40 type = "ingress"
41 from_port = 80
42 to_port = 80
43 protocol = "tcp"
44 cidr_blocks = ["0.0.0.0/0"]
45 security_group_id = aws_security_group.public.id
46 }
47  
48 resource "aws_security_group_rule" "public_in_https" {
49 type = "ingress"
50 from_port = 443
51 to_port = 443
52 protocol = "tcp"
53 cidr_blocks = ["0.0.0.0/0"]
54 security_group_id = aws_security_group.public.id
55 }
56  
57 ###
58 # Private Security Group
59 ##
60  
61 resource "aws_security_group" "private" {
62 name = "cloudcasts-${var.infra_env}-private-sg"
63 description = "Private internet access"
64- vpc_id = aws_vpc.vpc.id
65+ vpc_id = module.vpc.vpc_id
66  
67 tags = {
68 Name = "cloudcasts-${var.infra_env}-private-sg"
69 Role = "private"
70 Project = "cloudcasts.io"
71 Environment = var.infra_env
72 ManagedBy = "terraform"
73 }
74 }
75  
76 resource "aws_security_group_rule" "private_out" { ...
77 type = "egress"
78 from_port = 0
79 to_port = 0
80 protocol = "-1"
81 cidr_blocks = ["0.0.0.0/0"]
82  
83 security_group_id = aws_security_group.private.id
84 }
85  
86 resource "aws_security_group_rule" "private_in" {
87 type = "ingress"
88 from_port = 0
89 to_port = 65535
90 protocol = "-1"
91- cidr_blocks = [aws_vpc.vpc.cidr_block]
92+ cidr_blocks = [module.vpc.vpc_cidr_block]
93  
94 security_group_id = aws_security_group.private.id
95 }

File cloudcasts.tf

Finally we can update our root module as needed to use our updated VPC module.

1 module "ec2_app" {
2 source = "./modules/ec2"
3  
4 infra_env = var.infra_env
5 infra_role = "web"
6 instance_size = "t3.small"
7 instance_ami = data.aws_ami.app.id
8 # instance_root_device_size = 12
9- subnets = keys(module.vpc.vpc_public_subnets)
10+ subnets = module.vpc.vpc_public_subnets
11 security_groups = [module.vpc.security_group_public]
12 tags = {
13 Name = "cloudcasts-${var.infra_env}-web"
14 }
15 create_eip = true
16 }
17  
18 module "ec2_worker" {
19 source = "./modules/ec2"
20  
21 infra_env = var.infra_env
22 infra_role = "worker"
23 instance_size = "t3.large"
24 instance_ami = data.aws_ami.app.id
25 instance_root_device_size = 20
26- subnets = keys(module.vpc.vpc_private_subnets)
27+ subnets = module.vpc.vpc_private_subnets
28 security_groups = [module.vpc.security_group_private]
29 tags = {
30 Name = "cloudcasts-${var.infra_env}-worker"
31 }
32 create_eip = false
33 }
34  
35 module "vpc" {
36 source = "./modules/vpc"
37  
38 infra_env = var.infra_env
39 vpc_cidr = "10.0.0.0/17"
40  
41+ azs = ["us-east-2a", "us-east-2b", "us-east-2c"]
42+ public_subnets = slice(cidrsubnets("10.0.0.0/17", 4, 4, 4, 4, 4, 4), 0, 3)
43+ private_subnets = slice(cidrsubnets("10.0.0.0/17", 4, 4, 4, 4, 4, 4), 3, 6)
44 }

This uses the cidrsubnets and the slice functions to generate subnets for us.

To use the new module, we need to init it first:

1terraform init
2terraform plan -var-file=variables.tfvars
3terraform apply -var-file=variables.tfvars

Don't miss out

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