La lección anterior terminó con una pregunta implícita: si para tener una API disponible y elástica hay que montar un conjunto de escalado, un balanceador, sondas, reglas de autoescalado, parches del sistema operativo y certificados a mano… ¿no habrá algo que haga todo eso por ti? Lo hay, y se llama Azure App Service.

Contoso Airlines tiene dos aplicaciones nuevas que no arrastran deuda del pasado: Contoso Reservas, la web pública donde el cliente busca vuelos y compra su billete, y la API de Disponibilidad, el servicio interno que consulta plazas y precios. Ambas son código moderno (Java para la API, Node.js para la web) que Diego Salas puede empaquetar y desplegar sin depender del sistema operativo. Para ellas, mantener máquinas virtuales sería trabajo gratuito: parches, agentes, certificados y despliegues manuales que no aportan un céntimo al negocio.

En esta lección aprenderás qué es App Service, cómo funcionan sus planes y por qué se factura el plan y no la aplicación, cómo desplegar código con Azure CLI, cómo configurar la aplicación con ajustes y cadenas de conexión, cómo poner el dominio propio de Contoso con certificado gestionado, y cómo publicar versiones nuevas sin cortar el servicio usando ranuras de despliegue. Recuerda que en el módulo 1 ya quedó anunciada la aplicación app-contoso-reservas-pro: aquí la creamos de verdad.

Aviso de coste: el nivel Free (F1) es gratuito y sirve para casi todos los ejercicios. Los niveles Basic, Standard y Premium se facturan por hora mientras el plan exista, aunque la aplicación no reciba ni una petición. Al final tienes la limpieza.

Contenido

  1. Qué es App Service y por qué Contoso lo elige
  2. Planes de App Service: niveles, Linux y Windows
  3. Pilas de tiempo de ejecución soportadas
  4. Crear el plan y las aplicaciones con Azure CLI
  5. Desplegar código: ZIP deploy y despliegue desde Git
  6. Configuración: ajustes de aplicación y cadenas de conexión
  7. Dominios personalizados y certificados TLS gestionados
  8. Ranuras de despliegue e intercambio con calentamiento
  9. Escalado vertical, horizontal y automático
  10. Diagnóstico: registros, secuencia de registro y consola SSH
  11. Integración con la red virtual (introducción)
  12. Limpieza
  13. Errores Comunes y Consejos
  14. Ejercicios
  15. Conclusión

  1. Qué es App Service y por qué Contoso lo elige

Azure App Service es una plataforma como servicio (PaaS) para alojar aplicaciones web, API y aplicaciones móviles de back-end. Tú entregas el código o el artefacto; Azure se ocupa del sistema operativo, del servidor web, del tiempo de ejecución, del balanceo entre instancias y del certificado TLS.

Comparado con lo que montaste en la lección anterior:

Tarea Con VM y VMSS Con App Service
Parchear el sistema operativo Tuya De Azure
Instalar y actualizar el tiempo de ejecución (JDK, Node) Tuya De Azure (eliges la versión)
Balancear entre instancias Load Balancer que montas y pagas Incluido
Escalado automático Reglas sobre el conjunto de escalado Reglas sobre el plan (más simple)
Certificado TLS Compra, instalación y renovación manuales Gestionado y renovado por Azure
Despliegue sin caídas Lo diseñas tú Ranuras con intercambio, incluidas
Registros y consola Agentes que instalas Integrados en el servicio
Control del sistema operativo Total Ninguno

La última fila es la contrapartida: en App Service no hay acceso de administrador al servidor, no puedes instalar demonios arbitrarios ni depender de rutas fuera del espacio de tu aplicación. Por eso el motor de disponibilidad heredado sigue en una VM (lección 02-01) y las aplicaciones nuevas van aquí.

Decisión registrada de Contoso Airlines:

  • app-contoso-reservas-pro: web pública de venta, Node.js sobre Linux.
  • app-contoso-api-disponibilidad-pro: API interna de plazas y precios, Java sobre Linux.
  • Ambas en el mismo plan de producción al principio; la API se separa a su propio plan cuando el pico de la apertura de temporada lo justifique (lo verás en el ejercicio 1).

  1. Planes de App Service: niveles, Linux y Windows

El concepto más importante y el que más facturas sorpresa provoca: el plan de App Service es el conjunto de máquinas donde se ejecutan tus aplicaciones. Se factura el plan, no la aplicación.

Consecuencias directas:

  • Diez aplicaciones en un plan cuestan lo mismo que una: lo que pagas es el plan.
  • Una aplicación detenida en un plan de pago sigue costando, porque el plan sigue existiendo. Para dejar de pagar hay que borrar el plan (o bajarlo a Free).
  • Las aplicaciones de un mismo plan comparten CPU, memoria e instancias. Si una consume la CPU, las demás lo notan.
