TL;DR
- Azure proporciona deployment what-if para validación pre-despliegue — úsalo en CI antes de cada apply
- Azurite emula Storage, Queues y Tables localmente — más rápido que Azure real para tests de storage
- El error #1: saltarse testing de Azure Policy hasta que el despliegue falla en producción
Ideal para: Equipos desplegando en Azure con Terraform, Bicep o plantillas ARM Omitir si: Estás solo en AWS/GCP o usas Azure PaaS sin código de infraestructura Tiempo de lectura: 10 minutos
Tu plan de Terraform se ve limpio. El despliegue a Azure comienza. Veinte minutos después, falla: “Azure Policy evaluation failed.” Pasas una hora averiguando qué policy bloqueó el despliegue, luego otra hora refactorizando para cumplir. Mientras tanto, el equipo está bloqueado.
El testing de infraestructura Azure tiene desafíos únicos. La aplicación de Azure Policy ocurre en tiempo de despliegue. Las convenciones de nombres de recursos varían por región. La consistencia eventual en la propagación de Azure AD causa fallos intermitentes. Entender estos patrones marca la diferencia entre CI fluido y constante apagar incendios.
El Problema Real
Azure introduce desafíos de testing diferentes a AWS:
Azure Policy: Las suscripciones Azure empresariales tienen policies que bloquean despliegues no conformes. No sabes sobre violaciones hasta que terraform apply o az deployment falla.
Registro de Resource Provider: El primer uso de un servicio en una suscripción requiere registro del provider. Los tests fallan inesperadamente en suscripciones limpias.
Retrasos de propagación Azure AD: Service principals, managed identities y role assignments toman tiempo en propagarse. Tests que funcionan localmente fallan en CI.
Restricciones de nombres: Los nombres de recursos Azure tienen reglas complejas — storage accounts deben ser globalmente únicos, 3-24 caracteres alfanuméricos en minúscula. Key Vaults tienen reglas diferentes. VMs tienen reglas diferentes otra vez.
Deployment What-If
La operación what-if de Azure valida despliegues antes de ejecutarlos:
# ARM/Bicep what-if
az deployment group what-if \
--resource-group myResourceGroup \
--template-file main.bicep \
--parameters @params.json
# Despliegue a nivel de suscripción
az deployment sub what-if \
--location eastus \
--template-file main.bicep
Para Terraform, combina plan con validación específica de Azure:
# Generar plan
terraform plan -out=tfplan
# Convertir a JSON para análisis
terraform show -json tfplan > tfplan.json
# Verificar cumplimiento de Azure Policy (requiere Azure CLI)
az policy state trigger-scan --resource-group myResourceGroup
# O usar Checkov con reglas Azure
checkov -f tfplan.json --framework terraform_plan
Terratest para Azure
Terratest tiene módulos específicos para Azure:
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/azure"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestAzureStorageAccount(t *testing.T) {
t.Parallel()
subscriptionID := azure.GetSubscriptionID()
uniqueID := random.UniqueId()
terraformOptions := &terraform.Options{
TerraformDir: "../modules/storage-account",
Vars: map[string]interface{}{
"resource_group_name": "rg-test-" + uniqueID,
"storage_account_name": "sttest" + uniqueID,
"location": "eastus",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
// Obtener outputs
resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name")
storageAccountName := terraform.Output(t, terraformOptions, "storage_account_name")
// Verificar que storage account existe y tiene propiedades correctas
exists := azure.StorageAccountExists(t, storageAccountName, resourceGroupName, subscriptionID)
assert.True(t, exists)
// Verificar propiedades de storage account
storageAccount := azure.GetStorageAccount(t, storageAccountName, resourceGroupName, subscriptionID)
assert.Equal(t, "Standard_LRS", string(storageAccount.Sku.Name))
assert.True(t, *storageAccount.EnableHTTPSTrafficOnly)
}
func TestAzureVirtualNetwork(t *testing.T) {
t.Parallel()
subscriptionID := azure.GetSubscriptionID()
uniqueID := random.UniqueId()
terraformOptions := &terraform.Options{
TerraformDir: "../modules/virtual-network",
Vars: map[string]interface{}{
"resource_group_name": "rg-test-" + uniqueID,
"vnet_name": "vnet-test-" + uniqueID,
"address_space": []string{"10.0.0.0/16"},
"location": "eastus",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vnetName := terraform.Output(t, terraformOptions, "vnet_name")
resourceGroupName := terraform.Output(t, terraformOptions, "resource_group_name")
// Verificar que VNet existe
exists := azure.VirtualNetworkExists(t, vnetName, resourceGroupName, subscriptionID)
assert.True(t, exists)
// Verificar subnets
subnets := azure.GetVirtualNetworkSubnets(t, vnetName, resourceGroupName, subscriptionID)
assert.GreaterOrEqual(t, len(subnets), 1)
}
Azurite para Testing Local de Storage
Azurite emula servicios de Azure Storage localmente:
# Instalar via npm
npm install -g azurite
# Iniciar todos los servicios
azurite --silent --location ./azurite-data --debug ./azurite-debug.log
# O via Docker
docker run -d \
-p 10000:10000 \
-p 10001:10001 \
-p 10002:10002 \
-v azurite-data:/data \
mcr.microsoft.com/azure-storage/azurite
Configurar Terraform para usar Azurite:
provider "azurerm" {
features {}
# Para Azurite, sobrescribir endpoints de storage
# Nota: AzureRM completo no soporta Azurite directamente
# Usa este patrón para testing de código de app, no Terraform completo
}
# Para testing de aplicación con storage
resource "null_resource" "test_storage" {
provisioner "local-exec" {
command = <<-EOT
export AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1"
python test_storage_operations.py
EOT
}
}
Tests en Python con Azurite:
import os
from azure.storage.blob import BlobServiceClient
def test_blob_operations():
# String de conexión Azurite
connection_string = os.environ.get(
"AZURE_STORAGE_CONNECTION_STRING",
"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1"
)
blob_service = BlobServiceClient.from_connection_string(connection_string)
# Crear container
container_client = blob_service.create_container("test-container")
# Subir blob
blob_client = container_client.get_blob_client("test-blob.txt")
blob_client.upload_blob("Hello, Azure!", overwrite=True)
# Descargar y verificar
downloaded = blob_client.download_blob().readall()
assert downloaded == b"Hello, Azure!"
# Limpieza
container_client.delete_container()
Testing de Bicep con What-If
Para despliegues Bicep, integra what-if en CI:
# azure-pipelines.yml
trigger:
paths:
include:
- infra/**
stages:
- stage: Validate
jobs:
- job: BicepValidation
pool:
vmImage: ubuntu-latest
steps:
- task: AzureCLI@2
displayName: 'Bicep Lint'
inputs:
azureSubscription: 'MyServiceConnection'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az bicep build --file infra/main.bicep --stdout > /dev/null
- task: AzureCLI@2
displayName: 'What-If Analysis'
inputs:
azureSubscription: 'MyServiceConnection'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az deployment group what-if \
--resource-group $(ResourceGroup) \
--template-file infra/main.bicep \
--parameters infra/params.$(Environment).json
- stage: Deploy
dependsOn: Validate
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployInfra
environment: production
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
inputs:
azureSubscription: 'MyServiceConnection'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az deployment group create \
--resource-group $(ResourceGroup) \
--template-file infra/main.bicep \
--parameters infra/params.$(Environment).json
Testing de Azure Policy
Prueba cumplimiento de Azure Policy antes del despliegue:
func TestAzurePolicyCompliance(t *testing.T) {
t.Parallel()
subscriptionID := azure.GetSubscriptionID()
terraformOptions := &terraform.Options{
TerraformDir: "../modules/storage-account",
Vars: map[string]interface{}{
"resource_group_name": "rg-policy-test",
"storage_account_name": "stpolicytest" + random.UniqueId(),
// Intencionalmente no conforme para testing
"enable_https_only": false,
},
}
// No auto-destruir - queremos verificar estado de policy
terraform.Init(t, terraformOptions)
// Plan debería tener éxito
terraform.Plan(t, terraformOptions)
// Pero apply debería fallar por policy
_, err := terraform.ApplyE(t, terraformOptions)
// Afirmar que el error es relacionado con policy
assert.Error(t, err)
assert.Contains(t, err.Error(), "PolicyViolation")
// Limpiar el despliegue fallido
terraform.Destroy(t, terraformOptions)
}
Consultar cumplimiento de policy programáticamente:
# Disparar evaluación de policy
az policy state trigger-scan --resource-group myResourceGroup
# Verificar estado de cumplimiento
az policy state list \
--resource-group myResourceGroup \
--filter "complianceState eq 'NonCompliant'" \
--query "[].{Resource:resourceId, Policy:policyDefinitionName}"
Integración CI/CD
GitHub Actions para infraestructura Azure:
name: Azure Infrastructure
on:
pull_request:
paths:
- 'terraform/**'
permissions:
id-token: write
contents: read
pull-requests: write
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Azure Login (OIDC)
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: terraform
- name: Terraform Validate
run: terraform validate
working-directory: terraform
- name: Terraform Plan
id: plan
run: terraform plan -out=tfplan -no-color
working-directory: terraform
continue-on-error: true
- name: Run Checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: terraform/
framework: terraform
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
const output = `#### Terraform Plan 📖
\`\`\`
${{ steps.plan.outputs.stdout }}
\`\`\`
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
terratest:
runs-on: ubuntu-latest
needs: validate
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Run Terratest
run: go test -v -timeout 30m ./tests/...
env:
ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
ARM_USE_OIDC: true
Enfoques Asistidos por IA
Azure tiene reglas de nombres complejas e interacciones de policy. Las herramientas de IA ayudan a navegar esto.
Lo que la IA hace bien:
- Generar nombres de recursos conformes para convenciones de nombres Azure
- Traducir definiciones de Azure Policy en aserciones de test
- Crear código Terratest desde especificaciones de recursos Azure
- Explicar mensajes de error específicos de Azure y soluciones
Lo que todavía necesita humanos:
- Entender requisitos organizacionales de Azure Policy
- Diseñar arquitectura de tests para Azure Landing Zones complejas
- Decidir qué tests necesitan Azure real vs emulación local
- Depurar problemas de timing de propagación Azure AD
Prompt útil:
Tengo un módulo Terraform de Azure que crea:
- Resource Group
- Storage Account con blob containers
- Key Vault con access policies
- Azure Functions con managed identity
Genera:
1. Código Terratest para validar todos los recursos
2. Verificaciones de Azure Policy que debería incluir
3. Errores comunes específicos de Azure a probar
4. Tests de Azurite para operaciones de storage
Cuándo Esto Falla
El testing de infraestructura Azure tiene limitaciones:
Timing de Azure AD: Role assignments y propagación de managed identity pueden tomar minutos. Los tests necesitan lógica de retry y delays.
Diferencias regionales: Algunos servicios no están disponibles en todas las regiones. Tests que funcionan en eastus fallan en otras regiones.
Recursos a nivel de suscripción: Management groups, suscripciones y algunas policies requieren permisos elevados que service principals de CI pueden no tener.
Costo de limpieza: Terraform destroys fallidos dejan recursos huérfanos. Azure no tiene las mismas herramientas de limpieza que AWS.
Considera enfoques complementarios:
- Testing multi-cloud para patrones portables
- Azure DevTest Labs para entornos de test aislados
- Policy as Code para validación pre-despliegue
Framework de Decisión
Usa Azurite cuando:
- Probando código de aplicación que usa Azure Storage
- La velocidad es crítica (Azurite es instantáneo)
- Se requiere aislamiento de red
Usa what-if cuando:
- Validando despliegues Bicep/ARM
- Verificando cumplimiento de Azure Policy
- Revisión de cambios pre-despliegue
Usa Terratest con Azure real cuando:
- Probando módulos de infraestructura completos
- Validando integraciones cross-resource
- Validación final antes de producción
Midiendo el Éxito
| Métrica | Antes | Después | Cómo Rastrear |
|---|---|---|---|
| Fallos de Azure Policy en CI | Frecuentes | 0 | Logs de despliegue |
| Tiempo de ejecución de tests | 20+ min | <10 min | Métricas de CI |
| Recursos de test huérfanos | Desconocido | 0 | Azure Cost Management |
| Tasa de éxito primer despliegue | 60% | 95%+ | Historial de despliegues |
Señales de advertencia de que no está funcionando:
- what-if pasa pero deploy falla
- Tests inestables por timing de Azure AD
- Lista creciente de tareas de limpieza manual
- Equipos evitando CI para despliegues “rápidos”
Qué Sigue
Comienza con validación, luego expande a integración:
- Agrega
az deployment what-ifa cada PR - Implementa Checkov para escaneo de policy Azure
- Configura Azurite para testing local de storage
- Agrega Terratest para módulos de infraestructura críticos
- Configura automatización de limpieza para tests fallidos
- Rastrea métricas de test específicas de Azure
El objetivo es capturar problemas específicos de Azure antes del despliegue, no después.
Artículos relacionados:
- Testing de Infraestructura AWS con LocalStack
- Testing de Infraestructura Multi-Cloud
- Policy as Code Testing: OPA vs Sentinel
- Estrategias de Testing y Validación de Terraform
Recursos externos:
Recursos Oficiales
See Also
- Testing de Configuración de Red: Batfish, Terraform y Validación de VPC para Infraestructura Cloud - Domina el testing de configuración de red con Batfish para…
- Testing de Escalabilidad de Infraestructura: Validando Auto-Scaling con K6, Locust y Terraform - Domina el testing de escalabilidad de infraestructura con K6,…
- Testing de Security Groups: Validación de AWS Security Groups, Azure NSGs y Reglas de Firewall de GCP - Domina el testing de security groups en AWS, Azure y GCP con…
- Pruebas de Estimación de Costos para Infrastructure as Code: Guía Completa - Domina las pruebas de estimación de costos para IaC con Infracost,…
