Skip to content

Blog

Azure AI Foundry: tu hub unificado para IA generativa en Azure

Resumen

Azure AI Foundry (antes Azure AI Studio) es la plataforma unificada de Microsoft para desarrollar, desplegar y gestionar aplicaciones de IA generativa. Con acceso a 1900+ modelos (GPT-4o, o1, DeepSeek, Llama...), un entorno colaborativo tipo MLOps, y herramientas para RAG, evaluación y monitorización, todo en un portal integrado.

Si trabajas con Azure OpenAI, LangChain, o quieres construir copilots/agents, este es tu punto de partida.

Terraform: Uso de Variables y Secrets de GitHub

Resumen

Cómo usar variables y secrets de GitHub en despliegues con Terraform para Azure. Ejemplo práctico y buenas prácticas para admins y DevOps que quieren CI/CD seguro y reproducible.

¿Qué problema resuelve?

Permite gestionar credenciales y parámetros sensibles (como client secrets, IDs, claves) de forma segura en pipelines de GitHub Actions, evitando exponerlos en el código fuente.

¿Qué es y cómo funciona?

  • Secrets: Valores cifrados almacenados en GitHub (por repo o por entorno). Ej: credenciales Azure, claves API.
  • Variables de configuración: Valores no sensibles, útiles para parámetros de entorno, nombres, ubicaciones, flags, etc. Se gestionan en Settings → Secrets and variables → Actions → Variables.
  • Los workflows de GitHub Actions pueden acceder a estos valores como variables de entorno.
  • Terraform puede consumirlos usando la sintaxis ${{ secrets.SECRET_NAME }} para secrets y ${{ vars.VAR_NAME }} para variables de configuración en el workflow.

Ejemplo sencillo: despliegue de Azure Resource Group con variables y secrets

1. Añadir secrets en GitHub

  • Ve a tu repo → Settings → Secrets and variables → Actions
  • Añade los secrets:
  • AZURE_CLIENT_ID
  • AZURE_TENANT_ID
  • AZURE_SUBSCRIPTION_ID

2. Añadir variables de configuración (no sensibles)

  • Ejemplo: RG_NAME, LOCATION

3. Workflow de GitHub Actions (.github/workflows/terraform.yml)

name: 'Terraform Azure'
on:
  push:
    branches: [ main ]

---
  deploy:
    runs-on: ubuntu-latest
    env:
      # Recomendado: Federated Identity (OIDC) - no uses client secret
      ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
      ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
      TF_VAR_rg_name: ${{ vars.RG_NAME }}
      TF_VAR_location: ${{ vars.LOCATION }}
    steps:
      - uses: actions/checkout@v4
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
      - name: Terraform Init
        run: terraform init
      - name: Terraform Plan
        run: terraform plan
      - name: Terraform Apply
        run: terraform apply -auto-approve

En este ejemplo, RG_NAME y LOCATION son variables de configuración definidas en GitHub (no secrets) y se pasan automáticamente como variables de entrada a Terraform usando el prefijo TF_VAR_.

4. Código Terraform (main.tf)

provider "azurerm" {
  features {}
}

variable "rg_name" {
  description = "Nombre del resource group"
  type        = string
}

variable "location" {
  description = "Ubicación Azure"
  type        = string
}

resource "azurerm_resource_group" "ejemplo" {
  name     = var.rg_name
  location = var.location
}

Objetos complejos y versiones recientes de Terraform

Desde Terraform 1.3 en adelante, puedes pasar mapas y listas directamente como variables de entorno usando el prefijo TF_VAR_, sin necesidad de jsonencode/jsondecode. Terraform los interpreta automáticamente si el valor es un JSON válido.

Ejemplo directo (Terraform >= 1.3)

env:
  TF_VAR_tags: '{"env":"prod","owner":"devops"}'
variable "tags" {
  type = map(string)
}

resource "azurerm_resource_group" "ejemplo" {
  name     = var.rg_name
  location = var.location
  tags     = var.tags
}
No necesitas usar jsondecode() si la variable ya es del tipo adecuado y el valor es JSON válido.

¿Y si necesitas un objeto complejo y usas versiones antiguas?

Si necesitas pasar un objeto complejo (por ejemplo, un mapa o lista de objetos) y tu versión de Terraform es anterior a 1.3, lo más habitual es usar un secret o variable en formato JSON y parsearlo en Terraform.

env:
  TF_VAR_tags_json: ${{ secrets.TAGS_JSON }}
variable "tags_json" {
  description = "Tags en formato JSON"
  type        = string
}

locals {
  tags = jsondecode(var.tags_json)
}

resource "azurerm_resource_group" "ejemplo" {
  name     = var.rg_name
  location = var.location
  tags     = local.tags
}
Así puedes pasar cualquier estructura compleja (mapas, listas, objetos anidados) como string JSON y usar jsondecode() en Terraform para convertirlo a un tipo nativo. draft: false date: 2025-10-23 authors:

  • rfernandezdo categories:

  • DevOps

  • Terraform
  • GitHub Actions tags:

  • Terraform

  • GitHub Secrets
  • Azure

Buenas prácticas

  • Usa federated identity (OIDC) siempre que sea posible, evita client secrets.
  • Nunca subas secrets al repo ni los hardcodees en el código.
  • Usa GitHub Environments para separar prod/dev y limitar acceso a secrets.
  • Usa variables para parámetros no sensibles (ej: nombres, ubicaciones).
  • Valida siempre con terraform validate antes de aplicar.
  • Rota los secrets periódicamente si usas client secrets legacy.

Referencias

Azure Container Apps en 2025: La Evolución del Serverless para Contenedores

