Terraform Modules : How to use Best out of it? — with real time usage scenarios

DevOpsWithYoge
6 min readJan 13, 2024

--

Hi Everyone,

Its good to meet you guys with in-depth explanation of Terraform Modules.

People usually confused with the purpose of modules and end up over-using it (where-ever its unnecessary) or not using it all.

In this blog , I will be giving the best practices . and it will equip you for all circumstances for its usage.

For explanation purpose I will be using the Azure + Terraform combination.

Topics Covered:

  • What are Modules in Terraform?
  • Advantages of Modules
  • When to Use vs When not to use Modules
  • Flow Chart for Modules Dependencies

What are Modules in Terraform?

Fig 0.1 : Simple Example on modules

Modules are like transformers they look simple at the place where the parent module block is present. after the invocation they can be transformed based the parameters. in end of this chapter you will understand it better !!

Below goes the explanation for modules given by HashiCorp.

A module is a container for multiple resources that are used together.

Every Terraform configuration has at least one module, known as its root module, which consists of the resources defined in the .tf files in the main working directory.

A module can call other modules, which lets you include the child module’s resources into the configuration in a concise way. Modules can also be called multiple times, either within the same configuration or in separate configurations, allowing resource configurations to be packaged and re-used.

In simple terms,

Modules are the user configuration blocks in terraform which can contain one or more resource blocks which facilitaes in resource creates with set of parameters given at the parent level, its same as functions in any programming language.

It has facilities to

Validate its inputs with datatypes and validation rules.

Can send resulted values from creation of resources to the invocation at parent location.

Modules can have one child or more nested / sub/ childrens.

It can be versioned.

Modules are of two types -> Local modules and Remote Modules

Simple File Structure of a module:

Module - (Resource Group)
- variables.tf
- main.tf
- output.tf
- readme.md
  • variables.tf : Contains the variable declarations for the modules.
  • main.tf : contains the resource block
  • output.tf : will have the outputs
  • readme.md : details related to usage of modules and its parameters explanation.

Root level : where the invocation of module happens,

locals{

resource_groups = [
{
name = "sample-rg"
location = "west-us"
tags = {"env" = "dev"}
},
{
name = "sample-rg"
location = "west-us"
tags = {"env" = "dev"}
}
]

}

module "resource_group" {
for_each = { for rg in local.resource_groups : rg.display_name => inst }
source = "../local_modules/resource_group"
name = each.value.name
location = each.value.location
tags = each.value.tags
}

Folder -> local_modules

> main.tf

resource "azurerm_resource_group" "create_resource_group" {
name = var.name
location = var.location
tags = var.tags

}

> variables.tf

variable "name" {
type = string
description = "(Required) The Name which should be used for this Resource Group. Changing this forces a new Resource Group to be created"
}

variable "location" {
type = string
description = "(Required) The Azure Region where the Resource Group should exist. Changing this forces a new Resource Group to be created."
}

variable "tags" {
type = map()
description = "(Optional) A mapping of tags which should be assigned to the Resource Group."
}

> output.tf

output "rg_name" {
value = azurerm_resource_group.create_resource_group.name
description = "Name of the resource group created"
}

> readme.md

- this module is using in the creation of resource group in azure. 

- variable parameters:
name -> name of RG
location -> location of the RG
tags -> tagginf for the RG.

- output parameter:
rg_name -> outputs the name of the RG.

Advantages of Modules:

This gives a explanation with relationship between them

  • Modules are Logical Grouped Components used to organize and keep things together
  • Its provides us encapsulation using which the core resource definition attributes are not changed accidently. if we want to deploy a service bus with basic tier always using module we can make it consistent avoiding the developers to accidently change to premium .
  • It avoids repetitions in naming's for creating a resource by equipping us to run same module multiple times using loops. basically they are Reusable components in Terraform.
  • Parametrizing modules makes them exhibit polymorphic behavior to create resources based on the variables which are passed into it.
  • For Example -> Creating a simple module, to create a storage block in the targeted storage account container. now this module used by two different teams and uploads two different set of blobs.
  • Team 1 — uploads only block blobs — Monthly Sales Reports
  • Team 2 — uploads only append blobs — VM logs

Consider I have followed the above practice to declare the variables.tf , output.tf and readme.md , for this example I wanted to display the module usage alone.

# Module (storage_blob) - main.tf - used to create storage blob 
resource "azurerm_storage_blob" "storageblob" {
name = var.blob_name
storage_account_name = var.blob_sa_name
content_md5 = var.blob_content_md5
storage_container_name = var.blob_container_name
type = var.blob_type
source = var.file_path
}

The modules is invoked by two different root level components from each Team .

# Team 1
module "upload_block_blob" {
source = "../modules/storage_blob"
blob_name = "TestReport1"
blob_sa_name = "commonstorageaccount"
blob_content_md5 = filemd5("../files/testReports/TestReport1.csv")
blob_container_name = "Team1-Reports"
blob_type = "Block"
blob_source = "../files/testReports/TestReport1.csv"
}
# Team 2
module "upload_block_blob" {
source = "../modules/storage_blob"
blob_name = "VmLogs"
blob_sa_name = "commonstorageaccount"
blob_content_md5 = filemd5("../files/testReports/vm-monitor-report.json")
blob_container_name = "Team2-Reports"
blob_type = "Append"
blob_source = "../files/testReports/vm-monitor-report.json"
}

When to use vs When not to use modules ?

Achieving Modularity sounds Good but sometimes making every resource creations for modules may sounds ending things much complex .

Examples —

  1. when you create a SQL database and you want to add its connection string to key vault then its unnecessary to write a separate module for this, we can simply provide a key vault secrets- resource block in the same SQL database module level.

2. Another best example is providing RBAC to storage accounts . where you can declare the RBAC — resource block in the same storage account module level.

This reduces the much complex and over dependent nature .

if its a remote module then we need to versioned it so that the backward compatibility support is provisioned. and it may result in multiple version creations even for small change and hard to maintain.

Flow Chart for Modules Dependencies :

Team 1 — code is hosted in team1_repository, its dependent Remote Module (rm) versions — in remotemodule_repository,

Team 2— code is hosted in team2_repository, its dependent Remote Module (rm) versions — in remotemodule_repository,

In the below diagram you can see the interconnectivity and usage of both local and remote modules

Fig 1.0 Flow of interdependencies of local and remote modules

Conclusion:

I hope now you understand the illustrated image given about transformers.

Modules can be used to leverage the concept of functions in terraform . I hope this blog has provided you the clarity on it.,

References:

--

--

DevOpsWithYoge

An enthusiastic DevOps professional ,I would like to help/share Azure Cloud aspirers and learners to know the aspect where Azure Cloud meets the realworld.