After working on few of the Azure Terraform (Infrastructure as code) projects, I found I had to deploy same resource again and again, instead of defining same resource manually, I can use for_each loop to deploy resources smartly inside module block. In this example I am going to deploy Azure Virtual Network with multiple subnets in it, Below is the hierarchy of files and folders of terraform scripts as shown in the screenshot.
Here I have 2 example articles to understand basics of terraform modules Writing and Using Terraform modules and Terraform Using one module variable in another module for beginners.
Download terraform script Terraform_Module_for_each_loop_example_of_same_resoruce.zip here or it is also available on github.com.
In the module, below are resource block defined to deploy Azure Virtual Network (it is plain resource blocks with variable file) and I haven't mentioned anything about subnet in it and outputting vnet_name and rg_name values.
#Location: .\Modules\virtual_network #Azure virtual network module main.tf file resource "azurerm_virtual_network" "vnet" { name = var.name resource_group_name = var.resource_group_name location = var.location address_space = var.address_space } #Azure virtual network module output.tf file output "vnet_name" { value = var.name } output "rg_name" { value = var.resource_group_name } #Azure virtual network module variable.tf file variable "name" { type = string default = "example_vnet" } variable "resource_group_name" { type = string default = "vCloud-lab.com" } variable "location" { type = string default = "West US" } variable "address_space" { type = list(any) default = ["10.0.0.0/16"] }
Below is the subnet scripts block inside module file, it is using plain single subnet resource ready to deploy inside Azure Virtual Network. If you see below, I am not using any for_each loop here.
#Location: .\Modules\subnet #Azure Subnet module main.tf file resource "azurerm_subnet" "subnet" { name = var.name resource_group_name = var.resource_group_name virtual_network_name = var.virtual_network_name address_prefixes = var.address_prefixes } #Azure Subnet module variable.tf file variable "name" { type = string default = "web_subnet" } variable "resource_group_name" { type = string default = "vCloud-lab.com" } variable "virtual_network_name" { type = string default = "example_vnet" } variable "address_prefixes" { type = list default = ["10.0.2.0/24"] }
This is main.tf file content on the root folder, I have defined providers, VNET resource. Regarding subnet, One variable block for subnet where I have mentioned type map data with multiple subnet details. The main part in the script I am using depends_on in the module to ensure virtual network is created before creating subnets.
#Location: .\ #Terraform module main.tf file # We strongly recommend using the required_providers block to set the # Azure Provider source and version being used terraform { required_providers { azurerm = { source = "hashicorp/azurerm" version = ">=2.86.0" } } } # Configure the Microsoft Azure Provider provider "azurerm" { features {} } module "virtual_network" { source = "./modules/virtual_network" name = "test_vnet" resource_group_name = "vCloud-lab.com" location = "West US" address_space = ["10.1.0.0/16"] } variable "subnets" { description = "Map of Azure VNET subnet configuration" type = map(any) default = { app_subnet = { name = "app_subnet", resource_group_name = "vCloud-lab.com", virtual_network_name = "example_vnet", address_prefixes = ["10.1.1.0/24"] }, db_subnet = { name = "db_subnet", resource_group_name = "vCloud-lab.com", virtual_network_name = "example_vnet", address_prefixes = ["10.1.2.0/24"] } } } module "subnet" { source = "./modules/subnet" for_each = var.subnets name = each.value.name virtual_network_name = module.virtual_network.vnet_name resource_group_name = module.virtual_network.rg_name address_prefixes = each.value.address_prefixes depends_on = [ module.virtual_network ] }
After running terraform init, modules and provider plugins are initiated and downloaded.
PS D:\Projects\Terraform\Demo\All_Resource_In_Module\virtual_network> terraform init Initializing modules... - subnet in modules\subnet - virtual_network in modules\virtual_network Initializing the backend... Initializing provider plugins... - Reusing previous version of hashicorp/azurerm from the dependency lock file - Using previously-installed hashicorp/azurerm v2.88.1 Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.
To verify all is good I am running terraform plan to verify no issues in the code/script and it will generate tfstate file, there are no errors.
PS D:\Projects\Terraform\Demo\All_Resource_In_Module\virtual_network> terraform plan 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.subnet["app_subnet"].azurerm_subnet.subnet will be created + resource "azurerm_subnet" "subnet" { + address_prefix = (known after apply) + address_prefixes = [ + "10.1.1.0/24", ] + enforce_private_link_endpoint_network_policies = false + enforce_private_link_service_network_policies = false + id = (known after apply) + name = "app_subnet" + resource_group_name = "vCloud-lab.com" + virtual_network_name = "test_vnet" } # module.subnet["db_subnet"].azurerm_subnet.subnet will be created + resource "azurerm_subnet" "subnet" { + address_prefix = (known after apply) + address_prefixes = [ + "10.1.2.0/24", ] + enforce_private_link_endpoint_network_policies = false + enforce_private_link_service_network_policies = false + id = (known after apply) + name = "db_subnet" + resource_group_name = "vCloud-lab.com" + virtual_network_name = "test_vnet" } # module.virtual_network.azurerm_virtual_network.vnet will be created + resource "azurerm_virtual_network" "vnet" { + address_space = [ + "10.1.0.0/16", ] + dns_servers = (known after apply) + guid = (known after apply) + id = (known after apply) + location = "westus" + name = "test_vnet" + resource_group_name = "vCloud-lab.com" + subnet = (known after apply) + vm_protection_enabled = false } Plan: 3 to add, 0 to change, 0 to destroy. ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. PS D:\Projects\Terraform\Demo\All_Resource_In_Module\virtual_network>
This is the final step, I have applied the terraform configuration with terraform apply --auto-approve and there is no error, all the resources are deployed successfully.
PS D:\Projects\Terraform\Demo\All_Resource_In_Module\virtual_network> terraform apply --auto-approve
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.subnet["app_subnet"].azurerm_subnet.subnet will be created
+ resource "azurerm_subnet" "subnet" {
+ address_prefix = (known after apply)
+ address_prefixes = [
+ "10.1.1.0/24",
]
+ enforce_private_link_endpoint_network_policies = false
+ enforce_private_link_service_network_policies = false
+ id = (known after apply)
+ name = "app_subnet"
+ resource_group_name = "vCloud-lab.com"
+ virtual_network_name = "test_vnet"
}
# module.subnet["db_subnet"].azurerm_subnet.subnet will be created
+ resource "azurerm_subnet" "subnet" {
+ address_prefix = (known after apply)
+ address_prefixes = [
+ "10.1.2.0/24",
]
+ enforce_private_link_endpoint_network_policies = false
+ enforce_private_link_service_network_policies = false
+ id = (known after apply)
+ name = "db_subnet"
+ resource_group_name = "vCloud-lab.com"
+ virtual_network_name = "test_vnet"
}
# module.virtual_network.azurerm_virtual_network.vnet will be created
+ resource "azurerm_virtual_network" "vnet" {
+ address_space = [
+ "10.1.0.0/16",
]
+ dns_servers = (known after apply)
+ guid = (known after apply)
+ id = (known after apply)
+ location = "westus"
+ name = "test_vnet"
+ resource_group_name = "vCloud-lab.com"
+ subnet = (known after apply)
+ vm_protection_enabled = false
}
Plan: 3 to add, 0 to change, 0 to destroy.
module.virtual_network.azurerm_virtual_network.vnet: Creating...
module.virtual_network.azurerm_virtual_network.vnet: Still creating... [10s elapsed]
module.virtual_network.azurerm_virtual_network.vnet: Creation complete after 12s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/vCloud-lab.com/providers/Microsoft.Network/virtualNetworks/test_vnet]
module.subnet["db_subnet"].azurerm_subnet.subnet: Creating...
module.subnet["app_subnet"].azurerm_subnet.subnet: Creating...
module.subnet["app_subnet"].azurerm_subnet.subnet: Creation complete after 10s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/vCloud-lab.com/providers/Microsoft.Network/virtualNetworks/test_vnet/subnets/app_subnet]
module.subnet["db_subnet"].azurerm_subnet.subnet: Still creating... [10s elapsed]
module.subnet["db_subnet"].azurerm_subnet.subnet: Creation complete after 18s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/vCloud-lab.com/providers/Microsoft.Network/virtualNetworks/test_vnet/subnets/db_subnet]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
PS D:\Projects\Terraform\Demo\All_Resource_In_Module\virtual_network>
Useful Articles
Get Azure virtual machine backup reports using Powershell
Why is my Azure recovery services vault not getting deleted?
Create an Azure virtual machine scale set and load balancer using Terraform
Azure Terraform fixed Availibility Zones on Virtual Machine Scale Set
Writing and Using Terraform modules
Terraform Using one module variable in another module
Hashicorp Terraform dynamic block with example
Terraform for_each loop on map example
Terraform for_each loop on resource example