Resumen

Azure Container Apps ha evolucionado significativamente desde su lanzamiento en 2022. Este artículo actualizado explora las capacidades actuales del servicio, las novedades incorporadas en 2024-2025, y cómo se posiciona como la solución ideal para ejecutar aplicaciones nativas de la nube sin la complejidad de gestionar Kubernetes directamente.

TL;DR

  • Azure Container Apps es un servicio serverless que ejecuta contenedores sin gestionar infraestructura, construido sobre Kubernetes, KEDA, Dapr y Envoy
  • Novedades 2024-2025: GPUs serverless, Dynamic Sessions, Azure Functions integradas, workload profiles dedicados con GPUs A100/T4
  • Casos de uso: microservicios, APIs, procesamiento de eventos, cargas AI/ML, aplicaciones con IA generativa
  • Escalado automático (incluso a cero), revisiones, traffic splitting, integración nativa con servicios Azure

El Problema que Resuelve

«Necesito ejecutar aplicaciones Cloud Native en contenedores, pero no quiero la complejidad operativa de Kubernetes, OpenShift, etc.»

Esta es una necesidad recurrente en equipos de desarrollo que quieren aprovechar las ventajas de los contenedores y arquitecturas de microservicios sin dedicar recursos a:

  • Configurar y mantener clusters de Kubernetes
  • Gestionar nodos, redes, almacenamiento
  • Administrar actualizaciones y parches del orquestador
  • Implementar observabilidad y monitorización avanzada desde cero

Azure Container Apps: La Solución Serverless

Azure Container Apps es un servicio gestionado serverless que permite ejecutar contenedores sin preocuparse por la infraestructura subyacente. Lanzado inicialmente en 2022 y con mejoras continuas, se ha convertido en una opción madura para desplegar aplicaciones modernas.

Arquitectura Subyacente

Aunque no gestionas directamente Kubernetes, bajo el telón Azure Container Apps funciona sobre:

  • Azure Kubernetes Service (AKS): proporciona el orquestador
  • KEDA (Kubernetes Event Driven Autoscaling): escalado basado en eventos
  • Dapr (Distributed Application Runtime): integración nativa para microservicios
  • Envoy: proxy para enrutamiento y observabilidad
flowchart TB
  subgraph Internet["Internet / Usuarios"]
    USER["Cliente HTTP/API"]
  end

  subgraph ACA["Azure Container Apps Environment"]
    INGRESS["Ingress (Envoy)"]
    APP1["Container App 1"]
    APP2["Container App 2"]
    APP3["Container App 3"]
    DAPR["Dapr Sidecar"]
    KEDA["KEDA Scaler"]
  end

  subgraph Backend["Servicios Backend"]
    STORAGE["Azure Storage"]
    COSMOSDB["Cosmos DB"]
    SERVICEBUS["Service Bus"]
  end

  USER --> INGRESS
  INGRESS --> APP1
  INGRESS --> APP2
  INGRESS --> APP3
  APP1 --> DAPR
  APP2 --> DAPR
  DAPR --> STORAGE
  DAPR --> COSMOSDB
  SERVICEBUS --> KEDA
  KEDA --> APP3

Características Distintivas

Azure Container Apps se diferencia de otras soluciones de contenedores en Azure por:

  1. Optimización para microservicios: diseñado específicamente para aplicaciones que abarcan múltiples contenedores desplegados como servicios independientes

  2. Tecnologías open-source integradas:

  3. Dapr: service-to-service invocation, pub/sub, state management
  4. KEDA: escalado basado en eventos y métricas
  5. Envoy: balanceo de carga y observabilidad

  6. Arquitecturas event-driven: escalado basado en:

  7. Tráfico HTTP
  8. Mensajes en colas (Service Bus, Storage Queues, Event Hubs)
  9. Métricas personalizadas
  10. Escalado a cero para reducir costes

  11. Soporte para cargas de larga duración: procesos background, jobs y tareas programadas

Novedades 2024-2025: Lo que ha Cambiado

Desde el post original de 2022, Azure Container Apps ha incorporado capacidades empresariales significativas:

1. GPUs Serverless y Dedicadas (2024)

Serverless GPUs disponibles en regiones West US 3, Australia East y Sweden Central:

  • NVIDIA T4: coste-efectivas para inferencia de modelos pequeños (<10GB)
  • NVIDIA A100: para modelos grandes (>10GB), training, y cargas computacionales intensivas

Dedicated GPU workload profiles:

  • Perfiles NC24-A100, NC48-A100, NC96-A100
  • Aislamiento garantizado con hardware dedicado
  • Ideal para cargas de IA/ML continuas con baja latencia
# Ejemplo: Crear environment con GPU serverless
az containerapp env create \
  --name my-gpu-environment \
  --resource-group my-rg \
  --location westus3 \
  --enable-workload-profiles

# Desplegar app con GPU T4
az containerapp create \
  --name my-ai-app \
  --resource-group my-rg \
  --environment my-gpu-environment \
  --image myregistry.azurecr.io/ml-inference:latest \
  --cpu 8 --memory 56Gi \
  --workload-profile-name Consumption-GPU-NC8as-T4 \
  --target-port 8080 \
  --ingress external

2. Dynamic Sessions (Preview - 2024)

Ejecución segura y aislada de código generado por IA, ideal para:

  • Sandboxing de código no confiable
  • Evaluación de código generado por LLMs
  • Agentes de IA que ejecutan código dinámicamente

Características:

  • Aislamiento completo mediante Hyper-V
  • Contenedores gestionados (Python, Node.js) o personalizados
  • Escalado rápido y efímero