Nivel Uso previsto Instancias Ranuras Dominio propio y TLS Escalado automático Notas
Free (F1) Pruebas y aprendizaje 1 compartida, cuota diaria de CPU No No No Gratis, sin SLA, se duerme
Basic (B1–B3) Desarrollo y aplicaciones pequeñas Hasta 3, dedicadas No Sí Manual Sin ranuras: no hay despliegue sin caídas
Standard (S1–S3) Producción general Hasta 10 5 Sí Sí El primer nivel realmente apto para producción
Premium v3 (P0v3–P5v3) Producción exigente Hasta 30 20 Sí Sí Más CPU y memoria por instancia, zonas de disponibilidad, más entradas y salidas de red
Isolated v2 Aislamiento y cumplimiento estrictos Hasta 100 20 Sí Sí Se ejecuta en tu propia red (App Service Environment). Caro

Qué desbloquea cada salto, dicho sin rodeos:

  • De Free a Basic: instancias dedicadas, tu propio dominio y TLS, y que la aplicación no se duerma.
  • De Basic a Standard: ranuras de despliegue y escalado automático. Es el salto que convierte un juguete en un servicio de producción.
  • De Standard a Premium v3: hardware más rápido, más instancias, redundancia de zona y mejores límites de red. Es el nivel que exige la política de Contoso de tener los componentes críticos en dos zonas.

Linux frente a Windows

Aspecto Plan Linux Plan Windows
Pilas Java, Node, Python, PHP, .NET, Go (contenedor) .NET Framework, .NET, Java, Node, PHP
Precio Ligeramente inferior en niveles equivalentes Ligeramente superior
.NET Framework clásico (4.x) No Sí (es su razón de ser)
Contenedores propios Sí (detalle en 06-01) Limitado
Mezclar Linux y Windows en el mismo plan No se puede: son planes distintos

Contoso elige Linux para las dos aplicaciones: no hay nada de .NET Framework clásico, el precio es menor y las pilas de Node y Java están perfectamente soportadas.

  1. Pilas de tiempo de ejecución soportadas

App Service ejecuta tu código sobre una pila de tiempo de ejecución que eliges al crear la aplicación y puedes cambiar después. Consulta las disponibles con:

# Todas las pilas disponibles para aplicaciones web en Linux.
az webapp list-runtimes --os-type linux --output table

Salida abreviada y su lectura:

[
  "JAVA:21-java21",
  "JAVA:17-java17",
  "NODE:22-lts",
  "NODE:20-lts",
  "PYTHON:3.12",
  "PHP:8.3",
  "DOTNETCORE:8.0"
]

El formato es PILA:VERSION. Dos avisos importantes:

  • Las versiones tienen fin de soporte: cuando una versión de Node o Java sale del soporte de la comunidad, Azure la retira de la lista y avisa antes. Planifica actualizaciones; no descubras el final de soporte el día que falla un despliegue.
  • Si tu pila o versión no está en la lista (por ejemplo, Go o una versión antigua muy concreta), la salida es un contenedor propio, y eso se trata en la lección 06-01.

  1. Crear el plan y las aplicaciones con Azure CLI

Usamos primero el entorno de desarrollo, con nivel gratuito, para que puedas seguir la lección sin coste.

#!/usr/bin/env bash
set -euo pipefail

GRUPO="rg-contoso-reservas-dev"
REGION="westeurope"
PLAN="plan-contoso-reservas-dev"
APP_WEB="app-contoso-reservas-dev"
APP_API="app-contoso-api-disponibilidad-dev"

ETIQUETAS=(entorno=desarrollo proyecto=contoso-reservas centro-coste=CC-1042
           [email protected])

# 1. Plan de App Service: Linux, nivel gratuito.
az appservice plan create \
  --resource-group "${GRUPO}" \
  --name "${PLAN}" \
  --location "${REGION}" \
  --is-linux \
  --sku F1 \
  --tags "${ETIQUETAS[@]}" \
  --output table

# 2. Web de reservas sobre Node.js 22.
az webapp create \
  --resource-group "${GRUPO}" \
  --plan "${PLAN}" \
  --name "${APP_WEB}" \
  --runtime "NODE:22-lts" \
  --tags "${ETIQUETAS[@]}" \
  --output table

# 3. API de Disponibilidad sobre Java 21, en el mismo plan.
az webapp create \
  --resource-group "${GRUPO}" \
  --plan "${PLAN}" \
  --name "${APP_API}" \
  --runtime "JAVA:21-java21" \
  --tags "${ETIQUETAS[@]}" \
  --output table

Detalles que importan:

  • --is-linux define el sistema operativo del plan, no de la aplicación. No se puede cambiar después: si te equivocas, hay que crear otro plan.
  • El nombre de la aplicación es globalmente único, porque genera el nombre de host app-contoso-reservas-dev.azurewebsites.net. Si alguien en el mundo lo ha tomado, falla. Por eso Contoso usa un prefijo propio de organización.
  • Las dos aplicaciones comparten plan: el coste no cambia por añadir la segunda.
  • En el nivel F1 no puedes activar --https-only… en realidad sí puedes, y debes:
# Forzar HTTPS: cualquier petición HTTP se redirige a HTTPS.
az webapp update \
  --resource-group "${GRUPO}" \
  --name "${APP_WEB}" \
  --https-only true \
  --output none

