Inicio Linux & Systems Cybersecurity Cloud & DevOps Networks & Infrastructure SIEM & Monitoring DFIR & Threat Intel Development & Other Todas las categorias Herramientas

Gestión de discos en Azure VMs con Terragrunt: cómo mapear IDs de guest OS a discos gestionados

Gestión de discos en Azure VMs con Terragrunt: cómo mapear IDs de guest OS a discos gestionados

Tabla de contenidos

El problema: dos mundos, dos identificadores

Escenario habitual en equipos que gestionan infraestructura Azure con IaC: el equipo de sistemas Windows (Wintel) abre un ticket solicitando cambios en discos de una VM. El problema es que hablan idiomas diferentes:

  • Wintel dice: "Elimina el disco con Msft Virtual Disk ID 60022480XXXXXXXXXXXXXXXXXXXXXXXX y amplía el de 32 GB a 48 GB"
  • Tú ves en Terraform: data1, data2, data3 con LUNs y tamaños

El ID que proporciona Windows (60022480...) es un UniqueId del guest OS que no aparece en ningún sitio de tu código IaC ni en el portal de Azure. No puedes simplemente hacer grep en el repositorio y encontrar la respuesta.

Este artículo documenta el proceso completo para resolver este mapeo y ejecutar el cambio de forma segura con Terragrunt.

Conceptos clave

Antes de entrar en materia, aclaremos la terminología:

ConceptoDónde viveEjemplo
UniqueId (Guest)Dentro de Windows60022480B94E649D631F68911AA694D1
Managed DiskAzure Resource Managervm-name-data2-1x
LUNConfiguración de VM en Azure10
DiskNumberWindows Disk Management2
Nombre en IaCTerragrunt/Terraformdata2

La clave es entender que DiskNumber en Windows ≠ LUN en Azure. Son conceptos independientes que requieren correlación cruzada.

Paso 1: Inventario desde Azure (fuente de verdad)

Lo primero es obtener qué discos tiene realmente la VM en Azure:

BASH
az account set --subscription "<subscription-id>"

az vm show -g <resource-group> -n <vm-name> \
  --query "storageProfile.dataDisks[].{LUN:lun,Name:name,SizeGB:diskSizeGb}" \
  -o table

Resultado ejemplo:

CODE
LUN    Name                  SizeGB
-----  --------------------  --------
1      vm-name-data1-1x      128
10     vm-name-data2-1x      32
11     vm-name-data3-1x      128

Ya sabemos: 3 data disks en Azure con LUN 1, 10 y 11.

Paso 2: Inventario desde el guest OS (RunCommand)

Si no tienes acceso RDP a la VM, puedes ejecutar un script PowerShell remotamente con az vm run-command invoke. Este paso suele requerir varias iteraciones porque el entorno no siempre devuelve lo esperado.

2.1 Lanzar RunCommand desde Azure CLI

BASH
az vm run-command invoke \
  -g <resource-group> \
  -n <vm-name> \
  --command-id RunPowerShellScript \
  --scripts "Get-Disk | Select-Object Number, @{N='SizeGB';E={[math]::Round(\$_.Size/1GB,0)}}, UniqueId | Format-Table -AutoSize"

La respuesta viene en JSON con los campos stdout y stderr. Si el script falla, el error aparecerá en stderr.

2.2 El error del script truncado (primer intento fallido)

En el primer intento, se envió un script más complejo con Where-Object para filtrar por ID específico. El resultado fue:

CODE
You must provide a value expression following the '-eq' operator.
Missing closing '}' in statement block or type definition.

¿Por qué ocurre? El script se cortó a mitad de un bloque Where-Object { $_.Index -eq ... }. RunCommand tiene un límite práctico en el tamaño del --scripts inline, y caracteres especiales como $, {, } necesitan escapado correcto dependiendo del shell desde donde se lance.

Solución: Usar el script completo sin truncar y con variables explícitamente casteadas:

POWERSHELL
Get-Disk | ForEach-Object {
  $d = $_
  $wmi = Get-CimInstance Win32_DiskDrive |
    Where-Object { $_.Index -eq [int]$d.Number } |
    Select-Object -First 1
  [pscustomobject]@{
    DiskNumber = $d.Number
    SizeGB     = [math]::Round($d.Size / 1GB, 0)
    UniqueId   = $d.UniqueId
    LUN        = $wmi.SCSITargetId
    Model      = $wmi.Model
  }
} | Sort-Object DiskNumber | Format-Table -AutoSize

Consejo: Si el script es largo, es más fiable pasarlo como fichero con --scripts @script.ps1 en vez de inline.

2.3 El resultado confuso: LUN=0 para todos

El segundo intento se ejecutó correctamente, pero devolvió algo inesperado al consultar un disco específico por su UniqueId:

CODE
TargetId     : 600224808C5B855329AF53DEFF061AF0
DiskNumber   : 1
UniqueId     : 600224808C5B855329AF53DEFF061AF0
SerialNumber :
LUN          : 0

Aquí es donde muchos se confunden: el disco aparece con LUN 0, pero en Azure los data disks de esta VM están en LUN 1, 10 y 11. ¿Cómo es posible?

Explicación: SCSITargetId (que es lo que Win32_DiskDrive expone como LUN dentro del guest) no es lo mismo que el LUN de Azure. En muchas VMs Azure con Windows:

  • El controlador SCSI del guest reporta SCSITargetId = 0 para todos los discos Msft Virtual Disk
  • El LUN real de Azure solo es visible desde la API de Azure (el az vm show del Paso 1)
  • Intentar mapear usando este campo te llevará a decisiones incorrectas

2.4 Inventario completo final (la evidencia decisiva)

Ante la ambigüedad del LUN en guest, lo correcto es obtener el inventario completo de todos los discos con sus tamaños y UniqueIds:

CODE
DiskNumber  SizeGB  UniqueId                                  LUN  Model
----------  ------  --------                                  ---  -----
0           128     IDE\DISKVIRTUAL_HD...                     0    Virtual HD
1           128     600224808C5B855329AF53DEFF061AF0          0    Msft Virtual Disk
2           32      60022480B94E649D631F68911AA694D1          0    Msft Virtual Disk
3           128     60022480B16D7C3BE95C7D51B2235E9A          0    Msft Virtual Disk

Ahora sí tenemos lo que necesitamos: UniqueId + tamaño para cada disco. Esto, combinado con el inventario Azure, nos permite hacer la correlación.

Observaciones:

  • DiskNumber 0 con ID IDE\DISK... es el disco del SO (no se toca)
  • Los tres restantes son los managed data disks (todos reportan LUN 0 en guest, pero eso es irrelevante)
  • El de 32 GB es inequívocamente data2
  • Entre los dos de 128 GB, Wintel identificó explícitamente cuál eliminar por su UniqueId

Paso 3: Correlación cruzada (la tabla de mapeo)

Con la evidencia de ambos lados, construimos la tabla de decisión. Este es el artefacto más importante del proceso — es lo que justifica el cambio ante una auditoría:

UniqueId GuestSize GuestDisco AzureNombre IaCLUN AzureAcción
...1AF0128 GBvm-name-data1-1xdata11Eliminar
...94D132 GBvm-name-data2-1xdata210Resize → 48 GB
...5E9A128 GBvm-name-data3-1xdata311Mantener

Lógica de la decisión

  1. Disco de 32 GB: Solo hay uno en Azure (data2, LUN 10) y uno en el guest (UniqueId ...94D1). Mapeo inequívoco.
  2. Disco de 128 GB a eliminar: Wintel proporcionó el UniqueId explícito (...1AF0). En Azure hay dos discos de 128 GB (data1 en LUN 1 y data3 en LUN 11). Tomamos data1 como objetivo de eliminación basándonos en la evidencia combinada.
  3. Disco de 128 GB a conservar: data3 (LUN 11, UniqueId ...5E9A) no fue mencionado en la petición, se mantiene sin cambios.

Regla de oro: Cuando hay múltiples discos del mismo tamaño, nunca decidas solo por tamaño. Exige al equipo de sistemas que confirme el UniqueId, la letra de unidad, o el volumen label del disco afectado.

Paso 4: Implementación en Terragrunt

En el módulo de VM Windows, los discos de datos se definen en el bloque dynamic_data_disks del terragrunt.hcl:

Estado ANTES del cambio

HCL
dynamic_data_disks = {
  "data1" = {
    location                      = "North Europe"
    storage_account_type          = "StandardSSD_LRS"
    create_option                 = "Empty"
    managed_disk_size_gb          = 128
    public_network_access_enabled = false
    os_type                       = "Windows"
    lun                           = 1
    caching                       = "ReadWrite"
  }
  "data2" = {
    location                      = "North Europe"
    storage_account_type          = "StandardSSD_LRS"
    create_option                 = "Empty"
    managed_disk_size_gb          = 32
    public_network_access_enabled = false
    os_type                       = "Windows"
    lun                           = 10
    caching                       = "ReadWrite"
  }
  "data3" = {
    location                      = "North Europe"
    storage_account_type          = "StandardSSD_LRS"
    create_option                 = "Empty"
    managed_disk_size_gb          = 128
    public_network_access_enabled = false
    os_type                       = "Windows"
    lun                           = 11
    caching                       = "ReadWrite"
  }
}

Estado DESPUÉS del cambio