3. Azure Functions en Container Apps (GA - 2024)

Despliegue de Azure Functions como contenedores dentro de Container Apps:

  • Todos los triggers de Functions disponibles
  • Escalado basado en KEDA
  • Compartir infraestructura con microservicios y APIs
  • Soporte para GPUs en funciones compute-intensive
# Crear Function App optimizada para Container Apps
az containerapp create \
  --name my-function-app \
  --resource-group my-rg \
  --environment my-environment \
  --image myregistry.azurecr.io/my-functions:latest \
  --kind functionapp \
  --ingress external \
  --target-port 80

4. Workload Profiles Mejorados

Consumption Profile (por defecto):

  • Escalado automático
  • Pago solo por uso activo
  • Hasta 4 vCPU / 8 GiB memoria por réplica

Dedicated Profiles (opcionales):

  • General purpose (D4, D8, D16, D32)
  • Memory optimized (E4, E8, E16, E32)
  • GPU enabled (NC24-A100, NC48-A100, NC96-A100)

5. Networking y Seguridad Avanzada

  • Private Endpoints: acceso privado sin exposición pública
  • User Defined Routes (UDR): control total del tráfico de salida
  • NAT Gateway integration: simplifica conectividad saliente
  • Azure Front Door integration: CDN y WAF con Private Link
  • Client certificate authentication (mTLS): autenticación mutua TLS

6. Certificados Gestionados y Key Vault (GA - 2024)

  • Certificados TLS gratuitos gestionados automáticamente
  • Integración con Azure Key Vault para certificados personalizados
  • Renovación automática

7. Componentes Java Gestionados (Preview - 2024)

Para aplicaciones Spring:

  • Eureka Server: service discovery gestionado
  • Config Server: configuración centralizada
  • Tomcat support: despliegue directo desde código
  • JVM memory fit: configuración automática de memoria JVM

8. Observabilidad Mejorada

  • OpenTelemetry Agent (Preview): exportación de métricas, logs y traces sin configurar colector
  • Aspire Dashboard (Preview): dashboard interactivo para aplicaciones .NET
  • Java metrics: métricas de garbage collection, memoria, threads
  • Integración completa con Azure Monitor y Application Insights

Comparativa: ¿Cuándo Usar Container Apps?

Servicio Caso de Uso Ideal
Azure Container Apps Microservicios, APIs, event-driven apps, cargas AI/ML serverless, aplicaciones con escalado a cero
Azure Kubernetes Service (AKS) Control total de Kubernetes API, workloads complejos con requisitos específicos de K8s
Azure Red Hat OpenShift Despliegue OpenShift gestionado, migración desde on-premises OpenShift
Azure Functions Funciones FaaS puras, integraciones rápidas con triggers Azure, sin contenedores
Web App for Containers Aplicaciones web en contenedores Windows/Linux, migración lift-and-shift
Container Instances Contenedores de corta duración, batch jobs simples, aislamiento por hipervisor
Service Fabric Aplicaciones distribuidas legacy, necesidad de reliable services/actors
Container Registry Almacenamiento, gestión y replicación de imágenes de contenedor

Diagrama de Decisión Simplificado

flowchart TD
  START["¿Necesitas ejecutar contenedores?"]
  START --> K8S_API{"¿Necesitas acceso directo a Kubernetes API?"}

  K8S_API -->|Sí| AKS["Azure Kubernetes Service (AKS)"]
  K8S_API -->|No| SERVERLESS{"¿Prefieres serverless con escalado a cero?"}

  SERVERLESS -->|Sí| MICROSERVICES{"¿Es una arquitectura de microservicios / API?"}
  SERVERLESS -->|No| DEDICATED{"¿Necesitas hardware dedicado/GPU?"}

  MICROSERVICES -->|Sí| ACA["✅ Azure Container Apps"]
  MICROSERVICES -->|No| FUNCTIONS{"¿Solo funciones event-driven?"}

  FUNCTIONS -->|Sí| AZFUNC["Azure Functions"]
  FUNCTIONS -->|No| ACA2["✅ Azure Container Apps"]

  DEDICATED -->|Sí| ACA_DEDICATED["✅ Azure Container Apps
(Dedicated Workload Profiles)"] DEDICATED -->|No| WEBAPP["Web App for Containers"]

Casos de Uso Prácticos

1. Aplicación de IA Generativa con RAG

Implementar un chatbot con Retrieval Augmented Generation:

  • Frontend: contenedor React/Vue en Container App con ingress externo
  • API Gateway: contenedor .NET/Java con autenticación Entra ID
  • RAG Service: contenedor Python con GPU T4 para embeddings y inference
  • Vector Store: Cosmos DB con búsqueda vectorial
  • Escalado basado en peticiones HTTP, escala a cero en inactividad

2. Procesamiento de Eventos IoT

Pipeline de procesamiento de telemetría:

  • Ingestion: Container App escalando con Event Hubs (KEDA scaler)
  • Processing: múltiples Container Apps para transformación, enriquecimiento
  • Storage: escritura en Azure Storage / Cosmos DB mediante Dapr
  • Escalado automático según volumen de mensajes

3. Microservicios con Dapr

Arquitectura de e-commerce:

  • Catalog Service: gestión de productos
  • Order Service: procesamiento de pedidos
  • Inventory Service: control de stock
  • Payment Service: integración con pasarelas de pago

Comunicación via Dapr service invocation, state management en Redis/Cosmos DB, pub/sub con Service Bus.

4. Jobs Programados y Batch Processing

  • Jobs en Container Apps para tareas programadas (cron)
  • Procesamiento de archivos cargados en Storage
  • Generación de informes nocturnos
  • Limpieza de datos y mantenimiento

Despliegue: Ejemplos Prácticos

Despliegue Rápido con Azure CLI

# Variables
RESOURCE_GROUP="my-container-apps-rg"
LOCATION="westeurope"
ENVIRONMENT="my-environment"
APP_NAME="my-api"

# Crear resource group
az group create --name $RESOURCE_GROUP --location $LOCATION

# Crear environment
az containerapp env create \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION

# Desplegar desde imagen pública
az containerapp create \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --environment $ENVIRONMENT \
  --image mcr.microsoft.com/k8se/quickstart:latest \
  --target-port 80 \
  --ingress external \
  --min-replicas 0 \
  --max-replicas 10

Despliegue con Bicep

param location string = resourceGroup().location
param environmentName string = 'my-environment'
param containerAppName string = 'my-api'
param containerImage string = 'mcr.microsoft.com/k8se/quickstart:latest'

resource environment 'Microsoft.App/managedEnvironments@2023-05-01' = {
  name: environmentName
  location: location
  properties: {
    appLogsConfiguration: {
      destination: 'log-analytics'
      logAnalyticsConfiguration: {
        customerId: logAnalytics.properties.customerId
        sharedKey: logAnalytics.listKeys().primarySharedKey
      }
    }
  }
}

resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
  name: containerAppName
  location: location
  properties: {
    managedEnvironmentId: environment.id
    configuration: {
      ingress: {
        external: true
        targetPort: 80
        transport: 'auto'
      }
    }
    template: {
      containers: [
        {
          name: 'main'
          image: containerImage
          resources: {
            cpu: json('0.5')
            memory: '1Gi'
          }
        }
      ]
      scale: {
        minReplicas: 0
        maxReplicas: 10
        rules: [
          {
            name: 'http-rule'
            http: {
              metadata: {
                concurrentRequests: '100'
              }
            }
          }
        ]
      }
    }
  }
}

resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
  name: '${environmentName}-logs'
  location: location
  properties: {
    sku: {
      name: 'PerGB2018'
    }
  }
}

Despliegue con Dapr Habilitado

# Crear app con Dapr
az containerapp create \
  --name order-service \
  --resource-group $RESOURCE_GROUP \
  --environment $ENVIRONMENT \
  --image myregistry.azurecr.io/order-service:v1 \
  --target-port 3000 \
  --ingress internal \
  --min-replicas 1 \
  --enable-dapr \
  --dapr-app-id order-service \
  --dapr-app-port 3000 \
  --dapr-app-protocol http

# Invocar otro servicio via Dapr
# Desde código: http://localhost:3500/v1.0/invoke/inventory-service/method/check-stock

Administración del Ciclo de Vida: Revisiones

Una de las características más potentes de Container Apps son las revisiones (revisions):

  • Cada cambio en la configuración o imagen crea una nueva revisión
  • Múltiples revisiones activas simultáneamente
  • Traffic splitting: distribuir tráfico entre revisiones (A/B testing, blue/green)
  • Labels: asignar etiquetas (blue, green, canary) para URLs estables

Ejemplo: Blue/Green Deployment

# Desplegar revisión "blue" (producción actual)
az containerapp create \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --environment $ENVIRONMENT \
  --image myregistry.azurecr.io/myapp:v1.0 \
  --revision-suffix blue \
  --ingress external \
  --target-port 80 \
  --revisions-mode multiple

# Fijar 100% tráfico a blue
az containerapp ingress traffic set \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --revision-weight $APP_NAME--blue=100

# Asignar label "blue"
az containerapp revision label add \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --label blue \
  --revision $APP_NAME--blue

# Desplegar revisión "green" (nueva versión)
az containerapp update \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --image myregistry.azurecr.io/myapp:v2.0 \
  --revision-suffix green

# Probar green en URL específica: https://my-app---green.<environment-domain>

# Traffic splitting progresivo: 80% blue, 20% green
az containerapp ingress traffic set \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --revision-weight $APP_NAME--blue=80 $APP_NAME--green=20

# Si green funciona bien, cambiar 100% a green
az containerapp ingress traffic set \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --revision-weight $APP_NAME--green=100

# Desactivar revisión blue
az containerapp revision deactivate \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --revision $APP_NAME--blue

Networking: Opciones y Escenarios

Niveles de Accesibilidad

  1. External ingress: aplicación accesible desde Internet
  2. Internal ingress: solo accesible dentro del environment o VNet
  3. No ingress: para background workers o jobs

Integración con VNet

# Crear environment con VNet personalizada
az containerapp env create \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
  --infrastructure-subnet-resource-id /subscriptions/.../subnets/aca-subnet \
  --internal-only false

# Subnet mínimo: /27 (para workload profiles)
# Subnet mínimo: /23 (para consumption only)

Private Endpoints

# Crear environment con private endpoint
az containerapp env create \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
  --enable-workload-profiles \
  --public-network-access Disabled

# Crear private endpoint
az network private-endpoint create \
  --name my-private-endpoint \
  --resource-group $RESOURCE_GROUP \
  --vnet-name my-vnet \
  --subnet my-subnet \
  --private-connection-resource-id /subscriptions/.../managedEnvironments/$ENVIRONMENT \
  --group-id managedEnvironment \
  --connection-name my-connection

Observabilidad y Monitorización

Integración con Azure Monitor

Container Apps se integra automáticamente con:

  • Log Analytics: logs de contenedor, system logs
  • Application Insights: telemetría de aplicaciones, distributed tracing
  • Metrics: CPU, memoria, peticiones HTTP, réplicas activas