# Exigir TLS 1.2 como mínimo (valor recomendado; 1.3 donde esté disponible).
az webapp config set \
  --resource-group "${GRUPO}" \
  --name "${APP_WEB}" \
  --min-tls-version 1.2 \
  --output none

El dominio *.azurewebsites.net ya viene con certificado TLS válido, así que tu aplicación es accesible por HTTPS desde el primer segundo.

  1. Desplegar código: ZIP deploy y despliegue desde Git

ZIP deploy: el método directo

Es el más usado en scripts y tuberías: empaquetas la aplicación y la subes.

# Aplicación Node mínima que devuelve el estado de la web de reservas.
mkdir -p contoso-reservas && cd contoso-reservas

cat > index.js <<'EOF'
const http = require('http');
const puerto = process.env.PORT || 8080;
const version = process.env.VERSION_APP || 'desconocida';

http.createServer((req, res) => {
  if (req.url === '/salud') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    return res.end(JSON.stringify({ servicio: 'contoso-reservas', estado: 'ok', version }));
  }
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
  res.end(`<h1>Contoso Reservas</h1><p>Versión ${version}</p>`);
}).listen(puerto);
EOF

cat > package.json <<'EOF'
{
  "name": "contoso-reservas",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": { "start": "node index.js" }
}
EOF

zip -r ../contoso-reservas.zip . && cd ..

# Despliegue del paquete.
az webapp deploy \
  --resource-group rg-contoso-reservas-dev \
  --name app-contoso-reservas-dev \
  --src-path contoso-reservas.zip \
  --type zip

Tres cosas fundamentales de este código, porque son las que fallan a todo el mundo la primera vez:

  1. El puerto viene en process.env.PORT. App Service inyecta esa variable y espera que tu aplicación escuche ahí. Si escuchas en un puerto fijo, verás el error «Application Error» sin más pista.
  2. La aplicación debe escuchar en todas las interfaces, no solo en 127.0.0.1.
  3. App Service necesita saber cómo arrancarla. Con Node usa npm start o index.js; si tu punto de entrada es otro, defínelo:
az webapp config set \
  --resource-group rg-contoso-reservas-dev \
  --name app-contoso-reservas-dev \
  --startup-file "node index.js" \
  --output none

Para Java, el artefacto es un .jar o .war:

az webapp deploy \
  --resource-group rg-contoso-reservas-dev \
  --name app-contoso-api-disponibilidad-dev \
  --src-path api-disponibilidad.jar \
  --type jar

Despliegue desde Git

App Service incluye un repositorio Git local al que puedes empujar código; cada git push dispara una compilación y un despliegue.

# 1. Habilitar el repositorio Git local y obtener la URL de destino.
URL_GIT=$(az webapp deployment source config-local-git \
  --resource-group rg-contoso-reservas-dev \
  --name app-contoso-reservas-dev \
  --query url --output tsv)

# 2. Credenciales de publicación de ámbito de usuario (una vez por cuenta).
az webapp deployment user set --user-name contoso-despliegue

# 3. Añadir el remoto y empujar.
git remote add azure "${URL_GIT}"
git push azure main:master

También puede conectarse a GitHub para desplegar en cada confirmación de una rama. Es cómodo para empezar, pero para producción el camino correcto son las tuberías de Azure Pipelines, con sus entornos y aprobaciones: eso es el módulo 5, y no lo desarrollamos aquí.

Comprueba el resultado:

curl -s "https://app-contoso-reservas-dev.azurewebsites.net/salud"

  1. Configuración: ajustes de aplicación y cadenas de conexión

Una aplicación bien hecha no lleva la configuración dentro del código. En App Service hay dos mecanismos, y ambos llegan a tu proceso como variables de entorno.

Mecanismo Para qué Cómo lo ve la aplicación
Ajustes de aplicación (app settings) Cualquier parámetro: URL, modos, tiempos de espera, versiones Variable de entorno con el mismo nombre
Cadenas de conexión (connection strings) Conexiones a bases de datos, con tipo (SQLAzure, MySQL, PostgreSQL, Custom) Variable con prefijo según el tipo, por ejemplo SQLAZURECONNSTR_
# Ajustes de aplicación de la web de reservas.
az webapp config appsettings set \
  --resource-group rg-contoso-reservas-dev \
  --name app-contoso-reservas-dev \
  --settings VERSION_APP=1.4.0 \
             ENTORNO=desarrollo \
             URL_API_DISPONIBILIDAD="https://app-contoso-api-disponibilidad-dev.azurewebsites.net" \
             TIEMPO_ESPERA_MS=3000 \
  --output table

# Cadena de conexión a la base de datos de reservas (módulo 3).
az webapp config connection-string set \
  --resource-group rg-contoso-reservas-dev \
  --name app-contoso-reservas-dev \
  --connection-string-type SQLAzure \
  --settings ReservasDb="Server=tcp:sql-contoso-reservas-dev.database.windows.net;Database=db-reservas;Authentication=Active Directory Default;" \
  --output none