HCL
dynamic_data_disks = {
  "data2" = {
    location                      = "North Europe"
    storage_account_type          = "StandardSSD_LRS"
    create_option                 = "Empty"
    managed_disk_size_gb          = 48       # Antes: 32
    public_network_access_enabled = false
    os_type                       = "Windows"
    lun                           = 10
    caching                       = "ReadWrite"
  }
  "data3" = {
    location                      = "North Europe"
    storage_account_type          = "StandardSSD_LRS"
    create_option                 = "Empty"
    managed_disk_size_gb          = 128
    public_network_access_enabled = false
    os_type                       = "Windows"
    lun                           = 11
    caching                       = "ReadWrite"
  }
}

Cambios realizados:

  • Eliminado: bloque data1 completo (el módulo se encarga del detach + delete)
  • Modificado: managed_disk_size_gb de data2 de 32 → 48
  • Sin cambios: data3 permanece intacto

Paso 5: Validación pre-apply

BASH
terragrunt plan

El plan debe mostrar exactamente:

  • destroy: disco data1 (LUN 1, 128 GB)
  • update in-place: disco data2, size 32 → 48
  • Sin cambios en data3

Si aparece cualquier otro cambio inesperado: STOP. Revisa si hay drift en el estado o variables heredadas que hayan cambiado.

Paso 6: Apply y verificación post-cambio

BASH
terragrunt apply

Verificación Azure

BASH
az vm show -g <resource-group> -n <vm-name> \
  --query "storageProfile.dataDisks[].{LUN:lun,Name:name,SizeGB:diskSizeGb}" \
  -o table

Esperado:

CODE
LUN    Name                  SizeGB
-----  --------------------  --------
10     vm-name-data2-1x      48
11     vm-name-data3-1x      128

Verificación guest (solicitar a Wintel)

  1. Rescan de discos en Disk Management
  2. Confirmar que el disco eliminado ya no aparece
  3. Confirmar que el disco ampliado muestra 48 GB
  4. Extender la partición a nivel de filesystem (el resize de Azure no lo hace automáticamente)

Estrategia de rollback

Si algo sale mal post-apply:

  1. No tocar nada más — no intentar arreglar sobre la marcha
  2. Reponer data1 en dynamic_data_disks con sus valores originales
  3. Revertir data2 a 32 GB si es necesario
  4. terragrunt plan → verificar que el plan es coherente
  5. terragrunt apply

Nota importante: Si el disco eliminado tenía datos, el rollback creará un disco vacío nuevo. Los datos se habrán perdido. Por eso es crítico confirmar con el equipo de sistemas que el disco está libre de datos antes del apply.

Errores comunes y lecciones aprendidas

1. Confiar en SCSITargetId del guest

En muchas VMs Azure con Windows, Win32_DiskDrive.SCSITargetId devuelve 0 para todos los discos de datos. No uses ese valor como fuente de verdad para el LUN.

2. Scripts RunCommand incompletos

Si copias scripts PowerShell multilínea en RunCommand, asegúrate de que el bloque Where-Object { ... } esté completo. Un corte en medio produce errores crípticos:

CODE
You must provide a value expression following the '-eq' operator.
Missing closing '}' in statement block or type definition.

3. Dos discos del mismo tamaño

Cuando hay múltiples discos con el mismo tamaño (como dos de 128 GB), no puedes mapear solo por tamaño. Necesitas el UniqueId del guest o, alternativamente, que Wintel te confirme la letra de unidad/volumen asociada.

4. Resize no extiende la partición

Azure puede redimensionar el managed disk, pero el filesystem dentro de Windows seguirá viendo el tamaño anterior hasta que alguien ejecute Extend-Volume o lo haga desde Disk Management.

Checklist operativo

Usa esta checklist para cualquier cambio de discos en VMs Azure gestionadas con IaC:

  • [ ] Subscription y Resource Group confirmados
  • [ ] Inventario Azure (az vm show) guardado
  • [ ] Inventario guest (PowerShell) guardado
  • [ ] Tabla de mapeo completada y revisada
  • [ ] Cambio en IaC revisado (idealmente por segundo par de ojos)
  • [ ] terragrunt plan sin cambios inesperados
  • [ ] Backup/snapshot verificado antes de operación destructiva
  • [ ] Apply ejecutado en ventana de cambio aprobada
  • [ ] Verificación post-apply en Azure
  • [ ] Verificación post-apply con equipo de sistemas
  • [ ] Documentación archivada

Conclusión

El mapeo de disk IDs entre Windows guest y Azure managed disks no es trivial, especialmente cuando los identificadores que maneja cada equipo son completamente diferentes. La clave está en:

  1. No asumir — siempre cruzar evidencia de ambos lados
  2. Documentar la decisión — la tabla de mapeo es tu seguro ante auditorías
  3. Validar antes de destruir — un plan limpio no cuesta nada, un disco eliminado por error cuesta mucho

Con un flujo bien definido y Terragrunt/Terraform como fuente de verdad de la infraestructura, estos cambios pasan de ser operaciones de riesgo a procedimientos controlados y repetibles.

Comentarios