# Habilitar Application Insights
az containerapp env create \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
  --logs-workspace-id <LOG_ANALYTICS_WORKSPACE_ID> \
  --logs-workspace-key <LOG_ANALYTICS_WORKSPACE_KEY>

Consultas Útiles en Log Analytics

// Logs de aplicación
ContainerAppConsoleLogs_CL
| where ContainerAppName_s == "my-api"
| where TimeGenerated > ago(1h)
| project TimeGenerated, Log_s
| order by TimeGenerated desc

// Métricas de réplicas
ContainerAppSystemLogs_CL
| where Category == "ContainerAppScaling"
| where ContainerAppName_s == "my-api"
| project TimeGenerated, ReplicaCount_d
| render timechart

OpenTelemetry (Preview)

# Habilitar agente OpenTelemetry
az containerapp env telemetry app-insights set \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --connection-string "InstrumentationKey=...;IngestionEndpoint=..."

# Las apps envían automáticamente traces, metrics y logs a App Insights

Costes y Optimización

Modelo de Facturación

Consumption Plan:

  • Consumo activo: vCPU-segundos y GiB-segundos consumidos
  • Consumo idle: tarifa reducida cuando réplicas están idle (no procesando, <0.01 vCPU, <1KB/s red)
  • Requests: primeros 2 millones gratis/mes, luego por millón adicional
  • GPUs serverless: sin cargo por idle, solo por uso activo

Dedicated Plan (workload profiles):

  • Cargo fijo por gestión del plan si usas perfiles dedicados
  • Cargo por instancia de perfil (D4, E8, NC24-A100, etc.)
  • Escalado in/out ajusta coste según demanda

Estrategias de Optimización

  1. Escala a cero: configura minReplicas: 0 para apps con tráfico intermitente
  2. Right-sizing: ajusta CPU/memoria a necesidades reales
  3. Workload profiles: agrupa apps similares en el mismo perfil dedicado
  4. Savings Plans: Azure Container Apps elegible para planes de ahorro
  5. GPUs serverless: para cargas AI/ML variables, usa serverless en lugar de dedicado
# Configurar escalado a cero
az containerapp update \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --min-replicas 0 \
  --max-replicas 10 \
  --scale-rule-name http-rule \
  --scale-rule-type http \
  --scale-rule-metadata concurrentRequests=50

Seguridad: Mejores Prácticas

1. Managed Identities

# Habilitar system-assigned identity
az containerapp identity assign \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --system-assigned

# Asignar rol para acceder a Key Vault
IDENTITY_ID=$(az containerapp identity show \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --query principalId -o tsv)

az keyvault set-policy \
  --name my-keyvault \
  --object-id $IDENTITY_ID \
  --secret-permissions get list

2. Secretos

# Añadir secreto
az containerapp secret set \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --secrets db-connection="Server=...;Password=..."

# Referenciar en variable de entorno
az containerapp update \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --set-env-vars "DB_CONNECTION=secretref:db-connection"

3. Autenticación Built-in (EasyAuth)

# Habilitar autenticación con Entra ID
az containerapp auth update \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --enable \
  --action RequireAuthentication \
  --aad-tenant-id <TENANT_ID> \
  --aad-client-id <CLIENT_ID> \
  --aad-client-secret <CLIENT_SECRET>

4. Restricciones de IP

# Limitar acceso por IP
az containerapp ingress access-restriction set \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --rule-name office-network \
  --ip-address 203.0.113.0/24 \
  --action Allow

Conclusión

Azure Container Apps en 2025 es una plataforma madura y completa para ejecutar aplicaciones Cloud Native sin la complejidad de gestionar Kubernetes directamente. Con las incorporaciones de 2024-2025, se ha convertido en una opción viable para:

  • Microservicios y APIs: escalado automático, revisiones, traffic splitting
  • Event-driven architectures: integración con KEDA y múltiples event sources
  • Cargas AI/ML: GPUs serverless y dedicadas, Dynamic Sessions
  • Aplicaciones empresariales: networking avanzado, private endpoints, integración con Azure Functions

¿Cuándo Elegir Container Apps?

, si:

  • Quieres serverless con escalado a cero
  • Necesitas desplegar microservicios o APIs rápidamente
  • Prefieres no gestionar Kubernetes directamente
  • Quieres integración nativa con Dapr, KEDA, Envoy
  • Necesitas GPUs para cargas AI/ML con tráfico variable

No, si:

  • Necesitas acceso completo a Kubernetes API y CRDs
  • Tienes requisitos muy específicos de configuración de K8s
  • Ya tienes inversión significativa en operadores y herramientas K8s personalizadas

En resumen: si no necesitas acceso directo a Kubernetes API y trabajas con aplicaciones Cloud Native, Azure Container Apps es tu servicio en Azure.

Referencias y Recursos


Artículo actualizado en octubre de 2025. Para la versión original de 2022, ver Introducción a Azure Container Apps (Keepler Blog).

Cómo Resolver Divergencias en Git: Sincronizando Ramas Local y Remota

El Problema

Al intentar hacer git pull, te encuentras con el siguiente error:

hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only

Este mensaje indica que tu rama local y la rama remota han divergido: ambas tienen commits únicos que la otra no tiene. Git necesita que decidas cómo combinar estos cambios.

¿Por Qué Ocurre?

La divergencia sucede cuando:

  1. Trabajas localmente y realizas commits en tu rama main
  2. Mientras tanto, cambios se fusionan en el remoto (por ejemplo, a través de Pull Requests)
  3. Ambas ramas tienen commits exclusivos que la otra no posee

Ejemplo visual:

Local:   A---B---C---L1
                      ^(tu commit local)
