Menu
  • Home
  • About Me
  • Blog
  • Github
  • LinkedIn

Terraform Modules

July 6, 2022July 6, 2022

A terraform file can get very large very quickly as you add more and more resource blocks.

Custom modules are a great way of organizing your terraform code into logical pieces. You might have one module that handles the VPC set up, and another for EC2 instances. In this post, I will look at how to make custom modules in terraform.

Module Setup – Starting Point

  • main.tf: The primary entrypoint to the entire configuration.
  • variables.tf: Any input variables for the module. This allows the user running terraform to easily customize the configuration.
  • outputs.tf: Any outputs from the module. This allows the user running terraform to easily get data about any resources.

This is a terraform module, it’s the root module. This is all you need to start using terraform, but we want to organize our code a little better using modules. So we can add a new modules directory to the project and add other modules in there.

Create the following project structure:

VPC Module

The following terraform code defines resources for:

  • A VPC
  • One public and one private subnet
  • A route table
  • An internet gateway
  • A security group allowing access on port 80 from anywhere

Although each project will have different requirements, it will most likely need all of these pieces with some customizations.

Add the following code to modules/vpc/main.tf:

resource "aws_vpc" "custom_vpc" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "custom_public_subnet" {
  vpc_id            = aws_vpc.custom_vpc.id
  cidr_block        = "10.0.1.0/24"
}

resource "aws_subnet" "custom_private_subnet" {
  vpc_id            = aws_vpc.custom_vpc.id
  cidr_block        = "10.0.2.0/24"
}

resource "aws_internet_gateway" "custom_ig" {
  vpc_id = aws_vpc.custom_vpc.id
}

resource "aws_route_table" "public_rt" {
  vpc_id = aws_vpc.custom_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.custom_ig.id
  }
}

resource "aws_route_table_association" "public_1_rt_a" {
  subnet_id      = aws_subnet.custom_public_subnet.id
  route_table_id = aws_route_table.public_rt.id
}

resource "aws_security_group" "web_sg" {
  name   = "HTTP and SSH"
  vpc_id = aws_vpc.custom_vpc.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = -1
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Root Module

All of the VPC resources have been defined in modules/vpc/main.tf, but running terraform apply would do nothing because there’s nothing in the root module’s main.tf.

Add the following code to the root module’s main.tf:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.27"
    }
  }

  required_version = ">= 1.0.0"
}

provider "aws" {
  region = var.region
}

module "my_vpc" {
  source = "./modules/vpc"
}

The first part is just the basic aws setup code, but under that is a module block. This will tell terraform to add the resources from the modules/vpc module.

Add the following code to root module’s variables.tf file:

variable "region" {
  description = "AWS region"
  type        = string
  default     = "us-west-2"
}

Just to make it easy to change the region that’s used.

If you run terraform init to setup the project, then terraform apply, this will create all of the resources in the VPC module.

Module Variables

The CIDR blocks for the VPC and subnets are hardcoded into the VPC module.

To make these more dynamic using variables, add the following variables in modules/vpc/variables.tf:

variable "vpc_cidr" {
  description = "CIDR block for the entire VPC"
  type        = string
}

variable "public_sub_1_cidr" {
  description = "CIDR block for the public subnet"
  type        = string
}

variable "private_sub_1_cidr" {
  description = "CIDR block for the private subnet"
  type        = string
}

Then in modules/vpc/main.tf modify the resource blocks to use the variables:

resource "aws_vpc" "custom_vpc" {
  cidr_block = var.vpc_cidr
}

resource "aws_subnet" "custom_public_subnet" {
  vpc_id            = aws_vpc.custom_vpc.id
  cidr_block        = var.public_sub_1_cidr
}

resource "aws_subnet" "custom_private_subnet" {
  vpc_id            = aws_vpc.custom_vpc.id
  cidr_block        = var.private_sub_1_cidr
}

Now the CIDR values are coming from terraform variables, but where do we define the values for these variables?

Modify the root module’s main.tf module block:

module "my_vpc" {
  source = "./modules/vpc"

  vpc_cidr           = "10.0.0.0/16"
  public_sub_1_cidr  = "10.0.1.0/24"
  private_sub_1_cidr = "10.0.2.0/24"
}

If a sub-module defines variables in the variables.tf file, then the root module can pass in values when it defines the module block. And modules can be reused, so you could easily make multiple VPCs by just defining more of these blocks.

module "my_vpc_1" {
  source = "./modules/vpc"

  vpc_cidr           = "10.0.0.0/16"
  public_sub_1_cidr  = "10.0.1.0/24"
  private_sub_1_cidr = "10.0.2.0/24"
}

