Document

SUBSCRIBE TO GET FULL ACCESS TO THE E-BOOKS FOR FREE 🎁SUBSCRIBE NOW

Professional Dropdown with Icon

SUBSCRIBE NOW TO GET FREE ACCESS TO EBOOKS

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 

To keep things simple we will be creating two modules named module-1, module-2.

Each module will have its own main.tf files.

 

Terraform modules with its own main.tf files

 

To keep things more granular we will install two apache httpd servers in each module.

Let us start with the module-1 –

terraform {
  required_version = ">=0.12"
}
 
BASH


(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]
}
 
BASH

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
 
BASH


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"]
  }
}
 
BASH
 

module-1

terraform {
  required_version = “>=0.12”
}

resource “aws_instance” “ec2_example” {

    ami = “ami-05fa00d4c63e32376”
    instance_type = “t2.micro”
    key_name= “newkey”
    vpc_security_group_ids = [aws_security_group.main.id]

  user_data = <<-EOF
      #!/bin/sh
      sudo yum update -y
      sudo yum install -y httpd
      sudo systemctl start httpd
      sudo systemctl enable httpd
      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
}

resource “aws_security_group” “main” {
    name        = “EC2-webserver-SGraman-1”
  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”]
  }
}

 
BASH


module-2

terraform {
  required_version = “>=0.12”
}

resource “aws_instance” “ec2_example” {

    ami = “ami-0ee23bfc74a881de5”
    instance_type = “t2.micro”
    key_name= “newkey”
    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-2 at instance id `curl http://169.254.169.254/latest/meta-data/instance-id` </h1></body></html>” > /var/www/html/index.html
      EOF
}

resource “aws_security_group” “main” {
    name        = “EC2-webserver-SGubbuntu-1”
  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 = [“0.0.0.0/0”]
  }

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


 
BASH


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

 


terraform modules with parent module terraform file main.tf

 

 

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"
}
 
BASH
 

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 –

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
} 
 
BASH
 

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"
}
 
 
BASH
 

Terraform module also have the capability to produce the return output just like function which do return some value back after calling them.

Example –

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"
}
 
BASH
 

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>
 
BASH

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.htmlStep 2: Create a module folder say module1 and create following filesvariables.tf
variable “bucket_name” {
  description = “The name of the AWS S3 bucket this website will be published to.”
}
outputs.tf
output “website_endpoint” {
  description = “The public url of this website.”
  value = ${aws_s3_bucket.static_site.website_endpoint}
}
main.tf
resource “aws_s3_bucket” “static_site” {
  bucket = ${var.bucket_name}
  acl    = “public-read”

  website {
    index_document = “index.html”
    error_document = “error.html”
  }
}

resource “aws_s3_bucket_object” “index” {
  bucket       = ${aws_s3_bucket.static_site.bucket}
  key          = “index.html”
  source       = “html/index.html”
  content_type = “text/html”
  etag         = ${md5(file(“html/index.html”))}
  acl          = “public-read”
}

resource “aws_s3_bucket_object” “error” {
  bucket       = ${aws_s3_bucket.static_site.bucket}
  key          = “error.html”
  source       = “html/error.html”
  content_type = “text/html”
  etag         = ${md5(file(“html/error.html”))}
  acl          = “public-read”
}

Step 3: create main.tf file in the root folder
provider “aws” {
    region = “us-east-1”
 
}
module “module1” {
  source= “./module1”
  bucket_name = “testing77192”
}
output “websiteendpoint” {
  value=module.module1.website_endpoint
Share your love

Leave a Reply

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