0

I seem to be having trouble creating a module for VMs that leverage availability zones. It appears in terraform the zones attribute supports only one item per VM, so iteration for the VM is needed for each availability zone, i.e 1,2, or 3.

This works fine when it is just a VM and no data disks needing attached, however, we have some VMs that require multiple data disks, sometimes 1, 2, or more. The data disk creation has a count on the number of disks supplied in the data disk variable. This causes problems as you cannot have a count and a for_each loop in conjunction with each other in the same resource block when you go to associate the disks to azure VM. Need help is attempting to resolve that, i've tried removing the for_each, adding a dynamic block for the azure_virtual_machine_id but it did not like that. Not sure what else it could be.

Terraform Code

    resource "azurerm_virtual_machine" "vm" {
          for_each = toset(var.availability_zone)
          name                = var.vm_name
          location            = var.location
          resource_group_name   = var.resource_group_name
          network_interface_ids = [azurerm_network_interface.nic.id]
          vm_size = var.size
          availability_set_id    = azurerm_availability_set.av_set.id
          tags = var.tags
          zones = each.value
          identity {
            type = "SystemAssigned"
          }
        
          boot_diagnostics {
            enabled     = true
            storage_uri = ""
          }
        
          storage_os_disk {
            name              = "${var.vm_name}-osDisk"
            caching           = var.os_disk_caching
            create_option     = "FromImage"
            managed_disk_type = var.os_managed_disk_type
            disk_size_gb      = var.os_disk_size_gb
          }
        
          os_profile {
            computer_name  = var.vm_name
            admin_username = var.admin_username
            admin_password = var.admin_password
          }
          storage_image_reference {
            publisher = var.image_publisher
            offer     = var.image_offer
            sku       = var.image_sku
            version   = var.image_version
          }
        
          os_profile_windows_config {
            provision_vm_agent        = true
            enable_automatic_upgrades = true
          }
        
          delete_os_disk_on_termination    = true
          delete_data_disks_on_termination = true
        
        }
        
        resource "azurerm_managed_disk" "data" {
          count                = var.data_disk_count
          name                 = "${var.vm_name}-DataDisk-${count.index + 1}"
          location             = var.location
          resource_group_name  = var.resource_group_name
          storage_account_type = var.data_disk_storage_account_type
          create_option        = "Empty"
          disk_size_gb         = var.data_disk_size_gb
          tags                 = var.tags
        }
        resource "azurerm_virtual_machine_data_disk_attachment" "data" {
          for_each = azurerm_virtual_machine.vm
          count    = var.data_disk_count
          managed_disk_id    = azurerm_managed_disk.data[count.index].id
          for_each = azurerm_virtual_machine.vm
          virtual_machine_id = azurerm_virtual_machine.vm[each.key].id
          lun                = count.index + 5
          caching            = var.data_disk_caching
        }

Error

Error: Invalid combination of "count" and "for_each"
│
│   on modules\virtual_machine\main.tf line 92, in resource "azurerm_virtual_machine_data_disk_attachment" "data":
│   92:   for_each = azurerm_virtual_machine.vm
│
│ The "count" and "for_each" meta-arguments are mutually-exclusive, only one should be used to be explicit about the number of resources to be created.

Then if you omit the for_each loop in the azurerm_virtual_machine_data_disk_attachment resource block the error becomes:

Error: Missing resource instance key
│
│   on modules\virtual_machine\main.tf line 92, in resource "azurerm_virtual_machine_data_disk_attachment" "data":
│   92:   virtual_machine_id = azurerm_virtual_machine.vm.id
│
│ Because azurerm_virtual_machine.vm has "for_each" set, its attributes must be accessed on specific instances.
│
│ For example, to correlate with indices of a referring resource, use:
│     azurerm_virtual_machine.vm[each.key]
2
  • Try the resource "azurerm_virtual_machine_data_disk_attachment" resource, iterate over VMs using for_each and attach disks using count. This avoids conflicts between for_each and count. @NickP this might solve the blocker you're facing Commented Jun 25, 2024 at 14:25
  • This will be tough to construct a combined matrix of the two if one is not a set, list, or map. Would you be ok to convert the count with a number value to a for_each with a set value? Commented Jun 25, 2024 at 17:20

1 Answer 1

1

Create multiple data disks with VM in availability zones Issue while using terraform.

The Issue you're facing the is because 'count' and 'for_each' together which in practical not possible in terraform and also incorrect use of for_each which is used fetch the respective keys.

To overcome this issue, I tried make changes by using same for_each function for VM creation, disks & its attachments. Then passing locals in creating the disks for every VM.

Terraform Configuration:

main.tf:

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "rg" {
  name     = var.resource_group_name
  location = var.location
}

resource "azurerm_network_interface" "nic" {
  for_each            = { for idx, az in var.availability_zones : idx => az }
  name                = "${var.vm_name}-${each.value}-nic"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "${var.vm_name}-${each.value}-ipConfig"
    subnet_id                     = var.subnet_id
    private_ip_address_allocation = "Static"
    private_ip_address            = var.static_ip_addresses[each.key]
  }
}

