Problema

En entornos de producción es habitual depender de servicios gestionados (Blob storage, bases de datos, colas, etc.) que se encuentran en una única región o proveedor. Cuando uno de esos recursos sufre una interrupción —por ejemplo, una caída del servicio de Azure Blob en “East US”— la aplicación deja de responder y el equipo de guardia puede no estar disponible. La pregunta central es: ¿cómo reaccionar de forma automática, sin introducir deriva de configuración y sin depender de procesos manuales?

El desafío no es solo crear el recurso de reemplazo; es mantener la coherencia con el repositorio de IaC, evitar dos fuentes de verdad y, al mismo tiempo, permitir ajustes dinámicos (cambio de SKU, escalado) basados en métricas reales.

Causa

Las causas más frecuentes de este tipo de incidentes son:

  1. Fallos de zona o región – Azure, AWS y GCP publican incidentes de zona que pueden afectar a todos los recursos de esa zona.
  2. Problemas de plataforma – Un bug en el servicio gestionado o una actualización que rompe la disponibilidad.
  3. Sobrecarga inesperada – Picos de tráfico que saturan el SKU provisionado, provocando throttling o time‑outs.
  4. Errores de configuración – Políticas de red o de IAM que impiden que una réplica en otra región sea accesible.

En la práctica, la mayoría de los equipos tienen monitorización (Azure Monitor, CloudWatch) pero no un mecanismo que convierta una alerta en una corrección de infraestructura sin intervención humana.

Solución

Una estrategia reutilizable combina tres capas:

  1. Detección basada en alertas estructuradas
    • Usa métricas y logs (por ejemplo, ResourceHealth o consultas KQL) para generar alertas que incluyan un payload estructurado (tipo, región, recurso).
  2. Motor de decisión declarativo
    • Define reglas condition → action en un archivo YAML dentro del mismo repositorio de Terraform. Cada regla especifica:
      • Condición: expresión sobre la alerta (p. ej., resource.type == "azurerm_storage_account" y region == "eastus" y status == "unhealthy").
      • Acción: cambios a aplicar (cambiar location, actualizar sku, crear recurso de respaldo).
  3. Ejecutor automatizado
    • Un GitHub Action programado (cron) que:
      1. Consulta la API de monitorización.
      2. Evalúa las reglas contra las alertas recibidas.
      3. Modifica los archivos .tf correspondientes (añade un nuevo bloque o actualiza variables).
      4. Abre un Pull Request automáticamente; opcionalmente, si la regla está marcada como auto‑apply, ejecuta terraform apply directamente en un workspace aislado.

Paso a paso

1. Definir reglas en infra_rules.yaml

rules:
  - name: "Failover storage account"
    condition:
      resource_type: "azurerm_storage_account"
      region: "eastus"
      health: "unhealthy"
    action:
      file: "modules/storage/main.tf"
      replace:
        location: "westus2"
        sku: "Standard_LRS"
      auto_apply: false
  - name: "Scale up App Service on traffic spike"
    condition:
      resource_type: "azurerm_app_service"
      metric: "HttpRequests"
      threshold: 5000
      period_minutes: 5
      direction: "above"
    action:
      file: "modules/appservice/variables.tf"
      replace:
        sku_name: "P2v2"
      auto_apply: true

Cada regla es autocontenida; el motor solo necesita saber qué archivo tocar y qué valores reemplazar.

2. GitHub Action que ejecuta el motor

name: Infra auto‑recovery
on:
  schedule:
    - cron: '*/5 * * * *'   # cada 5 minutos
jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install jq and yq
        run: sudo apt-get update && sudo apt-get install -y jq yq
      - name: Fetch alerts from Azure Monitor
        env:
          AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
          AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
          AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
          AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
        run: |
          az login --service-principal -u $AZURE_CLIENT_ID -p $AZURE_CLIENT_SECRET --tenant $AZURE_TENANT_ID
          az monitor metrics alert list --resource-group my-rg > alerts.json
      - name: Evaluate rules
        run: |
          python3 scripts/evaluate_rules.py infra_rules.yaml alerts.json
      - name: Create PR if changes
        if: steps.evaluate.outputs.changes == 'true'
        uses: peter-evans/create-pull-request@v5
        with:
          commit-message: "Auto‑recovery: apply rule ${{ steps.evaluate.outputs.rule_name }}"
          branch: auto-recovery/${{ steps.evaluate.outputs.rule_name }}
          title: "[Auto‑Recovery] ${{ steps.evaluate.outputs.rule_name }}"
          body: "Cambios generados automáticamente por la regla de recuperación."

