Creating reusable Terraform with Terraform modules

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 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s