Remote:  A---B---C---R1---R2---R3
                      ^(commits del remoto vía PR)

Análisis: Identificar la Divergencia

Antes de resolver, es crucial entender qué ha divergido.

Paso 1: Sincronizar con el Remoto

git fetch origin main --tags

Este comando descarga los cambios del remoto sin fusionarlos.

Paso 2: Comparar Commits

Verifica qué commits están únicamente en cada lado:

# Commits que tienes localmente pero no están en el remoto
git log origin/main..main --oneline

# Commits que están en el remoto pero no tienes localmente
git log main..origin/main --oneline

Paso 3: Revisar los HEADs

# Ver el commit actual local
git rev-parse HEAD

# Ver el commit actual remoto
git rev-parse origin/main

Ejemplo Real

En nuestro caso, obtuvimos:

--- Commits en local, no en remoto ---
81e3b93 feat: add new feature for data processing

--- Commits en remoto, no en local ---
9d3e3e9 Merge pull request #42 from team/feature/update-docs
285a538 Complete documentation review - all documents validated
000b9cb Fix configuration and add new parameters
a7694d3 Initial implementation plan

Interpretación: El remoto tiene un PR fusionado con 4 commits que no tenemos localmente, y nosotros tenemos 1 commit local que el remoto no tiene.

Estrategias de Resolución

Existen tres estrategias principales:

1. Merge (Recomendado para Trabajo Colaborativo)

Cuándo usarlo: Cuando trabajas con un equipo y quieres preservar toda la historia, incluyendo los merge commits de PRs.

Ventajas:

  • ✅ Preserva la historia completa (incluidos merge commits)
  • ✅ Muestra claramente cuándo se integraron features
  • ✅ Más seguro: no reescribe historia

Desventajas:

  • ⚠️ Crea un commit de merge adicional
  • ⚠️ Historia no completamente lineal

Comando:

git merge origin/main --no-edit

Si prefieres revisar/editar el mensaje de merge:

git merge origin/main

2. Rebase (Para Historia Lineal)

Cuándo usarlo: Cuando trabajas solo o en una feature branch que aún no has compartido públicamente.

Ventajas:

  • ✅ Historia completamente lineal
  • ✅ Más limpia para revisar con git log

Desventajas:

  • ⚠️ Reescribe commits locales (cambian sus SHAs)
  • ⚠️ Puede causar problemas si ya compartiste estos commits
  • ⚠️ Pierdes el contexto de cuándo se integró el PR remoto

Comando:

git rebase origin/main

3. Fast-Forward Only (Restrictivo)

Cuándo usarlo: Cuando quieres asegurarte de que solo hagas pull si no hay divergencia.

Comando:

git config pull.ff only
git pull

Si hay divergencia, fallará y tendrás que decidir merge o rebase manualmente.

Solución Paso a Paso (Merge)

1. Analizar la Situación

# Sincronizar con remoto
git fetch origin main --tags

# Ver divergencias
echo "--- Commits locales únicos ---"
git log origin/main..main --oneline

echo "--- Commits remotos únicos ---"
git log main..origin/main --oneline

2. Decidir Estrategia

Para equipos colaborativos con PRs, merge es la opción más segura:

git merge origin/main --no-edit

3. Resolver Conflictos (Si Ocurren)

Si hay conflictos, Git te lo indicará:

Auto-merging some-file.md
CONFLICT (content): Merge conflict in some-file.md
Automatic merge failed; fix conflicts and then commit the result.

Pasos para resolver:

a) Abre el archivo con conflictos:

<<<<<<< HEAD
Tu versión local
=======
Versión del remoto
>>>>>>> origin/main

b) Edita manualmente para quedarte con el contenido correcto

c) Marca como resuelto:

git add some-file.md

d) Completa el merge:

git commit -m "Merge origin/main into main"

4. Verificar el Resultado

# Ver el log reciente
git log --oneline --graph -10

Deberías ver algo como:

*   3e1ed57 (HEAD -> main) Merge remote-tracking branch 'origin/main' into main
|\
| * 9d3e3e9 (origin/main) Merge pull request #42
| * 285a538 Complete documentation review
| * 000b9cb Fix configuration parameters
| * a7694d3 Initial implementation plan
* | 81e3b93 feat: add new feature for data processing
|/
* d140dce feat: improved monthly workflow

5. Empujar los Cambios

git push origin main

Salida esperada:

Enumerating objects: 15, done.
Counting objects: 100% (12/12), done.
Delta compression using up to 8 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 1.08 KiB | 1.08 MiB/s, done.
Total 7 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), completed with 4 local objects.
To https://github.com/tu-usuario/tu-repositorio.git
   9d3e3e9..3e1ed57  main -> main

Verificación Post-Merge

Comprobar Archivos Críticos

Si tienes workflows de CI/CD u otros archivos críticos, verifica su integridad:

# Buscar un patrón específico en un archivo
grep -n "specific_pattern" .github/workflows/deploy.yml

# O leer el archivo completo
cat .github/workflows/deploy.yml

Confirmar Sincronización

# Ambos deberían apuntar al mismo commit ahora
git rev-parse HEAD
git rev-parse origin/main

Configuración Permanente

Si quieres establecer una estrategia por defecto para futuros git pull:

Para Merge (Recomendado)

git config pull.rebase false

Para Rebase

git config pull.rebase true

Global (Todos tus Repos)

Añade --global:

git config --global pull.rebase false

Mejores Prácticas

✅ Hacer Fetch Frecuentemente

# Ejecutar cada mañana o antes de empezar a trabajar
git fetch origin

Esto te mantiene informado de cambios remotos sin afectar tu trabajo local.

