Part 7— HumanGov Application — Terraform-5: Terraform Modules

Cansu Tekin
8 min readOct 6, 2023

--

HumanGov is a software-as-a-service (SaaS) cloud company that will create a Human Resources Management SaaS application for the Department of Education across all 50 states in the US and host the application files and databases in the cloud.

In this following project series, we are going to transition the architecture from a traditional virtual machine architecture to a modern container-based architecture using Docker containers and Kubernetes running on AWS. In addition, we will also be responsible for automating the complete software delivery process using Pipelines CI/CD using AWS services such as AWS CodeCommit, AWS CodePipeline, AWS CodeBuild, and AWS CodeDeploy. Finally, we will learn how to monitor and observe the cloud environment in real-time using tools such as Prometheus, Grafana, and automate one-off cloud tasks using Python and the AWS SDK.

In this section, we are going to introduce working with Terraform Modules and practice Terraform with cloud provider AWS before using it in the implementation of the HumanGov application. This is the 6th part of a project series. Check Part 1, Part 2, Part 3, Part 4, Part 5 and Part6 to follow up.

Terraform Modules

Modules are reusable units that encapsulate resources and their configurations. Modules allow us to define infrastructure components once and use them multiple times within the same configuration or across different configurations. Modules also allow us parameterization. We can define variables and define values for them when using the module. That makes variables flexible and configurable.

Terraform has two types of modules; root modules and child modules:

Root Module: The root module includes a primary Terraform configuration file, main.tf, where the main infrastructure and configurations of our project are defined. This is the working directory where we run our Terraform core workflow (like terraform init, terraform plan, and terraform apply). Every Terraform configuration has at least one root module.

Child Module: It is a module that has been called by other modules and is used to encapsulate and abstract resources or configurations within the larger Terraform project. They are important to promote modularity and code reusability. Modularizing our configuration by breaking it into smaller, reusable child modules improves maintainability and project organization.

A Terraform module is organized as a directory defining resources, variables, outputs, or configuration files. It consists of a collection of .tf or .tf.json files. Let’s say we want to configure many resources. It is not scalable and practical to write a Terraform configuration for each one of them. Modules are handy at this point. We can create a child module including configuration for the resources and call this child module inside the root module.

We can use local modules that we defined by ourselves within the same project or also built-in remote modules important from external sources like Terraform Registry, GitHub, Generic Git, Mercurial repositories, HTTP URLs, S3 buckets, GCS buckets, etc.

Hands-on: Terraform Local Module

We are going to create a Terraform module, which is going to be our child module, to deploy 10 EC2 instances without code duplication. Instead of creating 10 different resource blocks to create these instances, we will refer to this module inside a root module for deployment.

Step 1: Create Directories for Root Module and Child Module

Go to AWS Cloud9 and open a new terminal: Window -> New Terminal

Create a new directory (root module) for the Terraform project and create a main.tf file:

mkdir terraform-module-ec2 
cd terraform-module-ec2
touch main.tf

Create a new directory named modules in your project directory, and inside it, create a folder named ec2_instance (child module):

mkdir -p modules/ec2_instance

Create the following files inside the modules/ec2_instance directory: variables.tf, outputs.tf, and main.tf:

touch modules/ec2_instance/variables.tf modules/ec2_instance/outputs.tf modules/ec2_instance/main.tf

Now, we have the folders and directories.

We now have 3 empty files inside the ec2_instance child module. We are going to use the main.tf file to create module resources. We are going to put outputs of the our module inside the output.tf file. We are going to define variables for the module inside variables.tf file.

The root module terraform-module-ec2 also has a main.tf file where we will call the ec2_instance module to create project resources.

Step 2: Define Input Variables for the Module

We will define some configurations and provide some information for the EC2 instances using variables here. It is one of the best practices to use description while creating variables to provide more information.

We have now 4 variables.

Step 3: Edit modules/ec2_instance/main.tf to Create the EC2 instance

We are going to write configurations here to provision the EC2 instances. Instead of creating 10 different blocks for each EC2 instance, we will only define one resource block. We can specify the number of instances we want to create, AMI, instance type, and tag.

We can read related information from the variables.tf file. Using values of variables like this allows us to use the module in a dynamic way. We can specify some information differently for each EC2 instance as we did here inside tags. The name is dynamic here. We named each instance differently using the instance name variable and the count index starts from 0. We added +1 to it because we want to instance names starting from 0 up to 10 (webserver-1, webserver-2,.., webserver-10). When we specify the count as a 10, Terraform will run this code 10 times and increase the count by 1 at each run.

Step 4: Edit modules/ec2_instance/outputs.tf to define the module’s output variables

Terraform will store the IDs and public IPs of the instances in the outputs.tf file whenever an instance is created. We are not going to have one instance but 10 of them. So, we will refer to all their IDs and IPs using resources-type.name.[*] to capture the related information for each EC2 instance.

Step 5: Edit the Root Module main.tf File to Use the EC2 Module

We need to specify the cloud provider first.

provider "aws" {
region = "us-east-1"
}

Then we will call the module specifying its configuration. We will provide a module path with a source argument. We will also need an AMI ID.

Go to AWS -> EC2 -> Instances -> Launch Instances -> Select Amazon Linux

Copy the AMI ID from here. We will use this ID in the main.tf file while configuring EC2 instances.

We will specify number of the instances using instance_count. Additionally, we will define instance_name and instance_type. Even though we do not define instance_type, it will still work because we already defined its default value as t2.micro in variables.tf file.

module "ec2_instances" {
source = "./modules/ec2_instance"
ami_id = "ami-067d1e60475437da2 # Amazon Linux 2 AMI
instance_count = 10
instance_name = "webserver"
instance_type = "t2.micro"
}

In Terraform, the output block is used to define values like variables, attributes of resources, or any other values that should be shown to the user after running terraform apply. We can use terraform output to view the values of defined outputs. If you remember we have output blocks in our child module’s outputs.tf file. However, Terraform has the scope, and outputs only belong to the scope of the child module. We should explicitly code to get output from the child module. We want to output values of instance IDs and public IPs. We can add 2 output blocks in our main.tf file as below:

output "instance_ids" {
value = module.ec2_instances.instance_ids
}

output "public_ips" {
value = module.ec2_instances.public_ips
}

Now we have all the configurations we need. Do not forget to save all the changes you made.

Step 6: Run Terraform

First, make sure you are in the root module terraform-module-ec2. We should run terraform commands there. Check for formatting using the terraform fmt command. If there is no issue so far we can initialize and run terraform.

cd terraform-module-ec2
terraform fmt
terraform init

You can see it is also initializing the child module. Make sure you run terraform init when you create a new module each time.

Run a terraform plan to see what changes will be made.

It will create 10 EC2 instances and will output their instance IDs and public IPs after we run the terraform apply as you can see. I only included information for instance 1 and instance 10 here. Actually, I will not create 10 instances for now. I will update instance_count to 3 in the root module main.tf file, just to save time and resources, then I will run the terraform plan and terraform apply.

Now, we can see that 3 instances are created and their instance IDs and public IPs are displayed. Let’s go to the AWS Services and check instances if we have them there.

You can see how easy to create resources and configure infrastructure in an automated way with Terraform.

Step 7: Destroy Resources

Now make sure you destroy resources if you are not going to use them.

terraform destroy

Refresh the AWS Services ->Instances

They are all terminated.

CONGRULATIOTIONS!!

--

--

Cansu Tekin

AWS Community Builder | Full Stack Java Developer | DevOps | AWS | Microsoft Azure | Google Cloud | Docker | Kubernetes | Ansible | Terraform