I am trying to attach network interface to backendend pool as per the below requirement
app-poc-1-nic & app-poc-2-nic to app-lb backendpool
db-nic-1-1-nic & db-nic-1-2-nic to db-lb backendpool
Below is the local block with nested map of objects
locals {
vms = {
nodes = {
app_node1 = {
"vm_name" = "app-poc"
"vm_num" = "1"
networks = {
nic1 = {
"vm_name" = "app-poc"
"subnet" = "/subscriptions/*****/subnets/app"
},
}
},
app_node2 = {
"vm_name" = "app-poc"
"vm_num" = "2"
networks = {
nic1 = {
"vm_name" = "app-poc"
"subnet" = "/subscriptions/*****/subnets/app"
},
}
},
service_node1 = {
"vm_name" = "service-poc"
"vm_num" = "1"
networks = {
nic1 = {
"vm_name" = "service-poc"
"subnet" = "/subscriptions/*****/subnets/app"
},
}
},
service_node2 = {
"vm_name" = "service-poc"
"vm_num" = "2"
networks = {
nic1 = {
"vm_name" = "service-poc"
"subnet" = "/subscriptions/*****/subnets/app"
},
}
},
db_node1 = {
"vm_name" = "db-poc"
"vm_num" = "1"
networks = {
nic1 = {
"vm_name" = "db-nic-1"
"subnet" = "/subscriptions/*****/subnets/app"
},
nic2 = {
"vm_name" = "db-nic-2"
"subnet" = "/subscriptions/*****/subnets/db"
}
}
},
db_node2 = {
"vm_name" = "db-poc"
"vm_num" = "2"
networks = {
nic1 = {
"vm_name" = "db-nic-1"
"subnet" = "/subscriptions/*****/subnets/app"
},
nic2 = {
"vm_name" = "db-nic-2"
"subnet" = "/subscriptions/*****/subnets/app"
},
}
},
},
}
lbs = {
tiers = {
app-lb = {
lb_name = "app-lb"
fip_name = "app-fip"
subnet_id = "/subscriptions/*****/subnets/app"
private_ip_type = "Dynamic"
address_pool_name = "app-address-pool"
lb_probes = {
ssh_probe = {
protocol = "Tcp"
port = "22"
}
}
lb_rules = {
ssh_rule = {
frontend_port = "22"
protocol = "Tcp"
backend_port = "22"
enable_floating_ip = true
frontend_ip_config_name = "app-fip"
}
}
},
db-lb = {
lb_name = "db-lb"
fip_name = "db-fip"
subnet_id = "/subscriptions/*****/subnets/app"
private_ip_type = "Dynamic"
address_pool_name = "db-address-pool"
lb_probes = {
ssh_probe = {
protocol = "Tcp"
port = "22"
}
}
lb_rules = {
ssh_rule = {
frontend_port = "22"
protocol = "Tcp"
backend_port = "22"
enable_floating_ip = false
frontend_ip_config_name = "db-fip"
}
}
}
}
}
}
Below are terraform resources that creates Network Interfaces, Virtual Machines, load balancer, probe, lb rules
data "azurerm_resource_group" "rg" {
name = "test-rg"
}
resource "azurerm_network_interface" "nic-poc" {
for_each = {
for vm in flatten([
for vm_name, vm in local.vms.nodes : [
for nic_name, nic in vm.networks : {
vm_number = vm.vm_num,
vm_name = vm_name,
nic_value = nic.vm_name,
subnet_value = nic.subnet
nic_name = nic_name
}
]
]
) : "${vm.vm_name}-${vm.nic_name}" => vm
}
name = "${each.value.nic_value}-${each.value.vm_number}-nic"
location = data.azurerm_resource_group.rg.location
resource_group_name = data.azurerm_resource_group.rg.name
ip_configuration {
name = "${each.value.nic_name}-${each.value.vm_number}-ipconfig"
subnet_id = each.value.subnet_value
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_linux_virtual_machine" "vm-poc" {
depends_on = [ azurerm_network_interface.nic-poc]
for_each = local.vms.nodes
name = "${each.value.vm_name}-${each.value.vm_num}"
admin_username = "test-admin"
admin_password = "password@29"
disable_password_authentication = false
location = data.azurerm_resource_group.rg.location
resource_group_name = data.azurerm_resource_group.rg.name
network_interface_ids = [for nic_key, nic in azurerm_network_interface.nic-poc : nic.id if startswith(nic_key, "${each.key}-")]
size = "Standard_B2ms"
identity {
type = "SystemAssigned"
}
os_disk {
name = "${each.value.vm_name}-${each.value.vm_num}-OSdisk"
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "RedHat"
offer = "RHEL"
sku = "82gen2"
version = "latest"
}
}
resource "azurerm_lb" "lb" {
for_each = local.lbs.tiers
name = each.value.lb_name
location = data.azurerm_resource_group.rg.location
resource_group_name = data.azurerm_resource_group.rg.name
sku = "Standard"
frontend_ip_configuration {
name = each.value.fip_name
subnet_id = each.value.subnet_id
private_ip_address_allocation = each.value.private_ip_type
}
}
resource "azurerm_lb_backend_address_pool" "bepool" {
for_each = local.lbs.tiers
loadbalancer_id = azurerm_lb.lb[each.key].id
name = each.value.address_pool_name
}
resource "azurerm_lb_probe" "probe" {
for_each = { for lb, details in local.lbs.tiers : lb => details.lb_probes }
loadbalancer_id = azurerm_lb.lb[each.key].id
name = "ssh-probe"
protocol = each.value["ssh_probe"].protocol
port = each.value["ssh_probe"].port
}
resource "azurerm_lb_rule" "rule" {
for_each = { for lb, details in local.lbs.tiers : lb => details.lb_rules }
loadbalancer_id = azurerm_lb.lb[each.key].id
name = "ssh-rule"
protocol = each.value["ssh_rule"].protocol
frontend_port = each.value["ssh_rule"].frontend_port
backend_port = each.value["ssh_rule"].backend_port
frontend_ip_configuration_name = azurerm_lb.lb[each.key].frontend_ip_configuration[0].name
enable_floating_ip = each.value["ssh_rule"].enable_floating_ip
backend_address_pool_ids = [azurerm_lb_backend_address_pool.bepool[each.key].id]
probe_id = azurerm_lb_probe.probe[each.key].id
}
Below is the code I have problem to handle the two different maps using for_loop
resource "azurerm_network_interface_backend_address_pool_association" "pool1-1" {
for_each = local.vms.nodes
network_interface_id = [for nic_key, nic in azurerm_network_interface.nic-poc : nic.id if startswith(nic_key, "${each.key}-")]
ip_configuration_name = [for nic_key, nic in azurerm_network_interface.nic-poc : nic.ip_configuration.name if startswith(nic_key, "${each.key}-")]
backend_address_pool_id = azurerm_lb_backend_address_pool.bepool[each.key].id
}
Error :
Error: Invalid index
│
│ on main.tf line 238, in resource "azurerm_network_interface_backend_address_pool_association" "pool1-1":
│ 238: backend_address_pool_id = azurerm_lb_backend_address_pool.bepool[each.key].id
│ ├────────────────
│ │ azurerm_lb_backend_address_pool.bepool is object with 2 attributes
│ │ each.key is "db_node2"
│
│ The given key does not identify an element in this collection value.
Other errors for the azurerm_network_interface_backend_address_pool_association resource
Error: Incorrect attribute value type
│
│ on main.tf line 340, in resource "azurerm_network_interface_backend_address_pool_association" "pool1-1":
│ 340: network_interface_id = [for nic_key, nic in azurerm_network_interface.nic-poc : nic.id if startswith(nic_key, "${each.key}-")]
│ ├────────────────
│ │ azurerm_network_interface.nic-poc is object with 8 attributes
│ │ each.key is "service_node1"
│
│ Inappropriate value for attribute "network_interface_id": string required.
Error: Incorrect attribute value type
│
│ on main.tf line 341, in resource "azurerm_network_interface_backend_address_pool_association" "pool1-1":
│ 341: ip_configuration_name = [for nic_key, nic in azurerm_network_interface.nic-poc : nic.ip_configuration.name if startswith(nic_key, "${each.key}-")]
│ ├────────────────
│ │ azurerm_network_interface.nic-poc is object with 8 attributes
│ │ each.key is "service_node2"
│
│ Inappropriate value for attribute "ip_configuration_name": string required.
Could someone throw someone light on this ? Thank you in advance.
Helder Sepulveda suggestion error :
│ Error: Invalid index
│
│ on main.tf line 199, in resource "azurerm_network_interface_backend_address_pool_association" "nic_lb_association":
│ 199: ip_configuration_name = azurerm_network_interface.nic-poc[each.key].ip_configuration[0].name
│ ├────────────────
│ │ azurerm_network_interface.nic-poc is object with 8 attributes
│ │ each.key is "db_node1"
│
│ The given key does not identify an element in this collection value.
From the first line in your question you have:
... requirement
app-poc-1-nic & app-poc-2-nic to app-lb backendpool
db-nic-1-1-nic & db-nic-1-2-nic to db-lb backendpool
as suggested by @VonC you need a map that creates that relation, I like his approach but I do not like hardcoding that map, instead we can get it dynamically ...
We can see that the vm nodes have app
and db
as a prefix same with the lb tiers, I'm going to assume that pattern remains the same in larger dataset, so we can use that prefix to build the map, see my sample code below:
locals {
vms = {
nodes = {
app_node1 = {},
app_node2 = {},
service_node1 = {},
service_node2 = {},
db_node1 = {},
db_node2 = {},
},
}
lbs = {
tiers = {
app-lb = {},
db-lb = {}
}
}
net_backend = {
for vm in keys(local.vms.nodes) :
vm => [
for lb in keys(local.lbs.tiers) : lb
if split("-", lb)[0] == split("_", vm)[0]
]
}
vm_to_lb_map = {
for k, v in local.net_backend : k => v[0]
if length(v) > 0
}
}
output "vm_to_lb_map" {
value = local.vm_to_lb_map
}
I'm simplifying your data to keep the code short, for our purposes we really do not care about the values so I used just {}
hopefully that does not confuse anyone.
Let's break it down...
I added two new local variables
net_backend = {
for vm in keys(local.vms.nodes) :
vm => [
for lb in keys(local.lbs.tiers) : lb
if split("-", lb)[0] == split("_", vm)[0]
]
}
The net_backend
loops over the vm nodes and the lb tiers looking for matches in the prefix, (notice you are splitting by underscore in one but dashes on the other, would be nice to stick to one and keep it standard) at the end of this we end up with some records that do not have a match...
vm_to_lb_map = {
for k, v in local.net_backend : k => v[0]
if length(v) > 0
}
The vm_to_lb_map
cleans up those that did not not get a match
...and a terraform plan on that code will give us:
terraform plan
Changes to Outputs:
+ vm_to_lb_map = {
+ app_node1 = "app-lb"
+ app_node2 = "app-lb"
+ db_node1 = "db-lb"
+ db_node2 = "db-lb"
}
then you can do the same as suggested by @VonC
resource "azurerm_network_interface_backend_address_pool_association" "nic_lb_association" {
for_each = local.vm_to_lb_map
network_interface_id = azurerm_network_interface.nic-poc[each.key].id
ip_configuration_name = azurerm_network_interface.nic-poc[each.key].ip_configuration[0].name
backend_address_pool_id = azurerm_lb_backend_address_pool.bepool[each.value].id
}