✅ Trabajar en Feature Branches

En lugar de trabajar directamente en main:

git checkout -b feature/my-feature
# ... hacer commits ...
git push origin feature/my-feature
# Abrir PR en GitHub

Esto evita divergencias en main.

✅ Pull Antes de Push

git fetch origin
git status
# Si hay cambios remotos, decide merge o rebase
git pull
# Ahora puedes pushear
git push

⚠️ Evitar Force Push en Ramas Compartidas

# ❌ NUNCA en main compartido
git push --force origin main

# ✅ OK solo en tus feature branches
git push --force origin feature/my-feature

Casos Especiales

Si Hiciste Rebase por Error y Quieres Revertir

# Ver el reflog (historial de cambios de HEAD)
git reflog

# Volver a un estado anterior
git reset --hard HEAD@{2}

Si Quieres Descartar Tus Commits Locales

# ⚠️ CUIDADO: Esto descarta tus commits locales permanentemente
git reset --hard origin/main

Si Quieres Preservar Cambios Locales Sin Commit

# Guardar cambios temporalmente
git stash

# Sincronizar con remoto
git pull

# Recuperar tus cambios
git stash pop

Resumen: Checklist Rápido

  • Fetch: git fetch origin main --tags
  • Analizar: git log origin/main..main --oneline y git log main..origin/main --oneline
  • Decidir: Merge (preservar historia) vs Rebase (lineal)
  • Ejecutar: git merge origin/main o git rebase origin/main
  • Resolver: Conflictos si los hay
  • Verificar: git log --graph --oneline -10
  • Push: git push origin main
  • Confirmar: Archivos críticos intactos

Conclusión

La divergencia de ramas es un escenario común en equipos colaborativos. La clave está en:

  1. Entender qué ha divergido (análisis con git log)
  2. Elegir la estrategia correcta (merge para equipos, rebase para trabajo local)
  3. Verificar el resultado antes de hacer push

En entornos colaborativos con Pull Requests, merge es generalmente la opción más segura ya que preserva la historia completa y evita reescribir commits que otros pueden estar usando como base.


Referencias

Azure DevOps Pipelines: environments, approvals y seguridad de secretos

Resumen

Azure DevOps Pipelines permite CI/CD seguro con environments, approvals y gestión avanzada de secretos. Este post va directo: cómo definir environments, configurar approvals, proteger secretos y auditar despliegues.

¿Qué es Pipelines avanzado?

  • Environments para dev, test, prod
  • Approvals y checks antes de despliegue
  • Secure secrets (Key Vault, variable groups)
  • Auditoría y compliance
  • Integración con Azure Monitor

Arquitectura / Cómo funciona

flowchart LR
    Dev[Dev] --> Repo[Repo]
    Repo --> Pipeline[Pipeline]
    Pipeline --> Env1[Environment: Dev]
    Pipeline --> Env2[Environment: Prod]
    Pipeline -.-> KeyVault[Key Vault]
    Pipeline -.-> Monitor[Azure Monitor]

Definir environments y approvals

  1. Crear environment:

    # Portal: Pipelines > Environments > New environment
    # Ejemplo: "prod-env"
    

  2. Configurar approval:

    environments:
      - name: prod-env
        resourceType: VirtualMachine
        approval:
          steps:
            - script: echo "Aprobado por admin"
    

  3. Checks avanzados:

  4. Deployment gates (Azure Monitor, REST API, Query Work Item)

Seguridad de secretos

  • Usar Azure Key Vault:

    variables:
    - group: kv-secrets
    steps:
    - task: AzureKeyVault@2
      inputs:
        azureSubscription: 'MySub'
        KeyVaultName: 'kv-prod'
        SecretsFilter: 'DbPassword,ApiKey'
        RunAsPreJob: true
    

  • Variable groups (portal):

    # Pipelines > Library > Variable groups > Link secrets
    

  • Secure files (certificados, pfx):

    # Pipelines > Library > Secure files
    

Auditoría y compliance

  • Auditar deployments:

    az pipelines runs list --pipeline-name "prod-pipeline"
    

  • Monitorizar cambios:

    az monitor activity-log list --resource-group $RESOURCE_GROUP
    

  • Alertas por cambios no aprobados:

    az monitor metrics alert create --name alert-unapproved --resource-group $RESOURCE_GROUP --scopes /subscriptions/$SUB_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.DevOps/pipelines/prod-pipeline --condition "total UnapprovedDeployments > 0" --window-size 5m --action $ACTION_GROUP_ID
    

Buenas prácticas

  • Usar environments y approvals en todos los despliegues a prod
  • Proteger secretos en Key Vault o variable groups
  • Auditar deployments y cambios semanalmente
  • Integrar alertas con Monitor y Sentinel
  • Usar secure files para certificados

Costes

  • Azure DevOps: gratis hasta 1,800 min/mes
  • Key Vault: ~$0.03/secret/mes
  • Monitor: ~$2.30/GB

Referencias

20251020 azure service bus mensajeria

Resumen

Azure Service Bus es el servicio de mensajería empresarial de Azure. Permite desacoplar aplicaciones y servicios mediante colas y topics, ideal para arquitecturas distribuidas y microservicios. Este post va directo a admins y DevOps que necesitan integrar sistemas de forma fiable y segura.

¿Qué es Azure Service Bus?

Azure Service Bus es un servicio PaaS de mensajería que ofrece:

  • Colas (Queues) para comunicación punto a punto
  • Topics y subscriptions para pub/sub
  • Entrega garantizada y ordenada
  • Soporte para mensajes transaccionales y sesiones
  • Integración con RBAC y Managed Identities

