Terraform Modules
Terraform Modules
Module will help you to organize your terraform configuration so that you can re-use the configuration and keep your terraform code more clean and modular.
As you manage your infrastructure with Terraform, you will create increasingly complex configurations. There is no intrinsic limit to the complexity of a single Terraform configuration file or directory, so it is possible to continue writing and updating your configuration files in a single directory. However, if you do, you may encounter one or more problems:
Understanding and navigating the configuration files will become increasingly difficult.
Updating the configuration will become more risky, as an update to one section may cause unintended consequences to other parts of your configuration.
What are modules for?
Here are some of the ways that modules help solve the problems listed above:
Organize configuration – Modules make it easier to navigate, understand, and update your configuration by keeping related parts of your configuration together. Even moderately complex infrastructure can require hundreds or thousands of lines of configuration to implement. By using modules, you can organize your configuration into logical components.
Encapsulate configuration – Another benefit of using modules is to encapsulate configuration into distinct logical components. Encapsulation can help prevent unintended consequences, such as a change to one part of your configuration accidentally causing changes to other infrastructure, and reduce the chances of simple errors like using the same name for two different resources.
Re-use configuration – Writing all of your configuration from scratch can be time consuming and error prone. Using modules can save time and reduce costly errors by re-using configuration written either by yourself, other members of your team, or other Terraform practitioners who have published modules for you to use. You can also share modules that you have written with your team or the general public, giving them the benefit of your hard work.
Provide consistency and ensure best practices – Modules also help to provide consistency in your configurations. Not only does consistency make complex configurations easier to understand, it also helps to ensure that best practices are applied across all of your configuration. For instance, cloud providers give many options for configuring object storage services, such as Amazon S3 or Google Cloud Storage buckets. There have been many high-profile security incidents involving incorrectly secured object storage, and given the number of complex configuration options involved, it’s easy to accidentally misconfigure these services.
Using modules can help reduce these errors. For example, you might create a module to describe how all of your organization’s public website buckets will be configured, and another module for private buckets used for logging applications. Also, if a configuration for a type of resource needs to be updated, using modules allows you to make that update in a single place and have it be applied to all cases where you use that module.
What is a Terraform module?
A Terraform module is a set of Terraform configuration files in a single directory. Even a simple configuration consisting of a single directory with one or more .tf files is a module. When you run Terraform commands directly from such a directory, it is considered the root module. So in this sense, every Terraform configuration is part of a module. You may have a simple set of Terraform configuration files such as:
.
├── LICENSE
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
In this case, when you run terraform commands from within the minimal-module directory, the contents of that directory are considered the root module.
Calling modules
Terraform commands will only directly use the configuration files in one directory, which is usually the current working directory. However, your configuration can use module blocks to call modules in other directories. When Terraform encounters a module block, it loads and processes that module’s configuration files.
A module that is called by another configuration is sometimes referred to as a “child module” of that configuration.
Local and remote modules
Modules can either be loaded from the local filesystem, or a remote source. Terraform supports a variety of remote sources, including the Terraform Registry, most version control systems, HTTP URLs, and Terraform Cloud or Terraform Enterprise private module registries.
Example
1. Create your first module
To keep things simple we will be creating two modules named module-1, module-2.
Each module will have its own main.tf
files.
1.1 Install apache httpd server in each module
To keep things more granular we will install two apache httpd servers in each module.
Let us start with the module-1 –
1. Specify the terraform required version
terraform {
required_version = ">=0.12"
}
2. Create an ec2 aws_instance along with the user_data block in which we will be writing bash commands for installing the apache2 httpd server.
(Note – Here in this instance I have used the key_name aws_key
. Follow this post on how to create aws keys for your ec2 instance )
resource "aws_instance" "ec2_example" {
ami = "ami-0767046d1677be5a0"
instance_type = "t2.micro"
key_name= "key"
vpc_security_group_ids = [aws_security_group.main.id]
}
and here is the user_data block for installing the apach2 httpd server.
We will be using the same user_data block for installing the apache2 httpd server on module-2 also.
user_data = <<-EOF
#!/bin/sh
sudo apt-get update
sudo apt install -y apache2
sudo systemctl status apache2
sudo systemctl start apache2
sudo chown -R $USER:$USER /var/www/html
sudo echo "<html><body><h1>Hello this is module-1 at instance id `curl http://169.254.169.254/latest/meta-data/instance-id` </h1></body></html>" > /var/www/html/index.html
EOF
3. Define the aws_security_group along with ingress rules for port 80 and 22.
We need to open the port 80 on the ec2 instance to access the apache httpd server home page and port 22 will need to ssh into the ec2 instance.
Here is the resource block –
resource "aws_security_group" "main" {
name = "EC2-webserver-SG-2"
description = "Webserver for EC2 Instances"
ingress {
from_port = 80
protocol = "TCP"
to_port = 80
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
protocol = "TCP"
to_port = 22
cidr_blocks = ["115.97.103.44/32"]
}
egress {
from_port = 0
protocol = "-1"
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
}
4. Complete terraform script for module-1 and module -2
module-1
module-2
2. Module structure
In the step 1 we have seen the main.tf
terraform files of module-1 and module-2.
Now let’s talk about the complete structure of your terraform project where you will have your parent main.tf
file which will be calling the module-1
and module-2
3. Calling the module
Alright in the previous steps we have seen how our modules main.tf
files along with the structure of the modules along with the parent main.tf
files.
Once you define the modules the next important step would be to call the modules from the parent main.tf
file and terraform makes it easy to call the modules based on the relative paths-
Here is my parent main.tf
calling the module-1 and module-2
provider "aws" {
region = var.web_region
}
module "webserver-1" {
source = ".//module-1"
}
module "webserver-2" {
source = ".//module-2"
}
4. Module Inputs
Just like any other programming language terraform also supports re-usability with the help of terraform module. You might be familiar with the concept of functions in other programming languages. In general functions always have some input parameter which needs to passed during the function call, similarly terraform module can also accept input parameters.
Example –
Let’s take the same example where we have defined the module-1 but instead of hard coding ec2 instance type let’s create an input variable which can be passed later –
1. Create a input variable inside module
Here is code snippet of module-1 where I have replaced hardcoded instance type to a variable var.web_instance_type
–
Path – terraform-modules/module-1/main.tf
resource "aws_instance" "ec2_module_1" {
ami = var.ami_id
instance_type = var.web_instance_type
key_name= "aws_key"
vpc_security_group_ids = [aws_security_group.main.id]
user_data = <<-EOF
#!/bin/sh
sudo apt-get update
sudo apt install -y apache2
sudo systemctl status apache2
sudo systemctl start apache2
sudo chown -R $USER:$USER /var/www/html
sudo echo "<html><body><h1>Hello this is module-1 at instance id `curl http://169.254.169.254/latest/meta-data/instance-id` </h1></body></html>" > /var/www/html/index.html
EOF
}
2. Pass the module input variable value while calling terraform module
In the following code snippet we are calling module-1 but also we are passing input variable .i.e. web_instance_type=”t2.large”, so based on your need and environment you can update instance type without changing the code your terraform module.
Path – terraform-modules/main.tf
provider "aws" {
region = var.web_region
access_key = var.access_key
secret_key = var.secret_key
}
module "jhooq-webserver-1" {
source = ".//module-1"
web_instance_type = "t2.large"
}
module "jhooq-webserver-2" {
source = ".//module-2"
}
5. Module Output
Terraform module also have the capability to produce the return output just like function which do return some value back after calling them.
Example –
1. Create an output variable public_ip_ec2
Let’s again take the example of same terraform module-1, define an output block inside terraform module-1.
The following code snippet is for adding an output variable inside module-1 (Refer to GitHub for code repo)
Path – terraform-modules/module-1/main.tf
resource "aws_instance" "ec2_module_1" {
ami = var.ami_id
instance_type = var.web_instance_type
key_name= "aws_key"
vpc_security_group_ids = [aws_security_group.main.id]
user_data = <<-EOF
#!/bin/sh
sudo apt-get update
sudo apt install -y apache2
sudo systemctl status apache2
sudo systemctl start apache2
sudo chown -R $USER:$USER /var/www/html
sudo echo "<html><body><h1>Hello this is module-1 at instance id `curl http://169.254.169.254/latest/meta-data/instance-id` </h1></body></html>" > /var/www/html/index.html
EOF
}
output "public_ip_ec2" {
value = aws_instance.app_server.public_ip
description = "Public IP address of EC2 instance"
}
2. Use module output variable to show the output
After declaring module output variable inside terraform module we can refer to the output variable inside main. tf. But to access the output variable you need to use following syntax
module.<MODULE_NAME>.<OUTPUT_VARIABLE_NAME>
Here is the main.tf accessing the output variable public_ip_ec2
Path – terraform-modules/main.tf
provider "aws" {
region = var.web_region
access_key = var.access_key
secret_key = var.secret_key
}
module "jhooq-webserver-1" {
source = ".//module-1"
web_instance_type = "t2.large"
}
output "public_ip_ec2" {
value = module.module-1.public_ip_ec2
description = "Public IP of EC2"
}
module "jhooq-webserver-2" {
source = ".//module-2"
}
Example 2:-
Create a static website using modules
Step 1: Create an html folder and add 2 files index.html and error.html
Step 2: Create a module folder say module1 and create following files
variables.tf
outputs.tf
main.tf
Step 3: create main.tf file in the root folder