Writing Terraform; like any other IaC toolset, over time you may be repeating the same process for common resources such as an Azure Virtual network, Container registry, Postgres Database etc – instead of copying the same resource multiple times, you can create what is called a Terraform module to assist you with this repetition allowing you to create reusable Terraform.
What is a Terraform module?
There is various thoughts/views on what a Terraform module is; to me, a Terraform module is a single or a grouping of Terraform resources that you can use to deploy multiple times within your infrastructure estate.
Think of a terraform module as a piece of your infrastructure puzzle – the puzzle is to deploy a full environment, a module will deploy part of that puzzle!
How are Terraform modules structured?
As mentioned above, as a Terraform module is a single or grouping of Terraform resources, a “module” is stored outside of your main Terraform deployment. This is usually in a separate folder or in another repository.
What is inside this folder/repository?
- Terraform Resources – That deploy whenever you reference your module within Terraform
- Terraform Inputs – From your main Terraform deployment you will input various values and configurations that will be referenced within your Terraform module
- Terraform Outputs – Outputs that can be used once the module is deployed, for example the resource ID
- Whatever else you want to include 🙂 – What else you want to include can be decided by you and each module is certainly different!
I will chat about two examples to begin the thought of why to use a Terraform Module.
Firstly, thinking of a simple Azure resource that I can deploy – lets look at deploying an Azure container register for my first module
What will I need to deploy an Azure Container Registry?
Lets think, what Azure Resources do I need to deploy an Azure Container Registry?
- Resource Group to deploy Azure Container Register into
- Azure Container Registry resource
A sample Terraform showing both
# Example from https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/container_registry
resource "azurerm_resource_group" "rg" {
name = "example-resources"
location = "West Europe"
}
resource "azurerm_container_registry" "acr" {
name = "containerRegistry1"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
sku = "Premium"
admin_enabled = false
}
How could we create a module for this? Lets list some points/thoughts
- Two resources required, azurerm_resource_group & azurerm_container_registry
- Name for each resource
- Location
- ACR SKU
- Tags? Costing/billing segregation – tags are awesome for this!
Creating a Terraform module
Now that I have discussed, how a module is structured, lets look at creating a module for an Azure Container Registry
Folder structure that will be used
Terraform
└── modules
└── acr
└── acr.tf
└── variables.tf
└── outputs.tf
└── main.tf
Lets look at each of the files inside the folder acr
acr.tf
Notice the user of variables? These are ingested from the main.tf file which I will show further down
resource "azurerm_resource_group" "acr_resource_group" {
name = "${var.name}-rg"
location = var.location
tags = {
Environment = var.environment
}
}
resource "azurerm_container_registry" "acr" {
name = "${var.name}acr"
resource_group_name = azurerm_resource_group.acr_resource_group.name
location = azurerm_resource_group.acr_resource_group.location
sku = "Premium"
admin_enabled = false
tags = {
Environment = var.environment
}
}
variables.tf
variable "name" {
}
variable "location" {
default = "uksouth"
}
variable "environment" {
}
outputs.tf
Outputs of a module can be used in other parts of your Terraform deployment, in this I am outputting the resource group ID to be used as part of another resource I may want to deploy.
output "resource_group_id" {
value = azurerm_resource_group.acr_resource_group.id
}
Using a module as part of your Terraform deployment
A very simple main.tf below to show how you can reference the acr module above
provider "azurerm" {
version = "~> 2.0"
features {}
}
terraform {
backend "local" {
}
}
module "acr" {
source = "./modules/acr"
name = "tamopsblog"
environment = "Production"
}
If I was to run the Terraform above, it would deploy the module acr , which would create
- Resource group: tamopsblog-rg in region: UK South (no need to input region as I have set to as default in my acr module to UK South)
- Azure Container Registry: tamopsblogacr in region: UK South
- Both resources will be tagged with Environment = Production
Lets see a Terraform Plan for the above
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.acr.azurerm_container_registry.acr will be created
+ resource "azurerm_container_registry" "acr" {
+ admin_enabled = false
+ admin_password = (sensitive value)
+ admin_username = (known after apply)
+ encryption = (known after apply)
+ georeplication_locations = (known after apply)
+ georeplications = (known after apply)
+ id = (known after apply)
+ location = "uksouth"
+ login_server = (known after apply)
+ name = "tamopsblogacr"
+ network_rule_set = (known after apply)
+ public_network_access_enabled = true
+ resource_group_name = "tamopsblog-rg"
+ retention_policy = (known after apply)
+ sku = "Premium"
+ tags = {
+ "Environment" = "Production"
}
+ trust_policy = (known after apply)
+ zone_redundancy_enabled = false
+ identity {
+ identity_ids = (known after apply)
+ principal_id = (known after apply)
+ tenant_id = (known after apply)
+ type = (known after apply)
}
}
# module.acr.azurerm_resource_group.acr_resource_group will be created
+ resource "azurerm_resource_group" "acr_resource_group" {
+ id = (known after apply)
+ location = "uksouth"
+ name = "tamopsblog-rg"
+ tags = {
+ "Environment" = "Production"
}
}
Plan: 2 to add, 0 to change, 0 to destroy.
Notice it references the two Terraform resources within the acr module?
- module.acr.azurerm_resource_group.acr_resource_group
- module.acr.azurerm_container_registry.acr
Awesome! If I ran Terraform Apply – it would then deploy the module, lets do that now!
module.acr.azurerm_resource_group.acr_resource_group: Creating...
module.acr.azurerm_resource_group.acr_resource_group: Creation complete after 1s [id=/subscriptions/XXXXX/resourceGroups/tamopsblog-rg]
module.acr.azurerm_container_registry.acr: Creating...
module.acr.azurerm_container_registry.acr: Creation complete after 7s [id=/subscriptions/XXXXX/resourceGroups/tamopsblog-rg/providers/Microsoft.ContainerRegistry/registries/tamopsblogacr]
I maybe want another two ACR’s to be deployed, how can I do this? Easily now as you have a module created to deploy an ACR!
module "acr" {
source = "./modules/acr"
name = "tamopsblog"
environment = "Production"
}
module "acr2" {
source = "./modules/acr"
name = "tamops2"
environment = "Production"
}
module "acr3" {
source = "./modules/acr"
name = "tamops3"
environment = "Production"
}
Now to apply the additional modules via terraform, you can see the additions
module.acr.azurerm_resource_group.acr_resource_group: Creating...
module.acr.azurerm_resource_group.acr_resource_group: Creation complete after 1s [id=/subscriptions/XXXXX/resourceGroups/tamopsblog-rg]
module.acr.azurerm_container_registry.acr: Creating...
module.acr.azurerm_container_registry.acr: Creation complete after 7s [id=/subscriptions/XXXXX/resourceGroups/tamopsblog-rg/providers/Microsoft.ContainerRegistry/registries/tamopsblogacr]
module.acr.azurerm_resource_group.acr_resource_group: Creating...
module.acr.azurerm_resource_group.acr_resource_group: Creation complete after 1s [id=/subscriptions/XXXXX/resourceGroups/tamops2blog-rg]
module.acr.azurerm_container_registry.acr: Creating...
module.acr.azurerm_container_registry.acr: Creation complete after 7s [id=/subscriptions/XXXXX/resourceGroups/tamops2blog-rg/providers/Microsoft.ContainerRegistry/registries/tamops2blogacr]
module.acr.azurerm_resource_group.acr_resource_group: Creating...
module.acr.azurerm_resource_group.acr_resource_group: Creation complete after 1s [id=/subscriptions/XXXXX/resourceGroups/tamops3blog-rg]
module.acr.azurerm_container_registry.acr: Creating...
module.acr.azurerm_container_registry.acr: Creation complete after 7s [id=/subscriptions/XXXXX/resourceGroups/tamops3blog-rg/providers/Microsoft.ContainerRegistry/registries/tamops3blogacr]
Awesome, hopefully this blog has given you the insight into what is a Terraform module and how to use them within your Terraform codebase! I will create a follow up blog soon to detail more advanced use-cases for creating Terraform modules!
GitHub Repo containing Terraform code used in this blog post
3 thoughts on “Creating reusable Terraform with Terraform modules”