Puntos clave que debes recordar:

  • Cambiar un ajuste reinicia la aplicación. No es instantáneo ni transparente: agrúpalos y aplícalos juntos.
  • Los ajustes sustituyen a los del fichero de configuración de tu aplicación con el mismo nombre. Es el mecanismo para tener el mismo artefacto en desarrollo y producción.
  • Cada ranura de despliegue puede tener sus propios ajustes; puedes marcarlos como «de ranura» para que no viajen al intercambiar (apartado 8).

Por qué NO se ponen secretos aquí en producción

Los ajustes de aplicación están cifrados en reposo y no aparecen en el código, lo cual ya es mejor que la alternativa habitual. Pero:

  • Cualquiera con permiso de Colaborador sobre la aplicación puede leerlos en claro desde el portal o con az webapp config appsettings list.
  • Quedan expuestos en exportaciones de plantillas y en volcados de configuración.
  • No hay rotación automática, ni caducidad, ni auditoría de acceso individual a cada secreto.

La solución correcta en Azure es Azure Key Vault, con referencias desde la aplicación que apuntan al secreto en lugar de contenerlo:

# Anticipo de la lección 04-03: el valor es una referencia, no el secreto.
az webapp config appsettings set \
  --resource-group rg-contoso-reservas-pro \
  --name app-contoso-reservas-pro \
  --settings CLAVE_PASARELA_PAGO="@Microsoft.KeyVault(SecretUri=https://kv-contoso-seguridad.vault.azure.net/secrets/clave-pasarela-pago/)" \
  --output none

Para que esto funcione, la aplicación necesita una identidad administrada con permiso de lectura sobre el almacén. Identidades administradas y RBAC son la lección 04-02; Key Vault, la 04-03. Contoso guarda ahí la clave de la pasarela de pago y la cadena de conexión de producción, en rg-contoso-seguridad-pro. Aquí solo debes quedarte con la regla: en desarrollo, ajustes; en producción, referencias a Key Vault.

  1. Dominios personalizados y certificados TLS gestionados

Nadie vende billetes en app-contoso-reservas-pro.azurewebsites.net. Contoso usa el dominio contosoairlines.example y quiere la web en www.contosoairlines.example.

El proceso tiene tres pasos:

Paso 1: demostrar que el dominio es tuyo. Se crean dos registros DNS en tu proveedor (Azure DNS se ve en la lección 02-06):

Tipo Nombre Valor Para qué
CNAME www app-contoso-reservas-pro.azurewebsites.net Encaminar el tráfico
TXT asuid.www Identificador de verificación de la aplicación Probar la propiedad
# Identificador de verificación de dominio de la aplicación.
az webapp show \
  --resource-group rg-contoso-reservas-pro \
  --name app-contoso-reservas-pro \
  --query customDomainVerificationId \
  --output tsv

Paso 2: asociar el dominio a la aplicación.

az webapp config hostname add \
  --resource-group rg-contoso-reservas-pro \
  --webapp-name app-contoso-reservas-pro \
  --hostname www.contosoairlines.example

Paso 3: certificado TLS gestionado, gratuito y con renovación automática.

# 1. Crear el certificado gestionado por App Service para ese nombre de host.
az webapp config ssl create \
  --resource-group rg-contoso-reservas-pro \
  --name app-contoso-reservas-pro \
  --hostname www.contosoairlines.example

# 2. Obtener su huella digital y enlazarlo con SNI.
HUELLA=$(az webapp config ssl list \
  --resource-group rg-contoso-reservas-pro \
  --query "[?subjectName=='www.contosoairlines.example'].thumbprint" \
  --output tsv)

az webapp config ssl bind \
  --resource-group rg-contoso-reservas-pro \
  --name app-contoso-reservas-pro \
  --certificate-thumbprint "${HUELLA}" \
  --ssl-type SNI

Lo que debes saber del certificado gestionado: es gratuito, se renueva solo, requiere nivel Basic o superior, y no cubre algunos casos (comodines en ciertas configuraciones, dominios sin verificación pública). Para esos casos se sube un certificado propio o se usa uno de App Service Certificate. La ventaja operativa es enorme: los cortes por certificados caducados dejan de existir.

  1. Ranuras de despliegue e intercambio con calentamiento

Aquí está, probablemente, la funcionalidad que más justifica pagar el nivel Standard.

Una ranura de despliegue es una instancia adicional de la aplicación, dentro del mismo plan, con su propio nombre de host y su propia configuración. El intercambio (swap) cambia de sitio el contenido de dos ranuras… sin reiniciar el proceso ni cortar el tráfico.

graph LR
    subgraph ANTES["Antes del intercambio"]
        P1["Ranura producción<br/>versión 1.4.0<br/>← tráfico real"]
        S1["Ranura preproduccion<br/>versión 1.5.0<br/>← pruebas internas"]
    end
    subgraph DESPUES["Después del intercambio"]
        P2["Ranura producción<br/>versión 1.5.0<br/>← tráfico real"]
        S2["Ranura preproduccion<br/>versión 1.4.0<br/>← lista para revertir"]
    end
    ANTES -->|"az webapp deployment slot swap"| DESPUES
