Introduction
Last week, during my time looking into how to rewrite a Terraform example that is written in one monolith main.tf
into separate modules. I found myself relearning Terraform and testing all the new features and functionalities HashiCorp is continuously adding. But before I get into how I managed to create those reusable modules, I stumbled upon an issue that I am sure many of you who written Terraform or any infrastructure-as-code (IaC) tools have faced:
- How to deploy multiple resource of the same type? Ex: EC2 instances, Azure VNet subnets, Monitoring Alerts …etc.
- How to reduce the number of copy/paste lines in your IaC code?
- How to reuse the same code for multiple projects? Ex: Prod, Test, Dev .. etc.
Of course, if you are familiar with writing scripts in PowerShell or Python, the answer would be as simple as for loop
. So what is the alternative when working with Terraform HCL.
count
: Replicating Identical Resources
First thing I have looked into is using count
, let me give you an example:
resource "aws_instance" "k8s_workers" {
count = 3
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
#...
}
count
is used to manage similar resources or ideally, identical once. As you can see from the example above, if you would like to have multiple Azure VMs or EC2 Instance, Terraform will replicate the given resource or module a specific number of times with an incrementing counter. I would use count
to deploy a fleet of VMs that has the same workload or server the same service or workload, ex: K8s worker nodes or Dev/Test SQL VMs.
What if each resource is identical and have different parameters? In this case count
won’t be doable, and HCL has another way for looping through variables in data structure rather than using an integer to replicate resources – for_each
.
for_each
: Looping Through maps and sets
Similar to count
, for_each
iterates over each item declared in data structure variable and creates one copy of the resource.
Something I learned while testing this. You cannot use both count
and for_each
in the same block.
Lets go through the example I worked on – Azure VNet Subnets. What I have achived is, I dont need to duplicate the number of resource blocks required in all the three files main.tf
, variables.tf
, and output.tf
. I just modify terrafrom.tfvars
with a data structure formate variables. I used map
in with VNet Subnets. But you can use set
as well, a quick example for set of strings to create AWS IAM User:
resource "aws_iam_user" "the-accounts" {
for_each = toset( ["Adam", "Kyle", "Nelson", "Cody"] )
name = each.key
}
Example: Provisioning Azure Subnets
Below are the to the Azure VNet Subnet example, I am currently using this example in one of the Terraform Modules to deploy Pure Cloud Block Store in Azure with all the required prerequisites. Link to repo.
variables.tf
variable "subnets" {
type = map(any)
default = {
cbs_subnet_mgmt = {
name = "cbs_subnet_mgmt"
address_prefixes = ["10.10.1.0/24"]
}
cbs_subnet_iscsi = {
name = "cbs_subnet_iscsi"
address_prefixes = ["10.10.2.0/24"]
}
cbs_subnet_repl = {
name = "cbs_subnet_repl"
address_prefixes = ["10.10.3.0/24"]
}
cbs_subnet_sys = {
name = "cbs_subnet_sys"
address_prefixes = ["10.10.4.0/24"]
}
}
}
In the main.tf I was able to pass the name of each subnet by using each.value["name"]
. Also, I have used a conditional argument since I only wanted to configure the cbs_system_subnet with service endpoints.
= each.value["map.key"] == "map.value" ? ["If yes, do something"] : ["If not, do nothing"]
main.tf
resource "azurerm_subnet" "subnet" {
for_each = var.subnets
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.cbs_virtual_network.name
name = format("%s%s%s", var.resource_group_name, var.resource_group_location, each.value["name"])
address_prefixes = each.value["address_prefixes"]
service_endpoints = each.value["name"] == "cbs_subnet_sys" ? ["Microsoft.AzureCosmosDB", "Microsoft.KeyVault"] : []
}
output.tf
output "azure_subnet_id" {
value = {
for id in keys(var.subnets) : id => azurerm_subnet.subnet[id].id
}
description = "Lists the ID's of the subnets"
}
output "azure_subnet_name" {
value = {
for id in keys(var.subnets) : id => azurerm_subnet.subnet[id].name
}
description = "Lists the Names's of the subnets"
}
Sum’ It up
Terraform, is no doubt a powerful infrastructure as code tool, provides a multitude of features to simplify the provisioning and management of cloud resources. Among these features, the for_each
. By Leveraging it I was able to reduce code duplication and improve code maintainability. Hope you would find this useful and it will influence you to script cleaner code and build reusable modules.
comments powered by Disqus