Terraform for Azure: Modules and Variables
This is the third part of a blog series about using Terraform for provisioning Azure resources. In the first two posts I showed you how to get started with Terraform and how to handle Terraform State. This post will cover Terraform Modules.
A module is a container for multiple resources that are used together. This will let you group sets of resources in seperate files, instead of having all resources in the same 'main.tf'. You can pass a set of variables to a module, so it can be reused with different configuration. You always have a root module (the intial 'main.tf' file) that configure and initalize the other modules. You run 'terraform apply' on this root module. The root module can contain resources that are shared, like Azure Application Insights or Azure Keyvault.
To understand the use of modules, we're going to setup a website hosted on a Azure App Service in a seperate Resource Group. Will create a seperate module for this website and initialize the module from the root module. Will also make use of some varialbles, and send them from the root module to the website module.
Variables
Let's start by declaring som variables in the root module (a 'main.' file). We'll create one variable for the environment (eg. dev, stage, prod) and one for location (what Azure region to place the resources):
variable "environment" {
type = string
description = "Deployment environment"
default = "dev"
}
variable "location" {
type = string
description = "Azure location to deploy resources"
default = "norwayeast"
}
You give the variable a name (in this case; environment, location), then you define a type for the variable. Available types are:
- string
- number
- bool
- list(
) - set(
) - object({
= , ... }) - tuple([
, ...])
For most variables you'll use one of the first three, but it's important to be aware of the rest, so check the Terraform docs. They can come in handy if you have arrays in your appsettings.json or something like that.
You give the variable a description and can set a default if you want to.
You can also define the variables in a seperate variables file (named something like 'variables.tfvars'). When you run 'terraform plan' or 'terraform apply', you can pass the variables file to the command like this:
terraform plan -var-file "variables.tfvars"
The variables file can even be in JSON format. Create a file called 'variables.tfvars.json'. Then you can add content like this:
{
"environment": "prod",
"location": "norwayeast"
}
Using variable files are useful when deploying the same resources to different environments, enabling each environment to have seperate configuration. Using the JSON format for the variables file is usefull when running Terraform commands as part of build pipelines (like in Azure Devops og Github Actions). Then you can use File Transform tasks to replace variables with variables from the pipeline. I'll cover this in another blog post.
Let's get back to modules. We'll create a seperate folder (named 'newwebsite') and module ('main.tf' in the 'newwebsite' folder) for the new website. Then we can add the same variables as in the root module:
variable "environment" {
type = string
description = "Deployment environment"
}
variable "location" {
type = string
description = "Azure location to deploy resources"
}
This time I didn't include the defaults. This will make the variables required and Terraform commands will fail if they are not provided.
Next, we add the code to create a Azure App Service to host the website. We'll use the location variable to set the location for all resources in the module, and we'll use the environment variable to prefix the name of all resources. In a real project, best pratice is to have a seperate Azure subscription for each environment. But if you only have one subsciption you can prefix them like this and for the purpose of this blog post it illustrates the use of variables.
resource "azurerm_resource_group" "newwebsite" {
name = "${var.environment}-newwebsite-rg"
location = var.location
}
resource "azurerm_app_service_plan" "newwebsite" {
name = "${var.environment}-newwebsite-plan"
location = azurerm_resource_group.newwebsite.location
resource_group_name = azurerm_resource_group.newwebsite.name
sku {
tier = "Standard"
size = "S1"
}
}
resource "azurerm_app_service" "newwebsite" {
name = "newwebsite-app-service"
location = azurerm_resource_group.newwebsite.location
resource_group_name = azurerm_resource_group.newwebsite.name
app_service_plan_id = azurerm_app_service_plan.newwebsite.id
}
From the code you see that the variables are available on the 'var' namespace, and can be used directly when setting a resource argument or by using string interpolation (eg.'${var.environment}-newwebsite-rg'). You can also see the use of attributes from other resources, like when we use 'azurerm_resource_group.newwebsite.location' for setting the location for the Resource Group. In the Terraform docs you'll find all the arguments (inputs when creating a resource) and attributes (to be used by others when resource has been created).
Referencing a module
Now, we'll go back to the root module and reference the module ('newwebsite') that we just created:
module "newwebsite" {
source = "./newwebsite"
environment = var.environment
location = var.location
}
To include the new module in your project, you'll a have to run 'terraform init' again (in the root folder). You should see the new module initializing in the output:
Initializing modules...
- newwebsite in newwebsite
Then plan and apply the resources:
terraform plan -var-file "variables.tfvars"
terraform apply -var-file "variables.tfvars"
From the output you should see 3 resources created. If you check the Azure Portal, you should see the same resources.
So, we've created the resources by using modules and variables! This will make it easier to reuse Terraform code and to configure different environments.
A cool thing about modules is that they don't have to be local modules. You can import modules from other sources, like Github or the official Terraform Registry. The Terraform docs describes available module sources and how to use them.
That's it for the third part of this blog series. You'll find the source code for the examples here. We have now covered important basics of Terraform. The rest of this series will describe in more detail how to provision spesific Azure resources (like Azure SQL Server, Azure API Managment etc.). Starting with Azure App Service.