GRUPO="rg-contoso-reservas-pro"
APP="app-contoso-reservas-pro"

# 1. Crear la ranura de preproducción clonando la configuración de producción.
az webapp deployment slot create \
  --resource-group "${GRUPO}" \
  --name "${APP}" \
  --slot preproduccion \
  --configuration-source "${APP}" \
  --output none

# 2. Desplegar la versión nueva SOLO en la ranura.
az webapp deploy \
  --resource-group "${GRUPO}" \
  --name "${APP}" \
  --slot preproduccion \
  --src-path contoso-reservas-1.5.0.zip \
  --type zip

# 3. Probar la ranura en su propia URL, sin afectar a los clientes.
curl -s "https://${APP}-preproduccion.azurewebsites.net/salud"

# 4. Intercambiar: la versión probada pasa a producción.
az webapp deployment slot swap \
  --resource-group "${GRUPO}" \
  --name "${APP}" \
  --slot preproduccion \
  --target-slot production

Qué hace exactamente el intercambio (y por qué no hay caída)

El intercambio no copia ficheros: cambia el enrutamiento entre dos conjuntos de trabajadores ya en marcha. Antes de conmutar, Azure ejecuta un calentamiento:

  1. Aplica a la ranura de origen los ajustes de la ranura de destino (los que no están marcados como «de ranura»).
  2. Reinicia los trabajadores de la ranura con esa configuración y espera a que respondan.
  3. Solo cuando responden correctamente, conmuta el enrutamiento.

Esto elimina el arranque en frío que sufriría el primer cliente tras un despliegue clásico. Puedes controlar el calentamiento con ajustes específicos:

az webapp config appsettings set \
  --resource-group "${GRUPO}" --name "${APP}" --slot preproduccion \
  --settings WEBSITE_SWAP_WARMUP_PING_PATH="/salud" \
             WEBSITE_SWAP_WARMUP_PING_STATUSES="200" \
             WEBSITE_WARMUP_PATH="/salud" \
  --output none

Ajustes de ranura

Algunos valores no deben viajar en el intercambio: la cadena de conexión de la base de datos de pruebas no puede acabar en producción. Se marcan como «de ranura» (slot setting):

az webapp config appsettings set \
  --resource-group "${GRUPO}" --name "${APP}" --slot preproduccion \
  --slot-settings ENTORNO=preproduccion \
  --output none

Reversión y despliegue progresivo

  • Revertir es volver a intercambiar: la versión anterior sigue viva en la otra ranura. Es la vuelta atrás más rápida que existe.
  • Despliegue progresivo (canary): puedes enviar un porcentaje del tráfico real a la ranura antes de intercambiar.
# El 10 % del tráfico real va a la ranura de preproducción.
az webapp traffic-routing set \
  --resource-group "${GRUPO}" --name "${APP}" \
  --distribution preproduccion=10

# Quitar el reparto cuando termine la prueba.
az webapp traffic-routing clear --resource-group "${GRUPO}" --name "${APP}"

Un aviso importante: las ranuras comparten el plan, es decir, comparten CPU y memoria con producción. Una prueba de carga contra la ranura afecta a los clientes reales. Para pruebas de carga serias, plan aparte.

  1. Escalado vertical, horizontal y automático

En App Service se escala el plan, no la aplicación.

# Escalado vertical: cambiar de nivel (más CPU y memoria por instancia).
az appservice plan update \
  --resource-group rg-contoso-reservas-pro \
  --name plan-contoso-reservas-pro \
  --sku P1v3

# Escalado horizontal manual: número de instancias.
az appservice plan update \
  --resource-group rg-contoso-reservas-pro \
  --name plan-contoso-reservas-pro \
  --number-of-workers 4

Escalado automático, con la misma mecánica de reglas que viste en 02-02 pero sin conjunto de escalado que administrar:

PLAN_ID=$(az appservice plan show \
  -g rg-contoso-reservas-pro -n plan-contoso-reservas-pro --query id -o tsv)

az monitor autoscale create \
  --resource-group rg-contoso-reservas-pro \
  --resource "${PLAN_ID}" \
  --name autoescala-plan-reservas \
  --min-count 2 --max-count 10 --count 2 --output none

az monitor autoscale rule create \
  --resource-group rg-contoso-reservas-pro \
  --autoscale-name autoescala-plan-reservas \
  --condition "CpuPercentage > 70 avg 5m" --scale out 2 --cooldown 5 --output none

az monitor autoscale rule create \
  --resource-group rg-contoso-reservas-pro \
  --autoscale-name autoescala-plan-reservas \
  --condition "CpuPercentage < 30 avg 10m" --scale in 1 --cooldown 10 --output none

Se aplican las mismas reglas de oro: subir rápido, bajar despacio, banda muerta amplia y perfil programado para la apertura de temporada. Y el mismo requisito: la aplicación no puede tener estado local. Como todas las instancias de un plan comparten el sistema de ficheros de la aplicación (montado desde almacenamiento), escribir ahí en caliente es lento y frágil: las tarjetas de embarque van a Blob Storage (lección 02-04) y los datos a la base de datos (módulo 3).