module "my_vpc_2" {
  source = "./modules/vpc"

  vpc_cidr           = "192.168.0.0/16"
  public_sub_1_cidr  = "192.168.1.0/24"
  private_sub_1_cidr = "192.168.2.0/24"
}

Module Outputs

The VPC is set up and we have a public subnet and security group. We could use this to deploy an ec2 instance running an HTTP server.

Modify the root’s main.tf file to create a new ec2 instance:

data "aws_ami" "amz_linux_2" {
  most_recent = true
  name_regex  = "amzn2-ami-hvm-2.*.1-x86_64-gp2"
  owners      = ["amazon"]
}

resource "aws_instance" "web_instance" {
  ami           = data.aws_ami.amz_linux_2.id
  instance_type = "t2.nano"

  subnet_id                   = module.my_vpc.public_subnet_id
  vpc_security_group_ids      = module.my_vpc.public_sg_id
  associate_public_ip_address = true

  user_data = <<-EOF
  #!/bin/bash -ex
  amazon-linux-extras install nginx1 -y
  systemctl enable nginx
  systemctl start nginx
  EOF
}

Now, we set up the VPC and EC2 instance running Nginx, we provided the subnet id and the security group ID from the resources that are defined in the VPC module.

To access VPC module resources, I need to output them from the module

resource "aws_instance" "web_instance" {
  ...

  subnet_id                   = module.my_vpc.public_subnet_id
  vpc_security_group_ids      = [module.my_vpc.public_sg_id]

  ...
}
output "public_subnet_id" {
  value = aws_subnet.some_public_subnet.id
}

output "public_sg_id" {
  value = aws_security_group.public_sg.id
}

I’m able to access the needed values from the VPC module’s outputs. If I run terraform apply, I will have an ec2 instance running nginx on the public subnet of the custom VPC.

EC2 Module

Let’s move the ec2 code into it’s own module

variable "public_sg_id" {
  description = "ID of the security group for the public subnet"
  type        = string
}

variable "public_subnet_id" {
  description = "ID of the public subnet"
  type        = string
}

output "public_ip" {
  value = aws_instance.web_instance.public_ip
}

data "aws_ami" "amz_linux_2" {
  most_recent = true
  name_regex  = "amzn2-ami-hvm-2.*.1-x86_64-gp2"
  owners      = ["amazon"]
}

resource "aws_instance" "web_instance" {
  ami           = data.aws_ami.amz_linux_2.id
  instance_type = "t2.nano"

  subnet_id                   = var.public_subnet_id
  vpc_security_group_ids      = [var.public_sg_id]
  associate_public_ip_address = true

  user_data = <<-EOF
  #!/bin/bash -ex
  amazon-linux-extras install nginx1 -y
  systemctl enable nginx
  systemctl start nginx
  EOF
}

Final Root Module

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.27"
    }
  }

  required_version = ">= 0.14.0"
}

provider "aws" {
  region = var.region
}

module "my_vpc" {
  source = "./modules/vpc"

  vpc_cidr           = "10.0.0.0/16"
  public_sub_1_cidr  = "10.0.1.0/24"
  private_sub_1_cidr = "10.0.2.0/24"
}

module "my_ec2" {
  source = "./modules/ec2"

  public_subnet_id   = module.my_vpc.public_subnet_id
  public_sg_id       = module.my_vpc.public_sg_id
}

Result

EC2 instance is up and running
It works!

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recent Posts
  • ChinguTalkBot v0.1.0: Setting up AWS Cognito with CDK for User Authentication
  • Phoenix & Elixir: Fix PostgreSQL connection refused
  • Demo: Git Log with Shell script to create a release notes
  • Metasploit
  • CyberSecurity Lab – Online Password Attack

Archives
  • March 2024
  • May 2023
  • April 2023
  • February 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • July 2022
  • June 2022
  • May 2022
  • April 2022
  • March 2022
  • February 2022
  • January 2022
Categories
  • Amazon Interview (3)
  • Ansible (3)
  • AWS (9)
  • Azure (9)
  • Certification (2)
  • ChinguTalkBot Project (1)
  • cybersecurity (3)
  • Data analytics (6)
  • Demo Videos (6)
  • Docker (5)
  • Git (1)
  • GitLab (1)
  • Golang (3)
  • JavaScript (2)
  • Jenkins (4)
  • PowerShell (1)
  • Python (10)
  • Terraform (11)
  • Uncategorized (9)

©2025 | Powered by WordPress and Superb Themes!