El script evaluate_rules.py carga las alertas, compara con las condiciones y, cuando coincide, usa yq para editar el archivo .tf. Si auto_apply es true, el mismo workflow ejecuta:

terraform init
terraform plan -out=tfplan
terraform apply -auto-approve tfplan

En entornos críticos se recomienda limitar auto_apply a cambios de escalado o a recursos sin datos persistentes.

3. Mantener la fuente única de verdad

Todas las modificaciones se hacen sobre los archivos versionados. El PR permite revisión humana; si se aprueba, el pipeline de CI/CD habitual ejecuta terraform apply en los entornos de producción. De esta forma no hay “drift” porque el estado deseado siempre proviene del repositorio.

Cuándo aplicar esta solución

Utilízala cuando:

  • Tienes recursos sin datos críticos (almacenamiento estático, colas, caches) que pueden ser recreados rápidamente en otra región.
  • Los costos de downtime superan el riesgo de cambios automáticos (SLA estricto, transacciones en tiempo real).
  • Tu pipeline IaC ya está integrado con GitHub Actions o Azure Pipelines; de lo contrario, la inversión en la automatización será mayor que el beneficio.

No es adecuada si:

  • El recurso contiene datos que requieren replicación consistente (bases de datos con transacciones).
  • La política de tu organización prohíbe despliegues sin revisión humana.
  • El número de reglas supera la capacidad de mantenimiento manual; en ese caso, una solución SaaS especializada puede ser más rentable.

Código

# Ejemplo de edición de Terraform con yq (instalado en el job)
yq eval '.resource.azurerm_storage_account.myaccount.location = "westus2"' -i modules/storage/main.tf
yq eval '.resource.azurerm_storage_account.myaccount.sku.name = "Standard_LRS"' -i modules/storage/main.tf
git add modules/storage/main.tf
git commit -m "Auto-recovery: switch storage to westus2"

Verificación

  1. Simular una alerta: crea una alerta falsa con az monitor metrics alert create que marque el recurso como unhealthy.
  2. Ejecutar el workflow: dispara manualmente el GitHub Action (workflow_dispatch).
  3. Revisar el PR: verifica que el archivo .tf contiene los cambios esperados.
  4. Aplicar en entorno de pruebas: ejecuta terraform apply en un workspace de staging y confirma que el recurso nuevo está operativo en la región indicada.
  5. Monitorizar: una vez aplicado, revisa que la métrica de salud vuelve a healthy y que no aparecen nuevos eventos de drift.

Notas adicionales

  • Idempotencia: las reglas deben ser idempotentes; si el recurso ya está en la región objetivo, el motor no debe volver a generar cambios.
  • Seguridad de secretos: usa los secretos de GitHub para credenciales de Azure; nunca los almacenes en el repositorio.
  • Limitaciones de API: la frecuencia de polling está sujeta a cuotas; ajusta el cron a un intervalo que no supere los límites de tu suscripción.
  • Rollback: incluye una regla espejo que revierta los cambios cuando la métrica vuelva a la normalidad (auto_apply: true en la regla de recuperación inversa).
  • Auditoría: habilita terraform plan en modo JSON y almacena los resultados en un bucket de logs para cumplir con requisitos de compliance.

Con este enfoque se consigue una respuesta automática a fallas de infraestructura, se preserva la única fuente de verdad (el repositorio IaC) y se mantiene la posibilidad de revisión humana cuando el impacto lo justifica. La clave está en tratar la detección, la decisión y la ejecución como componentes desacoplados pero orquestados mediante la misma cadena de CI/CD que ya utilizas.