Otras dos características del plan que conviene conocer:

  • Always On: mantiene la aplicación despierta. En Free y Basic la aplicación se descarga tras un rato sin tráfico y la siguiente petición sufre un arranque en frío. Actívalo en producción (--always-on true, requiere Basic o superior).
  • Redundancia de zona: disponible en Premium v3, distribuye las instancias entre zonas de disponibilidad. Es la opción que cumple la política de Contoso para componentes críticos.

  1. Diagnóstico: registros, secuencia de registro y consola SSH

GRUPO="rg-contoso-reservas-dev"
APP="app-contoso-reservas-dev"

# 1. Activar el registro de aplicación y del servidor web al sistema de ficheros.
az webapp log config \
  --resource-group "${GRUPO}" --name "${APP}" \
  --application-logging filesystem \
  --web-server-logging filesystem \
  --detailed-error-messages true \
  --failed-request-tracing true \
  --level information \
  --output none

# 2. Ver la secuencia de registro en tiempo real (Ctrl+C para salir).
az webapp log tail --resource-group "${GRUPO}" --name "${APP}"

# 3. Descargar los registros para analizarlos.
az webapp log download --resource-group "${GRUPO}" --name "${APP}" --log-file registros.zip

az webapp log tail es la herramienta de diagnóstico más rentable de App Service: muestra en vivo lo que escribe tu aplicación en la salida estándar. Si tu aplicación no arranca, ahí verás el motivo real (falta una dependencia, el puerto es incorrecto, la variable de entorno no existe).

Consola dentro del contenedor de la aplicación:

# Sesión SSH contra la instancia (solo planes Linux).
az webapp ssh --resource-group "${GRUPO}" --name "${APP}"

Dentro encontrarás tu código en /home/site/wwwroot. Recuerda: lo que escribas fuera de /home se pierde al reiniciar o al mover la aplicación de instancia; es el equivalente al disco temporal de una VM.

