🔎 Verificación y validación de recursos en Azure¶
Un Apply complete! dice que Terraform terminó, no que la infraestructura sea la que querías. En este módulo aprenderás a comprobarlo por varios caminos independientes: el estado de Terraform, la API del emulador a través de Azure CLI y, cuando sea posible, el propio recurso funcionando. Todo se ejecuta contra Topaz; donde el emulador no ofrece lo que Azure real sí (portal, métricas, costes), lo verás señalado en un recuadro 🔷 En Topaz junto con lo que harías en una suscripción de verdad.
Tabla de contenidos 1. Objetivos de aprendizaje 2. Verificación desde Terraform (sustituye al Portal) 3. Verificación mediante Azure CLI 4. Azure PowerShell (solo Azure real) 5. Funcionalidad, monitorización y salud 6. Verificación automatizada 7. Solución de problemas comunes 8. Buenas prácticas de verificación 9. Recursos adicionales
🔷 Punto de partida. Este módulo verifica el despliegue del módulo anterior: grupo
rg-webapp-produccion-001eneastuscon VNet, subred, plan de App Service y aplicación web (la cuenta de almacenamiento es opcional). Ejecuta los comandos desde el directorio del proyecto (~/recursos) con la CLI en la nubeTopaz.
1. Objetivos de aprendizaje¶
- Verificar un despliegue de Terraform por tres vías independientes: estado local, API de Azure (CLI) y funcionalidad del recurso.
- Interpretar
terraform state,terraform showyterraform plan -detailed-exitcodecomo herramientas de validación. - Usar consultas JMESPath en Azure CLI para validar ubicación, etiquetas, tipos y estado de aprovisionamiento.
- Escribir un script de verificación reutilizable que sirva tanto en el emulador como en Azure real.
- Distinguir qué comprobaciones son posibles en Topaz y cuáles exigen una suscripción real (portal, métricas, costes).
- Diagnosticar discrepancias entre lo declarado, lo que hay en el estado y lo que existe en la nube.
2. Verificación desde Terraform¶
🔷 En Topaz no hay Azure Portal. El emulador expone la API de Resource Manager, pero no una interfaz web. En el laboratorio, la "vista visual" del despliegue la dan
terraform showy las salidas-o tablede la CLI. Al final de esta sección tienes la equivalencia con el Portal para cuando trabajes en Azure real.
2.1. ¿Qué cree Terraform que existe?¶
El estado es la memoria de Terraform. Empieza por leerlo:
azurerm_linux_web_app.produccion
azurerm_resource_group.produccion
azurerm_service_plan.produccion
azurerm_subnet.produccion
azurerm_virtual_network.produccion
Si falta un recurso que está en tu .tf, el apply no lo llegó a crear (o se creó fuera de Terraform). Si sobra uno que ya no está en el código, el siguiente plan propondrá destruirlo.
2.2. Detalle de un recurso¶
# Un recurso concreto, con todos sus atributos tal como los devolvió el emulador
terraform state show azurerm_virtual_network.produccion
# Todo el despliegue, legible
terraform show
# Todo el despliegue, en JSON para filtrar con jq
terraform show -json | jq -r '.values.root_module.resources[] | "\(.type)\t\(.values.name)\t\(.values.location // "-")"'
Fíjate en que state show muestra lo que el servidor devolvió, no lo que tú escribiste. Es la forma más rápida de descubrir atributos que el emulador ignora o normaliza.
2.3. Outputs: los valores que importan¶
Declara como output lo que quieras verificar sin bucear en el estado. Añade a tu proyecto un outputs.tf:
# archivo: outputs.tf
output "resource_group_id" {
value = azurerm_resource_group.produccion.id
}
output "vnet_address_space" {
value = azurerm_virtual_network.produccion.address_space
}
output "web_app_hostname" {
value = azurerm_linux_web_app.produccion.default_hostname
}
output "resumen" {
value = {
grupo = azurerm_resource_group.produccion.name
ubicacion = azurerm_resource_group.produccion.location
recursos = 5
}
}
terraform apply -refresh-only -auto-approve # registra los outputs sin tocar recursos
terraform output # todos
terraform output -raw web_app_hostname # uno, sin comillas (ideal para scripts)
terraform output -json | jq .resumen.value
2.4. La prueba definitiva: plan sin cambios¶
Un plan limpio significa que configuración, estado y realidad coinciden. Con -detailed-exitcode el resultado es un código de salida que un script puede evaluar:
terraform plan -detailed-exitcode -input=false
echo "Código de salida: $?"
# 0 = sin cambios (todo coincide) 2 = hay cambios (drift o código nuevo) 1 = error
🔷 En Topaz. Si el
plandevuelve 2 y el único cambio estagsen el grupo de recursos, es la limitación del emulador que ya conoces (no devuelve las etiquetas del grupo). Ellifecycle { ignore_changes = [tags] }del módulo anterior lo elimina; si lo has quitado, vuélvelo a poner.
2.5. Equivalencia con Azure Portal (Azure real)¶
Cuando trabajes contra una suscripción real, cada comprobación anterior tiene su vista en portal.azure.com. Aquí tienes la correspondencia directa:
| Qué verificas | En el laboratorio (Topaz) | En Azure Portal |
|---|---|---|
| El grupo existe, ubicación, etiquetas | az group show |
Grupos de recursos → buscar el nombre → Información general |
| Inventario de recursos | terraform state list, az resource list -o table |
Hoja del grupo → Recursos |
| Propiedades de un recurso | terraform state show, az resource show |
Recurso → Información general / Propiedades / Vista JSON |
| Quién hizo qué y cuándo | No disponible | Hoja del grupo → Registro de actividad, filtrar por Escribir |
| Coste del despliegue | No aplica (coste cero) | Hoja del grupo → Análisis de costos |
Esta tabla te permite traducir mentalmente los comandos de CLI/Terraform a la interfaz gráfica que encontrarás en un entorno productivo de Azure.
3. Verificación mediante Azure CLI¶
La CLI es un cliente independiente de Terraform: si ambos ven lo mismo, el despliegue es real. Antes de nada, confirma que hablas con el emulador:
az account show --query '{cloud:environmentName, sub:id}' -o json
# Esperado: "cloud": "Topaz", "sub": "00000000-0000-0000-0000-000000000001"
3.1. Inventario básico¶
RG=rg-webapp-produccion-001
az group show -n $RG -o table
az resource list -g $RG -o table
az resource list -g $RG --query "length([])" -o tsv # número de recursos (sin contar el grupo)
El número debe coincidir con terraform state list menos uno (el grupo no aparece en az resource list). Si desplegaste VNet, subred, plan y web app, la subred tampoco cuenta: es un recurso hijo y la CLI la lista dentro de la VNet. Espera 3.
3.2. Filtrado por tipo¶
az resource list -g $RG --resource-type Microsoft.Network/virtualNetworks -o table
az resource list -g $RG --resource-type Microsoft.Web/serverfarms -o table # planes de App Service
az resource list -g $RG --resource-type Microsoft.Web/sites -o table # aplicaciones web
az resource list -g $RG --resource-type Microsoft.Storage/storageAccounts -o table
# Subredes: cuelgan de la VNet
az network vnet subnet list -g $RG --vnet-name vnet-webapp-produccion -o table
3.3. Consultas JMESPath: ubicación y etiquetas¶
# Nombre, tipo y ubicación de todo
az resource list -g $RG --query "[].{nombre:name, tipo:type, ubicacion:location}" -o table
# Recursos fuera de la región esperada (debe devolver lista vacía)
az resource list -g $RG --query "[?location!='eastus'].{nombre:name, ubicacion:location}" -o json
# Recursos con la etiqueta correcta
az resource list -g $RG --query "[?tags.entorno=='produccion'].{nombre:name, entorno:tags.entorno}" -o table
# Recursos SIN la etiqueta obligatoria (debe devolver lista vacía)
az resource list -g $RG --query '[?tags.entorno==`null`].name' -o tsv
🔷 En Topaz. Las etiquetas de la VNet, el plan y la web app se devuelven correctamente. Las del grupo de recursos no:
az group show -n $RG --query tagsdevuelvenullaunque las declaraste. Por eso el script de la sección 6 valida las etiquetas sobre la VNet y no sobre el grupo. En Azure real ambas funcionan.
3.4. Estado de aprovisionamiento¶
az resource list no devuelve properties; hay que consultar cada recurso. Este bucle lo hace por ti:
az group show -n $RG --query properties.provisioningState -o tsv # Succeeded
for id in $(az resource list -g $RG --query "[].id" -o tsv); do
az resource show --ids "$id" --query "{nombre:name, estado:properties.provisioningState}" -o tsv
done
💡 🔷 En Topaz algunos tipos de recurso pueden devolver el estado vacío en lugar de
Succeeded: el emulador no implementa todas las propiedades. Trátalo como "existe y no ha fallado". En Azure real, cualquier valor distinto deSucceeded(Failed,Canceled,Updating) merece investigación.
3.5. Historial: qué hay y qué no¶
🚨 Idea errónea frecuente:
az deployment group listno muestra nada creado por Terraform. Esos comandos listan despliegues ARM (plantillas ARM o Bicep). Terraform llama directamente a la API de cada recurso y no genera despliegues. Su historial es el estado (terraform.tfstatey sus copias.backup) y, en Azure real, el Registro de actividad.
# Historial local de Terraform
ls -la terraform.tfstate*
terraform state pull | jq '{serial, terraform_version, recursos: (.resources | length)}'
# Azure real (no disponible en Topaz): operaciones de escritura del último día
az monitor activity-log list -g $RG --offset 1d \
--query "[?contains(operationName.value,'write')].{hora:eventTimestamp, op:operationName.localizedValue, estado:status.value}" -o table
3.6. Detalle por tipo de recurso¶
# Red: espacio de direcciones y subredes
az network vnet show -g $RG -n vnet-webapp-produccion \
--query "{espacio:addressSpace.addressPrefixes, subredes:subnets[].{nombre:name, prefijo:addressPrefix}}" -o json
# App Service: plan y aplicación
az appservice plan show -g $RG -n asp-webapp-produccion --query "{sku:sku.name, so:kind}" -o json
az webapp show -g $RG -n app-webapp-produccion-001 --query "{estado:state, host:defaultHostName, plan:appServicePlanId}" -o json
# Storage (si lo desplegaste)
az storage account show -g $RG -n stgwebappprod001 --query "{tier:sku.tier, replicacion:sku.name, kind:kind}" -o json
Compara cada valor con tu .tf: 10.0.0.0/16, 10.0.1.0/24, S1, Standard_LRS. Esta es la verificación de configuración, un nivel por encima de la de existencia.
4. Azure PowerShell (solo Azure real)¶
🔷 Fuera del alcance en Topaz. El módulo
Azde PowerShell puede apuntar a nubes personalizadas conAdd-AzEnvironment, pero requiere declarar a mano cada endpoint del emulador y no está soportado oficialmente. En el curso usamos Azure CLI como cliente de verificación. Esta sección queda como referencia para equipos que trabajen en Windows contra Azure real.
Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force
Connect-AzAccount
Set-AzContext -Subscription "<id-de-suscripcion>"
$rg = Get-AzResourceGroup -Name "rg-webapp-produccion-001"
"{0} {1} {2}" -f $rg.ResourceGroupName, $rg.Location, $rg.ProvisioningState
$rg.Tags
$recursos = Get-AzResource -ResourceGroupName $rg.ResourceGroupName
"Total: $($recursos.Count)"
$recursos | Group-Object ResourceType | Format-Table Name, Count -AutoSize
$recursos | Where-Object { -not $_.Tags.entorno } | Select-Object Name, ResourceType # sin etiqueta obligatoria
Los equivalentes CLI de la sección 3 cubren exactamente lo mismo; elige la herramienta con la que tu equipo esté más cómodo, pero no mezcles ambas en el mismo script.
5. Funcionalidad, monitorización y salud¶
Que un recurso exista no significa que funcione. Este nivel de verificación es donde el emulador y Azure real más se separan, así que empezamos por el mapa:
| Comprobación | Topaz | Azure real |
|---|---|---|
| Plano de control (existe, propiedades, etiquetas) | ✅ Completo | ✅ |
| Plano de datos de Storage (crear contenedor, subir blob) | ⚠️ Sí, si el puerto 8891 y /etc/hosts están configurados |
✅ |
| Petición HTTP a la web app | ❌ No hay runtime detrás del recurso | ✅ curl https://<host>.azurewebsites.net |
| Métricas, alertas, Log Analytics, Advisor | ❌ No implementado | ✅ |
| Análisis de costes | ❌ No aplica | ✅ |
Utiliza esta tabla para saber rápidamente hasta dónde puedes llegar probando en local frente a un entorno cloud real.
5.1. Prueba funcional en Topaz: el plano de datos de Storage¶
Si desplegaste la cuenta de almacenamiento (y tienes el puerto 8891 publicado y stgwebappprod001.storage.topaz.local.dev en /etc/hosts), puedes ir más allá del plano de control:
SA=stgwebappprod001
KEY=$(az storage account keys list -g $RG -n $SA --query "[0].value" -o tsv)
az storage container create -n verificacion --account-name $SA --account-key "$KEY"
echo "hola topaz" > /tmp/prueba.txt
az storage blob upload -c verificacion -f /tmp/prueba.txt -n prueba.txt --account-name $SA --account-key "$KEY"
az storage blob list -c verificacion --account-name $SA --account-key "$KEY" -o table
Si el upload funciona, has verificado la cadena completa: ARM creó la cuenta, el emulador expone su plano de datos y las credenciales son válidas.
5.2. Salud de una aplicación web (Azure real)¶
HOST=$(terraform output -raw web_app_hostname)
curl -s -o /dev/null -w "HTTP %{http_code} %{time_total}s\n" "https://$HOST/"
curl -s "https://$HOST/health" | jq '.status == "ok"' # si la app expone /health
🔷 En Topaz el
default_hostnamese devuelve, pero no hay ningún servidor escuchando: el emulador modela el recurso, no ejecuta tu código. Elcurlfallará por resolución de nombres o conexión rechazada, y es normal.
5.3. Métricas y alertas (Azure real)¶
En una suscripción real, tras 5–10 minutos de actividad, los recursos publican métricas en Azure Monitor. Una alerta típica de CPU sobre una VM:
AG=$(az monitor action-group create -g $RG -n ag-notificaciones --short-name agnot \
--action email admin admin@empresa.com --query id -o tsv)
az monitor metrics alert create -g $RG -n alta-cpu-vm \
--scopes $(az vm show -g $RG -n vm-resiliente --query id -o tsv) \
--condition "avg Percentage CPU > 80" --window-size 5m --evaluation-frequency 1m \
--severity 2 --action $AG --description "CPU media > 80% durante 5 minutos"
Y una consulta KQL en Log Analytics para peticiones fallidas en la última hora:
WS=$(az monitor log-analytics workspace list -g $RG --query "[0].customerId" -o tsv)
az monitor log-analytics query -w $WS -o table --analytics-query "
AppRequests
| where TimeGenerated >= ago(1h)
| summarize Total=count(), Fallidas=countif(Success == false), Duracion=avg(DurationMs) by bin(TimeGenerated, 5m)
| order by TimeGenerated desc"
🔷 En Topaz. Ninguno de estos comandos funciona: el emulador no implementa
Microsoft.InsightsniMicrosoft.OperationalInsights. Si los ejecutas, verás un404o NoRegisteredProviderFound. No es un error tuyo: guarda estos ejemplos para la suscripción real, donde son la base de la verificación a medio plazo (sección 8.1).
6. Verificación automatizada¶
Todo lo anterior se convierte en un script que devuelve 0 si el despliegue es correcto y distinto de cero si no. Ese script es la pieza que después se conecta a cualquier pipeline.
🔷 En Topaz. Un runner de GitHub Actions o Azure DevOps hospedado en la nube no puede alcanzar
topaz.local.dev: el emulador vive en tu máquina. En el laboratorio ejecutas el script en local; los YAML de 6.2 y 6.3 son plantillas para un self-hosted runner instalado en el mismo host que el contenedor, o para Azure real cambiando dos variables.
6.1. Script de verificación reutilizable¶
Guárdalo como ~/recursos/verify-deployment.sh. Los valores esperados van en variables al principio; en Azure real solo cambian la ubicación y el número de recursos.
#!/usr/bin/env bash
# verify-deployment.sh — verificación post-apply (Topaz y Azure real)
set -uo pipefail
RG="${RG:-rg-webapp-produccion-001}"
EXPECTED_LOCATION="${EXPECTED_LOCATION:-eastus}"
EXPECTED_TAGS="entorno=produccion aplicacion=webapp equipo=infraestructura"
EXPECTED_TYPES=("Microsoft.Network/virtualNetworks" "Microsoft.Web/serverfarms" "Microsoft.Web/sites")
TAG_SOURCE_TYPE="Microsoft.Network/virtualNetworks" # en Topaz el RG no devuelve tags; validamos sobre la VNet
errores=0
ok() { echo " ✅ $*"; }
fail() { echo " ❌ $*"; errores=$((errores+1)); }
warn() { echo " ⚠️ $*"; }
echo "=== 1. Contexto ==="
CLOUD=$(az account show --query environmentName -o tsv 2>/dev/null) || { fail "Azure CLI sin sesión"; exit 1; }
ok "Nube: $CLOUD"
echo "=== 2. Grupo de recursos ==="
if ! GROUP=$(az group show -n "$RG" -o json 2>/dev/null); then
fail "El grupo $RG no existe"; exit 1
fi
LOC=$(echo "$GROUP" | jq -r .location)
[ "$LOC" = "$EXPECTED_LOCATION" ] && ok "Ubicación: $LOC" || fail "Ubicación $LOC (esperada $EXPECTED_LOCATION)"
STATE=$(echo "$GROUP" | jq -r .properties.provisioningState)
[ "$STATE" = "Succeeded" ] && ok "Estado: $STATE" || fail "Estado del grupo: $STATE"
echo "=== 3. Recursos esperados ==="
for tipo in "${EXPECTED_TYPES[@]}"; do
n=$(az resource list -g "$RG" --resource-type "$tipo" --query "length([])" -o tsv)
[ "$n" -gt 0 ] && ok "$n × $tipo" || fail "Falta $tipo"
done
echo "=== 4. Ubicación de todos los recursos ==="
fuera=$(az resource list -g "$RG" --query "[?location!='$EXPECTED_LOCATION'].name" -o tsv)
[ -z "$fuera" ] && ok "Todos en $EXPECTED_LOCATION" || fail "Fuera de región: $fuera"
echo "=== 5. Etiquetas (sobre $TAG_SOURCE_TYPE) ==="
TAGS=$(az resource list -g "$RG" --resource-type "$TAG_SOURCE_TYPE" --query "[0].tags" -o json)
for par in $EXPECTED_TAGS; do
k=${par%%=*}; v=${par#*=}
actual=$(echo "$TAGS" | jq -r --arg k "$k" '.[$k] // "null"')
[ "$actual" = "$v" ] && ok "$k=$v" || fail "$k: '$actual' (esperado '$v')"
done
sin_tag=$(az resource list -g "$RG" --query '[?tags.entorno==`null`].name' -o tsv)
[ -z "$sin_tag" ] && ok "Ningún recurso sin etiqueta 'entorno'" || warn "Sin etiqueta 'entorno': $sin_tag"
echo "=== 6. Estado de aprovisionamiento ==="
for id in $(az resource list -g "$RG" --query "[].id" -o tsv); do
read -r nombre estado <<< "$(az resource show --ids "$id" --query "[name, properties.provisioningState]" -o tsv | tr '\n' ' ')"
case "$estado" in
Succeeded) ok "$nombre" ;;
""|None) warn "$nombre: estado no informado (habitual en Topaz)" ;;
*) fail "$nombre: $estado" ;;
esac
done
echo "=== 7. Terraform: sin drift ==="
if [ -f terraform.tfstate ] || [ -d .terraform ]; then
terraform plan -detailed-exitcode -input=false -no-color > /tmp/plan.txt 2>&1; rc=$?
case $rc in
0) ok "plan sin cambios" ;;
2) fail "plan con cambios pendientes (ver /tmp/plan.txt)" ;;
*) fail "plan con error (ver /tmp/plan.txt)" ;;
esac
else
warn "No hay proyecto Terraform en este directorio; se omite"
fi
echo
if [ "$errores" -eq 0 ]; then
echo "🎉 Verificación superada"; exit 0
else
echo "💥 $errores comprobación(es) fallida(s)"; exit 1
fi
chmod +x verify-deployment.sh
./verify-deployment.sh
echo "Código de salida: $?"
# Contra Azure real, sin tocar el script:
RG=rg-webapp-prod-001-weu EXPECTED_LOCATION=westeurope ./verify-deployment.sh
💡 Dos detalles de bash que conviene fijar. Los bucles
forse cierran condone, no confi; es un error muy común al copiar ejemplos y provoca syntax error near unexpected token. Y las etiquetas se leen conjq --argen vez de interpolar la clave en la cadena: así funciona también con claves que llevan guiones, comofecha-creacion.
6.2. GitHub Actions (self-hosted runner o Azure real)¶
# .github/workflows/terraform-verify.yml
name: Verificar despliegue
on:
workflow_run:
workflows: ["Terraform Deploy"]
types: [completed]
jobs:
verify:
# 'self-hosted' para Topaz (runner en el mismo host que el emulador); 'ubuntu-latest' para Azure real
runs-on: self-hosted
if: ${{ github.event.workflow_run.conclusion == 'success' }}
env:
RG: rg-webapp-produccion-001
EXPECTED_LOCATION: eastus
SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt # confianza en el certificado de Topaz (Terraform)
REQUESTS_CA_BUNDLE: /etc/ssl/certs/ca-certificates.crt # ídem para Azure CLI
steps:
- uses: actions/checkout@v4
# Azure real: sustituye este paso por azure/login@v2 con secrets.AZURE_CREDENTIALS
- name: Sesión con Topaz
run: |
az cloud set --name Topaz
az account show > /dev/null || { echo "::error::Sesión de az caducada; ejecuta az login en el runner"; exit 1; }
- uses: hashicorp/setup-terraform@v3
- run: terraform init -input=false
- name: Verificar despliegue
run: ./verify-deployment.sh
6.3. Azure DevOps Pipelines¶
# azure-pipelines.yml
trigger:
- main
variables:
RG: rg-webapp-produccion-001
EXPECTED_LOCATION: eastus
stages:
- stage: Verify
displayName: Verificar despliegue
jobs:
- job: VerifyDeployment
pool:
name: LabTopaz # pool de agentes self-hosted; para Azure real: vmImage: ubuntu-latest
steps:
# Azure real: usa la tarea AzureCLI@2 con azureSubscription en lugar de este script
- script: |
az cloud set --name Topaz
az account show > /dev/null || { echo "##[error]Sesión de az caducada"; exit 1; }
displayName: Sesión con Topaz
- script: terraform init -input=false
displayName: terraform init
- script: ./verify-deployment.sh
displayName: Verificar despliegue
env:
SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt
REQUESTS_CA_BUNDLE: /etc/ssl/certs/ca-certificates.crt
La lógica vive en el script, no en el YAML: así la pruebas en tu terminal, la versionas con el resto del proyecto y la misma copia sirve para GitHub, Azure DevOps o GitLab.
7. Solución de problemas comunes¶
7.1. Discrepancias entre Terraform y la CLI¶
🚨 Síntoma:
terraform state listmuestra recursos queaz resource listno devuelve
Causa: el recurso se borró fuera de Terraform (con az o recreando el contenedor del emulador, que arranca vacío).
Solución: terraform plan detectará la ausencia y propondrá recrearlos. Si recreaste el contenedor, todo el estado es obsoleto: elimina terraform.tfstate* y aplica de nuevo, o ejecuta terraform state rm recurso a recurso.
🚨 Síntoma:
az resource listmuestra recursos que no están en el estado
Causa: se crearon con az, desde otro directorio de Terraform, o el estado se perdió.
Solución: impórtalos (terraform import o bloque import {}) si deben gestionarse desde este proyecto; bórralos con az si eran restos de pruebas. Nunca los dejes en tierra de nadie.
🚨 Síntoma:
plan -detailed-exitcodedevuelve 2 justo después de unapplylimpio
Causa: el servidor normaliza o descarta algún atributo (en Topaz, las etiquetas del grupo de recursos; en Azure real, mayúsculas en ubicaciones o valores por defecto que el provider no conoce).
Solución: lee el plan: si el cambio es siempre el mismo y no importa, lifecycle { ignore_changes = [...] }. Si es un atributo que sí importa, es un bug del provider o del emulador que conviene reportar.
7.2. Azure CLI¶
🚨 Síntoma:
az resource listdevuelve lista vacía oResourceGroupNotFound
- Confirma la nube:
az account show --query environmentNamedebe decirTopaz. Si diceAzureCloud, estás consultando Azure real:az cloud set --name Topaz. - Confirma el nombre exacto:
az group list -o table. - Comprueba que el contenedor está en marcha:
docker ps --filter name=azure-environment. - Para ver la petición HTTP real, añade
--debug.
🚨 Síntoma: la consulta
--querydevuelvenullo falla con invalid jmespath
- Los literales JMESPath van entre acentos graves:
[?tags.entorno==null]. En bash, encierra toda la consulta entre comillas simples para que no se interpreten. az resource listno devuelveproperties; usaaz resource show --idso el comando específico del tipo (az network vnet show,az webapp show).- Comprueba el nombre exacto del campo con
-o jsonsin filtro antes de escribir la consulta.
🚨 Síntoma:
az deployment group listno muestra el despliegue de Terraform
Causa: no es un fallo. Terraform no crea despliegues ARM; llama a la API de cada recurso. Ese comando solo lista plantillas ARM o Bicep.
Solución: el historial de Terraform es su estado (terraform state pull, ficheros .backup) y, en Azure real, el Registro de actividad.
7.3. Autenticación y conectividad¶
| Error | Causa | Solución |
|---|---|---|
401 / InvalidAuthenticationToken |
Sesión de az caducada (el emulador emite tokens de corta duración) |
az login --use-device-code |
SSLError / CERTIFICATE_VERIFY_FAILED |
La CLI (Python) no confía en el certificado de Topaz | export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt |
x509: certificate signed by unknown authority |
Terraform (Go) no confía en el certificado | export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt |
Connection refused / i/o timeout |
Contenedor parado o /etc/hosts sin la entrada |
docker start azure-environment; revisa /etc/hosts |
AuthorizationFailed |
Solo en Azure real: faltan permisos RBAC (mínimo Lector para verificar) | az role assignment list --assignee <quien> --all -o table |
7.4. Verificación funcional y métricas¶
🚨 Síntoma:
az storage container createse queda colgado o falla por nombre no resuelto
Causa: el plano de datos de Storage vive en <cuenta>.storage.topaz.local.dev:8891 y ese nombre no está en /etc/hosts o el puerto no está publicado.
Solución: añade la entrada apuntando a 127.0.0.1 y recrea el contenedor con -p 8891:8891 (recuerda reinstalar el certificado después).
🚨 Síntoma:
curla la web app falla;az monitordevuelve 404
Causa: límites del emulador: no ejecuta aplicaciones ni implementa Azure Monitor. Solución: ninguna en Topaz. En Azure real, espera 5–10 minutos tras el despliegue para que aparezcan métricas, genera tráfico y amplía el intervalo de consulta si sigue vacío.
8. Buenas prácticas de verificación¶
8.1. Estrategia por capas¶
| Nivel | Método | Cuándo | Topaz |
|---|---|---|---|
| Inmediato | terraform state list, az resource list -o table, plan -detailed-exitcode |
Tras cada apply |
✅ |
| Corto plazo | Consultas de configuración por recurso, script verify-deployment.sh, prueba de plano de datos |
Antes de dar el despliegue por bueno | ✅ (Storage con 8891) |
| Medio plazo | Métricas, logs, health checks HTTP | Primer día de operación | ❌ Solo Azure real |
| Largo plazo | Auditoría de etiquetas y nombrado, Azure Policy, revisión de costes | Semanal / mensual | ⚠️ Solo etiquetas y nombrado |
Esta tabla resume cómo escalar tus validaciones desde la ejecución local hasta la operación continua.
8.2. Lista de verificación post-despliegue¶
✅ Existencia y estado
- terraform state list contiene todos los recursos del .tf.
- az resource list los devuelve todos (menos grupo y recursos hijos).
- El grupo está en Succeeded; ningún recurso en Failed.
- terraform plan -detailed-exitcode devuelve 0.
ℹ️ Configuración y etiquetado
- Ubicación de todos los recursos = la declarada.
- Etiquetas entorno, aplicacion, equipo, costo presentes en cada recurso que las admite.
- Valores críticos coinciden: espacio de direcciones, SKU del plan, replicación de Storage.
- Relaciones correctas: la subred cuelga de la VNet, la web app apunta al plan.
⚠️ Funcionalidad (lo que Topaz permite)
- Storage: crear un contenedor y subir un blob funciona.
- Los outputs devuelven valores no nulos (terraform output -json | jq).
- En Azure real, además: la web app responde 200, latencia dentro de límites, sin excepciones en los logs.
📝 Gobernanza
- No hay recursos fuera del estado de Terraform ("huérfanos").
- Nombres conforme a la convención <tipo>-<app>-<entorno>-<instancia>.
- En Azure real: sin violaciones de Azure Policy y coste acorde al SKU elegido.
8.3. Principios¶
- Dos fuentes independientes. Terraform y la CLI hablan con la misma API pero por caminos distintos; si coinciden, el despliegue es real. Nunca valides solo con la herramienta que hizo el cambio.
- Un script, no una lista de comandos. Lo que no está en un script con código de salida no se ejecuta la segunda vez.
- Verifica la configuración, no solo la existencia. Una VNet con el espacio de direcciones equivocado "existe" perfectamente.
- Trata el
planlimpio como prueba de aceptación. Es la afirmación más fuerte que Terraform puede hacer: código, estado y realidad coinciden. - Conoce los límites de tu entorno. En Topaz, una verificación funcional que falla no siempre es un error; en Azure real, siempre lo es.
9. Recursos adicionales¶
Terraform¶
Azure CLI¶
- Consultas JMESPath con
--query - Referencia de
az resource - Referencia de
az monitor(Azure real) - Tutorial de JMESPath
- Manual de jq