resource "azurerm_virtual_machine" "vm" {
  for_each            = { for idx, az in var.availability_zones : idx => az }
  name                = "${var.vm_name}-${each.value}"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.nic[each.key].id]
  vm_size             = var.size
  zones               = [each.value]

  identity {
    type = "SystemAssigned"
  }

  storage_os_disk {
    name              = "${var.vm_name}-${each.value}-osDisk"
    caching           = var.os_disk_caching
    create_option     = "FromImage"
    managed_disk_type = var.os_managed_disk_type
    disk_size_gb      = var.os_disk_size_gb
  }

  os_profile {
    computer_name  = "${var.vm_name}-${each.value}"
    admin_username = var.admin_username
    admin_password = var.admin_password
  }

  storage_image_reference {
    publisher = var.image_publisher
    offer     = var.image_offer
    sku       = var.image_sku
    version   = var.image_version
  }

  os_profile_windows_config {
    provision_vm_agent        = true
    enable_automatic_upgrades = true
  }

  delete_os_disk_on_termination    = true
  delete_data_disks_on_termination = true

  tags = var.tags
}

locals {
  data_disks = flatten([
    for vm_key, az in var.availability_zones : [
      for disk_num in range(1, var.data_disk_count + 1) : {
        name     = "${var.vm_name}-${az}-DataDisk-${disk_num}"
        zone     = az
        vm_key   = vm_key
        disk_num = disk_num
      }
    ]
  ])
}

resource "azurerm_managed_disk" "data_disk" {
  for_each = { for disk in local.data_disks : "${disk.vm_key}-${disk.disk_num}" => disk }

  name                 = each.value.name
  location             = azurerm_resource_group.rg.location
  resource_group_name  = azurerm_resource_group.rg.name
  storage_account_type = var.data_disk_storage_account_type
  create_option        = "Empty"
  disk_size_gb         = var.data_disk_size_gb
  zone                 = each.value.zone
  tags                 = var.tags
}

resource "azurerm_virtual_machine_data_disk_attachment" "data_disk_attachment" {
  for_each = { for disk in local.data_disks : "${disk.vm_key}-${disk.disk_num}" => disk }

  virtual_machine_id = azurerm_virtual_machine.vm[each.value.vm_key].id
  managed_disk_id    = azurerm_managed_disk.data_disk[each.key].id
  lun                = each.value.disk_num + 4
  caching            = var.data_disk_caching
}

Variable.tf:

 variable "resource_group_name" {
  description = "The name of the resource group"
  type        = string
}

variable "location" {
  description = "The location of the resource group"
  type        = string
}

variable "vm_name" {
  description = "The name of the virtual machine"
  type        = string
}

variable "subnet_id" {
  description = "The subnet ID for the VM"
  type        = string
}

variable "size" {
  description = "The size of the virtual machine"
  type        = string
}

variable "os_disk_caching" {
  description = "The caching type for the OS disk"
  type        = string
}

variable "os_managed_disk_type" {
  description = "The type of the OS managed disk"
  type        = string
}

variable "os_disk_size_gb" {
  description = "The size of the OS disk in GB"
  type        = number
}

variable "admin_username" {
  description = "The admin username for the VM"
  type        = string
}

variable "admin_password" {
  description = "The admin password for the VM"
  type        = string
}

variable "image_publisher" {
  description = "The publisher of the VM image"
  type        = string
}

variable "image_offer" {
  description = "The offer of the VM image"
  type        = string
}

variable "image_sku" {
  description = "The SKU of the VM image"
  type        = string
}

variable "image_version" {
  description = "The version of the VM image"
  type        = string
}

variable "tags" {
  description = "Tags to apply to resources"
  type        = map(string)
}

variable "data_disk_count" {
  description = "The number of data disks to attach to the VM"
  type        = number
}

variable "data_disk_size_gb" {
  description = "The size of the data disks in GB"
  type        = number
}

variable "data_disk_storage_account_type" {
  description = "The storage account type for the data disks"
  type        = string
}

variable "data_disk_caching" {
  description = "The caching type for the data disks"
  type        = string
}

variable "availability_zones" {
  description = "The availability zones for the VMs"
  type        = list(string)
}

variable "static_ip_addresses" {
  description = "The static IP addresses to assign to the NICs"
  type        = list(string)
}

Deployement succedded:

enter image description here

enter image description here

enter image description here

enter image description here

reference:

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_data_disk_attachment

Sign up to request clarification or add additional context in comments.

3 Comments

this worked on the VM, until the NIC were set to static IP allocation and pass a list with 2 or 3 IPs
Made necessary changes in configuration as you mentioned in the code so that youre allowed to pass n number of IPs @NickP
you sir deserve a raise! thank you, didnt think i could call a list without first declaring the loop.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.