Arquitectura / Cómo funciona

flowchart LR
  App1[Productor] -- Envia mensaje --> SB[Service Bus Queue]
  SB -- Recibe mensaje --> App2[Consumidor]
  SB -. Dead-letter .-> DLQ[Dead Letter Queue]
  • El productor envía mensajes a la cola
  • El consumidor los procesa de forma asíncrona
  • Mensajes fallidos van a la Dead Letter Queue

Uso práctico: crear y usar una cola

1. Crear un Service Bus Namespace y una cola

# Variables
RESOURCE_GROUP="my-rg"
LOCATION="westeurope"
SB_NAMESPACE="my-sb-namespace"
QUEUE="myqueue"

# Crear namespace
az servicebus namespace create \
  --resource-group $RESOURCE_GROUP \
  --name $SB_NAMESPACE \
  --location $LOCATION

# Crear cola
az servicebus queue create \
  --resource-group $RESOURCE_GROUP \
  --namespace-name $SB_NAMESPACE \
  --name $QUEUE

2. Enviar y recibir mensajes (Python, passwordless recomendado)

import asyncio
from azure.servicebus.aio import ServiceBusClient
from azure.servicebus import ServiceBusMessage
from azure.identity.aio import DefaultAzureCredential

FULLY_QUALIFIED_NAMESPACE = "<namespace>.servicebus.windows.net"
QUEUE_NAME = "myqueue"

async def send_message():
  credential = DefaultAzureCredential()
  async with ServiceBusClient(FULLY_QUALIFIED_NAMESPACE, credential) as client:
    sender = client.get_queue_sender(QUEUE_NAME)
    async with sender:
      msg = ServiceBusMessage("Hola Azure Service Bus!")
      await sender.send_messages(msg)
    await credential.close()

async def receive_message():
  credential = DefaultAzureCredential()
  async with ServiceBusClient(FULLY_QUALIFIED_NAMESPACE, credential) as client:
    receiver = client.get_queue_receiver(QUEUE_NAME)
    async with receiver:
      async for msg in receiver:
        print("Mensaje recibido:", str(msg))
        await receiver.complete_message(msg)
    await credential.close()

# Ejecutar
asyncio.run(send_message())
asyncio.run(receive_message())

Más detalles y ejemplos oficiales:

Buenas prácticas / Seguridad

  • Usa Managed Identities en vez de connection strings
  • Activa el cifrado con claves gestionadas por el cliente (CMK) si es necesario
  • Configura reglas de red y firewall
  • Usa colas de dead-letter para mensajes no procesables
  • Monitoriza con Azure Monitor y alertas

Referencias

Terraform en Azure: detección y corrección de drift

Resumen

Cómo detectar y corregir drift en recursos Azure gestionados con Terraform. Post directo para admins y DevOps: comandos prácticos y ejemplos reproducibles.

¿Qué es el drift?

  • Cambios fuera de Terraform (portal, scripts, etc)
  • Recursos "fuera de sync" con el estado deseado
  • Impacto: errores, inseguridad, compliance roto

Arquitectura / Funcionamiento

flowchart LR
    TF[Terraform State] --> Azure[Azure Resource]
    Azure -.-> Portal[Portal]
    Azure -.-> Script[Script]
    TF -.-> Drift[Drift Detection]

Detección de drift

  1. Comprobar estado:

    terraform plan
    

  2. Usar azurerm_resource_drifts:

    resource "azurerm_resource_drifts" "example" {
      resource_id = azurerm_virtual_machine.example.id
    }
    

  3. Validar con Azure Policy:

    az policy state list --resource-group $RG --policy-assignment $POLICY
    

Corrección de drift

  1. Aplicar cambios:

    terraform apply
    

  2. Forzar recreación:

    terraform taint azurerm_virtual_machine.example
    terraform apply
    

  3. Reimportar recursos:

    terraform import azurerm_virtual_machine.example /subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Compute/virtualMachines/xxx
    

Buenas prácticas

  • Auditar drift semanalmente
  • Usar Policy para detectar cambios manuales
  • Documentar excepciones
  • Validar con MCP y Terraform Registry

Referencias

Azure AD PIM: Just-In-Time y alertas avanzadas

Resumen

Cómo usar Privileged Identity Management (PIM) en Azure AD para acceso JIT y alertas de seguridad. Post directo para admins y arquitectos cloud.

¿Qué es PIM JIT?

  • Acceso Just-In-Time (JIT) a roles privilegiados
  • Alertas por uso sospechoso
  • Auditoría y compliance

Arquitectura / Funcionamiento

flowchart LR
    User[Usuario] --> PIM[PIM]
    PIM --> Role[Rol Privilegiado]
    PIM --> Alertas[Alertas]
    PIM --> Audit[Auditoría]

Configuración rápida

  1. Activar PIM:

    az ad pim role assignment list
    

  2. Configurar alertas:

    az ad pim alert list --role "Global Administrator"
    

  3. Acceso JIT:

    az ad pim role assignment activate --role "Global Administrator" --duration 1
    

Auditoría y compliance

  • Revisar logs:

    az ad pim audit list
    

  • Alertas automáticas:

    az monitor metrics alert create --name alert-pim --resource-group $RG --scopes /subscriptions/$SUB_ID/resourceGroups/$RG/providers/Microsoft.Authorization/roleAssignments --condition "total SuspiciousPIMActivations > 0" --window-size 5m --action $ACTION_GROUP_ID
    

Buenas prácticas

  • Usar PIM para todos los roles críticos
  • Configurar alertas y auditoría
  • Revisar logs semanalmente
  • Validar con MCP

Referencias