Otras herramientas del servicio, para que sepas que existen:

  • Diagnosticar y solucionar problemas en el portal: detectores automáticos de caídas, reinicios, uso de memoria y errores HTTP.
  • Kudu (https://<app>.scm.azurewebsites.net): la consola avanzada del servicio, con explorador de ficheros, procesos y registros de despliegue.
  • Application Insights: la telemetría real de la aplicación (peticiones, dependencias, excepciones, rendimiento). Es la lección 07-03 y es lo que Contoso acaba usando a diario.

  1. Integración con la red virtual (introducción)

Por defecto, una Web App vive fuera de tu red virtual: recibe tráfico de internet y sale a internet. Contoso necesita lo contrario en dos direcciones:

  • Salida: la web y la API deben llegar a la base de datos sql-contoso-reservas-pro sin pasar por internet. Eso se resuelve con la integración con red virtual, que conecta la salida de la aplicación a una subred delegada (snet-app).
  • Entrada: la API de Disponibilidad no debería ser accesible desde internet, sino solo desde la red. Eso se resuelve con restricciones de acceso o con un punto de conexión privado.
# Integración de salida con una subred delegada a App Service.
az webapp vnet-integration add \
  --resource-group rg-contoso-reservas-pro \
  --name app-contoso-reservas-pro \
  --vnet vnet-contoso-pro \
  --subnet snet-app

Lo dejamos aquí a propósito: el diseño de vnet-contoso-pro, sus subredes, la delegación, los grupos de seguridad de red y los puntos de conexión privados son el contenido completo de la lección 02-05, que viene en dos lecciones.

  1. Limpieza

# Borrar las aplicaciones y el plan (el plan es lo que factura).
az webapp delete --resource-group rg-contoso-reservas-dev --name app-contoso-reservas-dev
az webapp delete --resource-group rg-contoso-reservas-dev --name app-contoso-api-disponibilidad-dev
az appservice plan delete --resource-group rg-contoso-reservas-dev --name plan-contoso-reservas-dev --yes

# Comprobar que no quedan planes facturando en la suscripción.
az appservice plan list --query "[].{Plan:name, Nivel:sku.name, Instancias:sku.capacity, Grupo:resourceGroup}" --output table

Insistimos porque es el error de coste típico: borrar la aplicación no borra el plan. Un plan P1v3 olvidado cuesta más de cien euros al mes sin servir una sola petición.

Errores Comunes y Consejos

  • Creer que se factura la aplicación. Se factura el plan. Detener la aplicación no ahorra nada; borrar el plan (o bajarlo a Free), sí.
  • Borrar aplicaciones y dejar el plan vivo. Es la factura fantasma más habitual de App Service.
  • No escuchar en process.env.PORT. Provoca «Application Error» sin explicación. Es el fallo número uno al desplegar Node o Python por primera vez.
  • Elegir mal el sistema operativo del plan. Linux y Windows no se mezclan y el plan no se convierte: hay que crear otro.
  • Meter secretos en los ajustes de aplicación en producción. Cualquier colaborador los lee en claro. Usa referencias a Key Vault (04-03) con identidad administrada (04-02).
  • Cambiar ajustes uno a uno en producción. Cada cambio reinicia la aplicación. Agrúpalos.
  • Intentar despliegue sin caídas en nivel Basic. No hay ranuras hasta Standard. Es la razón principal para subir de nivel.
  • Olvidar marcar como «de ranura» los ajustes específicos del entorno. Al intercambiar, la configuración de preproducción acaba en producción. Sucede, y duele.
  • Hacer pruebas de carga contra una ranura del plan de producción. Comparten CPU: perjudicas a los clientes reales.
  • Dejar Always On desactivado en producción. El primer cliente tras un rato de calma paga el arranque en frío.
  • Consejo: activa --https-only true y --min-tls-version 1.2 en todas las aplicaciones, incluidas las de desarrollo. Cuesta dos comandos y evita una observación en la revisión de seguridad del módulo 4.
  • Consejo: nombra las aplicaciones con prefijo de organización (app-contoso-…). El nombre es global y los genéricos ya están tomados.

Ejercicios

Ejercicio 1: decidir planes y niveles

Contoso tiene cuatro aplicaciones: la web Contoso Reservas (pública, tráfico alto y estacional), la API de Disponibilidad (interna, tráfico alto en el pico), el panel de operaciones del personal de tierra (interno, 40 usuarios, horario de oficina) y un portal de pruebas que Diego usa para demostraciones.

  1. ¿Cuántos planes de App Service crearías y qué aplicaciones pondrías en cada uno? Justifícalo.
  2. ¿Qué nivel asignarías a cada plan y qué desbloquea ese nivel?
  3. ¿Qué configuración añadirías al plan de producción para cumplir la política de Contoso sobre componentes críticos?

Ejercicio 2: despliegue sin caídas

Escribe la secuencia completa de comandos de Azure CLI para publicar la versión 1.5.0 de Contoso Reservas sin que ningún cliente vea un error:

  1. Crear la ranura preproduccion clonando la configuración de producción.
  2. Marcar ENTORNO como ajuste que no viaja en el intercambio.
  3. Configurar el calentamiento contra /salud.
  4. Desplegar el ZIP en la ranura y verificarla.
  5. Enviar el 10 % del tráfico real a la ranura durante la validación.
  6. Intercambiar y explicar cómo revertirías si algo va mal.

Ejercicio 3: diagnosticar una aplicación que no arranca

Diego despliega la API de Disponibilidad y https://app-contoso-api-disponibilidad-dev.azurewebsites.net devuelve «Application Error». Enumera, en orden, los pasos y comandos que usarías para encontrar la causa, y menciona al menos tres causas probables con su solución.

Soluciones

Solución 1:

  1. Tres planes:
    • plan-contoso-reservas-pro: web de reservas. Se separa porque su tráfico es el más alto y estacional y no queremos que su pico afecte a nada más.
    • plan-contoso-api-pro: API de Disponibilidad. Escala con un perfil distinto (el pico de la API es más agudo que el de la web) y conviene aislarla para que una prueba de carga o un fallo de la web no la degrade.
    • plan-contoso-interno-pro: panel de operaciones y portal de pruebas. Tráfico bajo y previsible; comparten plan porque el coste es el del plan y así solo se paga uno.
  2. Niveles:
    • Los dos planes públicos, Premium v3 (P1v3): ranuras de despliegue, escalado automático, redundancia de zona y mejor rendimiento por instancia.
    • El plan interno, Standard (S1): suficiente para 40 usuarios y ya incluye ranuras y escalado automático.
  3. Redundancia de zona en los planes de producción (disponible en Premium v3), con al menos 2 instancias, para cumplir la política de «componentes críticos en al menos dos zonas de disponibilidad». Además, always-on activado y https-only en todas.

Solución 2:

#!/usr/bin/env bash
set -euo pipefail
GRUPO="rg-contoso-reservas-pro"
APP="app-contoso-reservas-pro"

# 1. Ranura clonando la configuración de producción.
az webapp deployment slot create -g "${GRUPO}" -n "${APP}" \
  --slot preproduccion --configuration-source "${APP}" --output none

# 2. Ajuste que NO viaja en el intercambio.
az webapp config appsettings set -g "${GRUPO}" -n "${APP}" --slot preproduccion \
  --slot-settings ENTORNO=preproduccion --output none

# 3. Calentamiento contra el punto de salud.
az webapp config appsettings set -g "${GRUPO}" -n "${APP}" --slot preproduccion \
  --settings WEBSITE_SWAP_WARMUP_PING_PATH="/salud" \
             WEBSITE_SWAP_WARMUP_PING_STATUSES="200" --output none

# 4. Desplegar y verificar la ranura.
az webapp deploy -g "${GRUPO}" -n "${APP}" --slot preproduccion \
  --src-path contoso-reservas-1.5.0.zip --type zip
curl -s "https://${APP}-preproduccion.azurewebsites.net/salud"

# 5. Despliegue progresivo: 10 % del tráfico real.
az webapp traffic-routing set -g "${GRUPO}" -n "${APP}" --distribution preproduccion=10
# ... vigilar errores y latencia ...
az webapp traffic-routing clear -g "${GRUPO}" -n "${APP}"

# 6. Intercambio.
az webapp deployment slot swap -g "${GRUPO}" -n "${APP}" \
  --slot preproduccion --target-slot production

Reversión: repetir el mismo comando de intercambio. Tras el primer swap, la versión 1.4.0 quedó en preproduccion, viva y calentada, así que volver atrás cuesta segundos y no requiere volver a desplegar nada.

Solución 3:

# 1. Lo primero, siempre: la secuencia de registro en vivo.
az webapp log config -g rg-contoso-reservas-dev -n app-contoso-api-disponibilidad-dev \
  --application-logging filesystem --level information --output none
az webapp log tail -g rg-contoso-reservas-dev -n app-contoso-api-disponibilidad-dev

# 2. Comprobar la configuración de arranque y la pila.
az webapp config show -g rg-contoso-reservas-dev -n app-contoso-api-disponibilidad-dev \
  --query "{Inicio:appCommandLine, Pila:linuxFxVersion, AlwaysOn:alwaysOn}" -o json

# 3. Verificar que el artefacto está donde debe.
az webapp ssh -g rg-contoso-reservas-dev -n app-contoso-api-disponibilidad-dev
# dentro: ls -la /home/site/wwwroot

# 4. Revisar los ajustes (¿falta alguna variable que la aplicación exige?).
az webapp config appsettings list -g rg-contoso-reservas-dev \
  -n app-contoso-api-disponibilidad-dev -o table

Tres causas probables y su solución:

Causa Síntoma en el registro Solución
La aplicación escucha en un puerto fijo El contenedor no responde y App Service lo reinicia en bucle Escuchar en process.env.PORT (o server.port=${PORT} en Java)
Falta el comando de arranque o el nombre del artefacto no es el esperado «no se encontró el punto de entrada» az webapp config set --startup-file "java -jar /home/site/wwwroot/app.jar"
Falta una variable de entorno obligatoria (por ejemplo, la cadena de conexión) Excepción al inicializar el contexto de la aplicación Añadirla con az webapp config appsettings set (o referencia a Key Vault en producción)

Conclusión

Ya sabes publicar aplicaciones en Azure sin administrar servidores. Entiendes qué es App Service y por qué Contoso pone ahí Contoso Reservas y la API de Disponibilidad mientras el motor heredado se queda en una VM. Dominas el concepto que más dinero cuesta ignorar: se factura el plan, no la aplicación, con su tabla de niveles y lo que desbloquea cada salto —Basic para tener dominio propio y TLS, Standard para ranuras y escalado automático, Premium v3 para redundancia de zona—. Sabes elegir pila de tiempo de ejecución, crear plan y aplicaciones con CLI, y desplegar código con ZIP deploy y Git, con las tres trampas del primer despliegue resueltas (puerto, interfaz y comando de arranque). Configuras la aplicación con ajustes y cadenas de conexión como variables de entorno, y sabes por qué los secretos de producción van a Key Vault por referencia. Has puesto un dominio propio con certificado gestionado y renovación automática, has publicado sin caídas con ranuras, calentamiento, despliegue progresivo y reversión inmediata, sabes escalar el plan vertical, horizontal y automáticamente, y tienes las herramientas de diagnóstico (registro en vivo, descarga de registros, SSH y Kudu) para cuando algo no arranque.

Queda un cabo por atar, y es un cabo con nombre. Tu aplicación no puede guardar nada en local: ni la sesión, ni los ficheros, ni las tarjetas de embarque en PDF que Contoso genera cada vez que alguien compra un billete. En el módulo 1 creaste sttarjetascontosodev y su contenedor tarjetas-embarque, y dejaste una decisión aplazada: qué redundancia usar en desarrollo y cuál en producción.

La siguiente lección, Azure Storage: blobs, archivos, colas y tablas, recoge ese cabo suelto y lo resuelve del todo: los cuatro servicios de una cuenta de almacenamiento, los tipos de blob y los niveles de acceso Hot, Cool, Cold y Archive con sus reglas de ciclo de vida —aplicados a unas tarjetas de embarque que se descargan mucho la primera semana y casi nunca después—, la tabla completa de redundancia LRS, ZRS, GRS, GZRS y RA-GRS con la decisión definitiva de Contoso, y cómo dar acceso seguro a un fichero mediante firmas de acceso compartido temporales en lugar de repartir la clave de la cuenta.

Curso de Azure

Módulo 1: Introducción a Azure

Módulo 2: Servicios principales de Azure

Módulo 3: Bases de datos de Azure

Módulo 4: Seguridad en Azure

Módulo 5: Azure DevOps

Módulo 6: Servicios avanzados de Azure

Módulo 7: Monitoreo y gestión

Módulo 8: Gestión y optimización de costos

Módulo 9: Estudios de caso y mejores prácticas

© Copyright 2026. Todos los derechos reservados