Hay una asimetría incómoda en lo que Contoso Airlines ha construido hasta aquí. El código de la web de reservas está versionado, revisado por dos personas, compilado y probado en cada cambio, empaquetado con versión semántica y desplegado con aprobaciones y reversión en segundos. La plataforma sobre la que corre ese código —vnet-contoso-pro con sus cinco subredes, app-contoso-reservas-pro con su ranura y su plan Premium v3, sql-contoso-reservas-pro con su punto privado, kv-contoso-pro, el WAF, las políticas de gobierno— existe únicamente porque alguien tecleó una vez los comandos correctos. No hay historia, no hay revisión, no hay reversión y no hay manera de recrearla en North Europe si West Europe desaparece.

Esta lección cierra esa brecha y, con ella, el módulo. Bicep es el lenguaje de infraestructura como código nativo de Azure: describes el estado deseado de tus recursos en ficheros de texto, los versionas junto al resto del código y dejas que Azure Resource Manager —el mismo ARM del módulo 1— se encargue de que la realidad coincida con la descripción.

Contenido

  1. Declarativo frente a imperativo, e idempotencia
  2. El panorama de herramientas y por qué Bicep
  3. Sintaxis de Bicep desde cero
  4. La plantilla de Contoso paso a paso
  5. Módulos y registro privado
  6. Bucles, condicionales y ámbitos de despliegue
  7. Desplegar: --what-if, modos y validación
  8. El pipeline de infraestructura
  9. Estado, secretos, desviación y decompilación
  10. Errores Comunes y Consejos
  11. Ejercicios
  12. Conclusión

  1. Declarativo frente a imperativo, e idempotencia

Imperativo (la CLI de los módulos 1-4) Declarativo (Bicep)
Qué escribes Los pasos: crea, luego configura, luego conecta El resultado: esto es lo que debe existir
Ejecutar dos veces Falla, o duplica, salvo que tú lo controles No pasa nada: ya está como debe estar
Estado desconocido Hay que averiguarlo con if y consultas Lo calcula la plataforma
Legibilidad Se lee la historia, no el resultado Se lee el resultado

La propiedad que hace útil lo declarativo es la idempotencia: aplicar la misma plantilla una vez o cien veces produce exactamente el mismo estado final. Eso cambia la naturaleza del despliegue de infraestructura. Ya no es «una operación arriesgada que hay que ejecutar en el orden correcto y una sola vez», sino «una comprobación de que la realidad coincide con lo escrito», que se puede lanzar cuantas veces haga falta. Es la misma propiedad que hacía seguro aquel script bash idempotente de 01-06, ahora garantizada por la plataforma en lugar de por tu cuidado.

  1. El panorama de herramientas y por qué Bicep

ARM (JSON) Bicep Terraform Pulumi
Lenguaje JSON verboso DSL propio, conciso HCL Lenguajes generales (C#, Python, TS)
Alcance Solo Azure Solo Azure Multinube Multinube
Estado Lo guarda Azure Lo guarda Azure Fichero de estado propio que hay que custodiar Servicio propio o autogestionado
Servicios de Azure el día 1 Sí Sí Con retraso del proveedor Con retraso
Curva de aprendizaje Alta Baja Media Baja si ya programas
Soporte de Microsoft Sí Sí, es la recomendación No es de Microsoft No es de Microsoft

Bicep no es un producto distinto de ARM: compila a ARM en JSON. Es una capa de sintaxis, lo que significa que no añade ninguna capa de traducción con retraso y que cualquier recurso de Azure está disponible el día que se publica.

La elección honesta: si tu organización usa varias nubes, Terraform es la respuesta razonable y su ecosistema es enorme. Si estás solo en Azure, Bicep gana en tres cosas concretas —no hay fichero de estado que custodiar, bloquear ni corromper; los servicios nuevos están disponibles inmediatamente; y el soporte de Microsoft es de primera parte—. Contoso está solo en Azure y elige Bicep. Las plantillas ARM en JSON siguen existiendo como formato intermedio, pero ya nadie las escribe a mano.

  1. Sintaxis de Bicep desde cero

// PARAMETROS: lo que cambia entre entornos, con tipo, decoradores y valor por defecto
@description('Entorno de destino, que gobierna nombres y tamanos')
@allowed(['dev', 'pro'])
param entorno string
param ubicacion string = resourceGroup().location   // Hereda la del grupo de recursos
@secure()                                           // No se registra en el historial
param contrasenaAdministrador string

// VARIABLES: valores calculados, no parametrizables desde fuera
var etiquetasComunes = {
  entorno: entorno
  proyecto: 'contoso-reservas'
  'centro-coste': 'CC-1042'
  propietario: '[email protected]'
}
// Interpolacion y uniqueString, para un nombre global unico y DETERMINISTA
var nombreAlmacen = 'stcontoso${entorno}${uniqueString(resourceGroup().id)}'

// RECURSO: tipo@version-de-api, nombre simbolico local, y sus propiedades
resource almacen 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: nombreAlmacen
  location: ubicacion
  tags: etiquetasComunes
  sku: { name: entorno == 'pro' ? 'Standard_ZRS' : 'Standard_LRS' }
  kind: 'StorageV2'
  properties: {
    minimumTlsVersion: 'TLS1_2'
    allowBlobPublicAccess: false      // Politica sin-blobs-publicos (04-06)
    supportsHttpsTrafficOnly: true    // Politica solo-https
    allowSharedKeyAccess: false       // Decision de Contoso: solo identidad administrada
  }
}

output puntoConexionBlob string = almacen.properties.primaryEndpoints.blob

Los cuatro decoradores que más se usan: @description documenta el parámetro y aparece en la ayuda; @allowed restringe los valores admitidos y falla en validación, antes de tocar nada; @secure() marca un parámetro como secreto para que no se registre en el historial de despliegues; y @minValue/@maxValue acotan los numéricos. Las funciones habituales son resourceGroup() y subscription() para leer el contexto, uniqueString() para generar sufijos deterministas —el mismo grupo de recursos produce siempre el mismo sufijo, lo que preserva la idempotencia— y la interpolación ${} para componer nombres.

  1. La plantilla de Contoso paso a paso

Ampliamos el fichero anterior con el plan y la aplicación. Lo importante es la dependencia implícita:

var sufijo = entorno == 'pro' ? 'pro' : 'dev'

resource plan 'Microsoft.Web/serverfarms@2023-12-01' = {
  name: 'plan-contoso-reservas-${sufijo}'
  location: ubicacion
  tags: etiquetasComunes
  sku: {
    name: entorno == 'pro' ? 'P1v3' : 'B1'      // Premium v3 solo en produccion
    capacity: entorno == 'pro' ? 3 : 1
  }
  properties: {
    reserved: true                              // Linux
    zoneRedundant: entorno == 'pro'             // Redundancia de zona en produccion
  }
}

resource app 'Microsoft.Web/sites@2023-12-01' = {
  name: 'app-contoso-reservas-${sufijo}'
  location: ubicacion
  tags: etiquetasComunes
  identity: { type: 'SystemAssigned' }          // Identidad administrada (04-02)
  properties: {
    // Al referenciar plan.id, Bicep DEDUCE que el plan debe crearse antes:
    // esa es la dependencia implicita, y hace innecesario cualquier dependsOn.
    serverFarmId: plan.id
    httpsOnly: true
    siteConfig: {
      linuxFxVersion: 'DOTNETCORE|8.0'
      healthCheckPath: '/salud'                 // El punto de salud de 05-04
      appSettings: [
        // El VALOR del secreto no esta aqui: solo una referencia al almacen
        { name: 'PasarelaPagoClave', value: '@Microsoft.KeyVault(SecretUri=${uriSecretoPasarela})' }
      ]
    }
  }
}

// La ranura de preproduccion que 05-04 intercambia: solo existe en produccion
resource ranura 'Microsoft.Web/sites/slots@2023-12-01' = if (entorno == 'pro') {
  parent: app                                   // Relacion padre-hijo explicita
  name: 'preproduccion'
  location: ubicacion
  properties: { serverFarmId: plan.id }
}

Los valores por entorno se separan en ficheros .bicepparam, que sustituyen a los antiguos ficheros de parámetros JSON:

// pro.bicepparam
using './main.bicep'          // Vinculado a la plantilla: los errores se detectan al validar

param entorno = 'pro'
param ubicacion = 'westeurope'

Una plantilla, tantos ficheros de parámetros como entornos. Esa es la respuesta a «recrear la plataforma en otra región»: cambiar una línea del fichero de parámetros.

  1. Módulos y registro privado

Un fichero único con toda la plataforma sería ingobernable. Un módulo es simplemente otro fichero .bicep invocado desde el principal:

// 'name' identifica el despliegue anidado en el historial del grupo de recursos
module red 'modulos/modulo-red.bicep' = {
  name: 'despliegue-red'
  params: { entorno: entorno, ubicacion: ubicacion, espacioDirecciones: '10.20.0.0/16' }
}

module aplicacion 'modulos/modulo-app.bicep' = {
  name: 'despliegue-app'
  params: {
    entorno: entorno
    // Consumir una salida del otro modulo crea la dependencia implicita
    idSubredIntegracion: red.outputs.idSubredIntegracionApp
  }
}

Cuando los módulos se comparten entre equipos, dejan de vivir en el repositorio y pasan a un registro privado, que en Azure es el Azure Container Registry —el mismo servicio que aloja imágenes de contenedor, tema de 06-01—:

az bicep publish --file modulos/modulo-red.bicep \
  --target br:acrcontosopro.azurecr.io/bicep/modulo-red:v1.2.0
// Consumirlo por su version exacta, igual que un paquete de 05-05
module red 'br:acrcontosopro.azurecr.io/bicep/modulo-red:v1.2.0' = {
  name: 'despliegue-red'
  params: { entorno: entorno, ubicacion: ubicacion }
}

Es la misma idea que las plantillas de pipeline ancladas a etiqueta (05-03) y que los paquetes versionados (05-05): la pieza compartida tiene versión y quien la consume decide cuándo actualizar.

  1. Bucles, condicionales y ámbitos de despliegue

// BUCLE: las cinco subredes de vnet-contoso-pro, generadas desde una lista
var subredes = [
  { nombre: 'snet-web',             prefijo: '10.20.1.0/24' }
  { nombre: 'snet-app',             prefijo: '10.20.2.0/24' }
  { nombre: 'snet-datos',           prefijo: '10.20.3.0/24' }
  { nombre: 'snet-gestion',         prefijo: '10.20.4.0/24' }
  { nombre: 'snet-integracion-app', prefijo: '10.20.5.0/24' }
]

resource red 'Microsoft.Network/virtualNetworks@2023-11-01' = {
  name: 'vnet-contoso-${sufijo}'
  location: ubicacion
  properties: {
    addressSpace: { addressPrefixes: ['10.20.0.0/16'] }
    subnets: [for s in subredes: {
      name: s.nombre
      properties: { addressPrefix: s.prefijo }
    }]
  }
}

// CONDICIONAL: el punto privado de SQL solo existe en produccion
resource puntoPrivado 'Microsoft.Network/privateEndpoints@2023-11-01' = if (entorno == 'pro') {
  name: 'pe-sql-reservas'
  location: ubicacion
  properties: { /* subred snet-datos y conexion al servidor */ }
}

No todo se despliega en un grupo de recursos. El ámbito se declara al principio del fichero, y esto es lo que permite llevar a código las políticas del módulo 4:

targetScope = 'subscription'    // Permite crear grupos de recursos y asignar politicas

resource grupoRed 'Microsoft.Resources/resourceGroups@2023-07-01' = {
  name: 'rg-contoso-red-pro'
  location: 'westeurope'
  tags: etiquetasComunes
}

// La iniciativa de gobernanza de 04-06, ahora como codigo revisable
resource asignacion 'Microsoft.Authorization/policyAssignments@2024-04-01' = {
  name: 'base-gobernanza-contoso'
  properties: {
    displayName: 'Base de gobernanza de Contoso'
    policyDefinitionId: idIniciativaGobernanza
    enforcementMode: 'Default'
  }
}

Los cuatro ámbitos posibles son resourceGroup (el predeterminado), subscription, managementGroup —para gobernar mg-contoso y sus hijos— y tenant. Con esto, la iniciativa «Base de gobernanza de Contoso» con sus asignaciones regiones-permitidas, requiere-etiqueta-*, hereda-centro-coste, sin-blobs-publicos, solo-https, tamanos-vm-dev y diagnostico-app-service deja de ser algo que alguien configuró un día y pasa a ser código revisable.

  1. Desplegar: --what-if, modos y validación

COMUN="--resource-group rg-contoso-reservas-pro --template-file main.bicep \
       --parameters pro.bicepparam"

# 1. Validar: comprueba sintaxis, tipos y permisos SIN cambiar nada
az deployment group validate $COMUN

# 2. Vista previa: QUE cambiaria exactamente. El paso que evita destrozos.
az deployment group create $COMUN --what-if

# 3. Desplegar de verdad, en modo incremental (el predeterminado)
az deployment group create $COMUN --mode Incremental \
  --name despliegue-$(date +%Y%m%d-%H%M)

La salida de --what-if marca cada recurso con + Create, ~ Modify, - Delete, = NoChange o ! Deploy. Leerla es obligatorio: es la diferencia entre saber lo que va a pasar y esperar que salga bien.

Y ahora la advertencia más seria de la lección. Los dos modos de despliegue no son equivalentes:

Modo Qué hace con lo que está en el grupo pero no en la plantilla
Incremental (predeterminado) Lo deja intacto. Solo crea o modifica lo declarado
Complete LO BORRA. El grupo de recursos queda exactamente como dice la plantilla

Advertencia: un despliegue en modo Complete sobre rg-contoso-reservas-pro con una plantilla que se olvidó de declarar la base de datos elimina la base de datos, con sus copias de seguridad dependientes. Es la forma más rápida de provocar un desastre irreversible en Azure. Usa Incremental salvo que sepas exactamente lo que haces, ejecuta siempre --what-if antes, y protege los recursos críticos con bloqueos CanNotDelete (01-05), que impiden el borrado incluso en modo completo.

A esto se añade el linter integrado, que avisa de malas prácticas —parámetros sin usar, versiones de API antiguas, secretos en salidas— y que se puede configurar en bicepconfig.json para que ciertos avisos sean errores:

az bicep lint --file main.bicep    # Deberia ejecutarse en el pipeline y bloquear

  1. El pipeline de infraestructura

Aquí converge todo el módulo: el repositorio contoso-infra tiene su propio pipeline, con las mismas garantías que el del código.

name: infra-$(Date:yyyyMMdd)$(Rev:.r)

trigger:
  branches: { include: [ main ] }
  paths:   { include: [ 'infra/*' ] }
pr:
  branches: { include: [ main ] }

pool: { vmImage: ubuntu-latest }

variables:
  - name: comun
    value: '-g rg-contoso-reservas-pro -f infra/main.bicep -p infra/pro.bicepparam'

stages:
  # ETAPA 1: se ejecuta tambien en las solicitudes de cambio
  - stage: validar
    jobs:
      - job: analizar
        steps:
          - script: az bicep lint --file infra/main.bicep
            displayName: Linting de Bicep
          - task: AzureCLI@2
            displayName: Validar y publicar el what-if
            inputs:
              azureSubscription: sc-contoso-infra-pro   # Conexion con MAS permisos
              scriptType: bash
              scriptLocation: inlineScript
              inlineScript: |
                az deployment group validate $(comun)
                # El what-if se guarda como artefacto para que quien revise lo LEA
                az deployment group create --what-if --no-pretty-print $(comun) \
                  > $(Build.ArtifactStagingDirectory)/whatif.txt
          - publish: $(Build.ArtifactStagingDirectory)
            artifact: whatif

  # ETAPA 2: solo desde main, y tras la aprobacion configurada en el entorno
  - stage: desplegar
    dependsOn: validar
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - deployment: aplicar
        environment: produccion-infra    # Aqui espera la aprobacion de Marta Rios
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self
                - task: AzureCLI@2
                  inputs:
                    azureSubscription: sc-contoso-infra-pro
                    scriptType: bash
                    scriptLocation: inlineScript
                    inlineScript: az deployment group create $(comun) --mode Incremental

Fíjate en lo que ha pasado. La infraestructura ha adquirido exactamente las mismas garantías que el código:

graph LR
    A["Cambio en<br/>infra/main.bicep"] --> B["Solicitud de cambios"]
    B --> C["Lint + validate<br/>+ what-if publicado"]
    C --> D{"Revisión de<br/>Contoso-Infraestructura<br/>leyendo el what-if"}
    D -->|aprobada| E["Squash sobre main"]
    E --> F{"Aprobación de Marta<br/>en produccion-infra"}
    F -->|aprobada| G["az deployment group create<br/>--mode Incremental"]

Nótese también que la conexión de servicio es distinta y con más permisos (sc-contoso-infra-pro), en lugar de haber ampliado los de sc-contoso-pro: la que despliega la aplicación sigue sin poder crear redes.

  1. Estado, secretos, desviación y decompilación

Gestión del estado. Terraform mantiene un fichero de estado que registra qué recursos gestiona, y ese fichero hay que almacenarlo, bloquearlo para evitar escrituras simultáneas y protegerlo, porque contiene datos sensibles; si se corrompe o se pierde, recuperarlo es doloroso. Bicep no tiene estado propio: el estado es Azure mismo, consultado a través de ARM en cada despliegue. Es la ventaja operativa más práctica de Bicep, y la contrapartida es que Bicep solo sabe de Azure.

Qué no poner en Bicep. Secretos, nunca. Ni siquiera con @secure(), que solo evita que el valor se registre pero exige que alguien lo pase. La forma correcta es que la plantilla lea el secreto del almacén en tiempo de despliegue:

// Referencia a un Key Vault EXISTENTE, sin crearlo
resource almacenClaves 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
  name: 'kv-contoso-pro'
  scope: resourceGroup('rg-contoso-seguridad-pro')
}

module baseDatos 'modulos/modulo-sql.bicep' = {
  name: 'despliegue-sql'
  params: {
    // getSecret solo puede usarse al pasar un parametro @secure() a un modulo:
    // el valor nunca aparece en el historial de despliegues ni en los registros
    contrasenaAdministrador: almacenClaves.getSecret('sql-admin-clave')
  }
}

Aunque, como sabes desde el módulo 4, la mejor contraseña es la que no existe: la aplicación se autentica con identidad administrada y el almacén solo custodia lo irreductible.

Desviación de la configuración. Es lo que ocurre cuando alguien cambia algo desde el portal para resolver una urgencia y no lo lleva a la plantilla: la realidad y el código dejan de coincidir, y el siguiente despliegue revertirá ese cambio sin avisar —o peor, no lo revertirá y nadie sabrá cuál es la verdad—. Se detecta ejecutando --what-if de forma programada contra producción: si la vista previa muestra modificaciones sin que nadie haya tocado el repositorio, hay desviación. Añadir esa ejecución nocturna al pipeline es el equivalente de infraestructura a la compilación nocturna de 05-03. La prevención es la de siempre: bloqueos de recursos, permisos de solo lectura en producción y la disciplina de que todo cambio pase por el repositorio.

Decompilar lo existente. Toda la plataforma de Contoso está ya creada a mano. El punto de partida se genera automáticamente:

# Exportar la plantilla ARM del grupo de recursos y convertirla a Bicep
az group export --name rg-contoso-reservas-pro > exportado.json
az bicep decompile --file exportado.json

El resultado siempre hay que limpiarlo, y conviene saber por qué: la exportación produce nombres simbólicos ilegibles (resource resource0 ...), incrusta valores literales donde deberían ir parámetros, arrastra propiedades de solo lectura que ARM rechaza al desplegar, fija versiones de API antiguas y contiene identificadores completos codificados a mano. Sirve como borrador para no partir de cero, nunca como resultado final. El criterio práctico: decompila, limpia, ejecuta --what-if y no des la plantilla por buena hasta que el what-if devuelva = NoChange en todo. Ese es el momento en que la plantilla describe fielmente lo que existe.

Errores Comunes y Consejos

  • Desplegar en modo Complete sin leer el what-if. Borra todo lo que no esté en la plantilla. Es el error más caro de esta lección.
  • Poner secretos en la plantilla o en el .bicepparam. Ambos están en el repositorio. getSecret desde kv-contoso-pro, o mejor, identidad administrada y ningún secreto.
  • Un único fichero gigante, o su contrario, dependsOn explícitos por todas partes. Módulos por dominio —red, aplicación, datos, seguridad— y deja que Bicep deduzca las dependencias de las referencias: los dependsOn manuales suelen sobrar y ocultan errores reales de diseño.
  • Aceptar la salida de decompile tal cual. Es un borrador. Sin limpiarla, es menos mantenible que el JSON original.
  • Convivir con la desviación. En cuanto un cambio urgente del portal no vuelve al repositorio, la plantilla deja de ser la verdad y todo el edificio pierde sentido.
  • Nombres no deterministas. Un uniqueString(utcNow()) genera un nombre distinto en cada ejecución y rompe la idempotencia: crea recursos nuevos en lugar de actualizar los existentes.
  • Consejo: ejecuta --what-if de forma programada contra producción; es tu detector de desviación y cuesta unos minutos de agente.
  • Consejo: protege los recursos con datos —bases de datos, almacenes de claves, cuentas de almacenamiento— con bloqueos CanNotDelete. Es la red de seguridad frente a un error de plantilla.

Ejercicios

Ejercicio 1: parametrizar para dos entornos

Escribe el esqueleto de una plantilla para el proyecto de ejercicios Contoso Millas (centro-coste=CC-2077) que despliegue un plan de App Service y una aplicación, sabiendo que: en dev el plan es B1 con una instancia y en pro es P1v3 con dos y redundancia de zona; las etiquetas obligatorias son las cuatro del módulo 1; solo se admiten westeurope y northeurope; y en pro debe existir una ranura preproduccion que en dev no.

  1. Indica qué parámetros, decoradores, variables y condicionales usarías.
  2. ¿Qué va en la plantilla y qué en los ficheros .bicepparam?
  3. ¿Cómo garantizas que nadie despliegue en una región no permitida, y qué mecanismo del módulo 4 lo refuerza?

Ejercicio 2: leer un what-if y decidir

El pipeline de infraestructura muestra esta vista previa antes de desplegar en rg-contoso-reservas-pro:

~ Microsoft.Web/serverfarms/plan-contoso-reservas-pro
    ~ sku.capacity: 3 => 1
= Microsoft.Web/sites/app-contoso-reservas-pro
- Microsoft.Sql/servers/sql-contoso-reservas-pro/databases/db-reservas
+ Microsoft.Storage/storageAccounts/stcontosopro7f2a
  1. Interpreta cada una de las cuatro líneas.
  2. ¿Cuál te haría rechazar la solicitud de cambios inmediatamente, y qué la ha provocado probablemente?
  3. ¿Qué dos mecanismos habrían impedido que ese cambio llegara a ejecutarse?

Ejercicio 3: llevar a código lo creado a mano

Contoso quiere convertir en Bicep el grupo rg-contoso-seguridad-pro, creado a mano en el módulo 4 y que contiene kv-contoso-pro y log-contoso-pro.

  1. Describe el procedimiento completo, del primer comando a la plantilla aprobada.
  2. ¿Cómo sabes que la plantilla describe fielmente lo que ya existe?
  3. Menciona tres defectos concretos que esperas encontrar en la salida de decompile.

Soluciones

Solución 1:

  1. Parámetros: entorno con @allowed(['dev','pro']), ubicacion con @allowed(['westeurope','northeurope']) y valor por defecto resourceGroup().location, y propietario como cadena con @description. Variables: etiquetasComunes con entorno, proyecto: 'contoso-millas', centro-coste: 'CC-2077' y propietario; y sufijo derivado de entorno. Condicionales: el operador ternario para sku.name (entorno == 'pro' ? 'P1v3' : 'B1'), para capacity (2 o 1) y para zoneRedundant (entorno == 'pro'); y if (entorno == 'pro') en el recurso de la ranura, que así no existe en desarrollo.
  2. En la plantilla va todo lo estructural: qué recursos hay, cómo se relacionan y la lógica que traduce el entorno en tamaños. En los .bicepparam van solo los valores que distinguen un entorno de otro: entorno, ubicacion y propietario. La prueba de que la separación es correcta es que desplegar en otra región solo exige cambiar una línea del fichero de parámetros.
  3. En la plantilla, con @allowed, que falla en la fase de validación antes de tocar nada. Pero eso solo protege a quien usa la plantilla: el refuerzo real es la política regiones-permitidas de la iniciativa «Base de gobernanza de Contoso» (04-06), con efecto Deny, que rechaza la creación venga de donde venga, incluido el portal o un az suelto. Es el principio de las dos capas: la plantilla hace fácil lo correcto, la política hace imposible lo incorrecto.

Solución 2:

  1. ~ en el plan: se modifica, bajando la capacidad de 3 instancias a 1. = en la aplicación: sin cambios. - en db-reservas: la base de datos se elimina. +: se crea una cuenta de almacenamiento nueva.
  2. La línea - de db-reservas, sin ninguna duda: es la base de datos de reservas de producción. La causa más probable es un despliegue en modo Complete con una plantilla que no declara la base de datos, o que alguien la ha eliminado del fichero al refactorizar los módulos. La bajada de capacidad de 3 a 1 instancias en producción también merece una pregunta, porque compromete la redundancia de zona y la capacidad en hora punta.
  3. Primero, un bloqueo CanNotDelete sobre la base de datos, que impide el borrado aunque el despliegue lo intente. Segundo, la combinación de revisión obligatoria de Contoso-Infraestructura por la directiva de ruta de 05-02 y la publicación del what-if en la solicitud de cambios, que es precisamente para que un humano lea esta salida antes de aprobar. Como refuerzo, fijar el modo a Incremental explícitamente en el pipeline.

Solución 3:

  1. (a) az group export --name rg-contoso-seguridad-pro > exportado.json y az bicep decompile --file exportado.json. (b) Limpiar: renombrar los símbolos, extraer los valores repetidos a parámetros y variables, eliminar propiedades de solo lectura, actualizar versiones de API y separar en módulos. (c) az bicep lint y az deployment group validate. (d) --what-if hasta que no proponga ningún cambio. (e) Confirmar en contoso-infra con una solicitud de cambios revisada por Contoso-Infraestructura. (f) Ejecutar el pipeline de infraestructura con su aprobación.
  2. Porque el --what-if devuelve = NoChange en todos los recursos. Mientras aparezca cualquier ~ o - que nadie ha pedido, la plantilla no coincide con la realidad. Ese es el criterio objetivo de que la adopción está completa, y es el mismo comando que después detectará la desviación.
  3. (a) Nombres simbólicos ilegibles del tipo resource0, resource1. (b) Identificadores de recurso completos codificados literalmente en lugar de referencias y funciones, lo que impide reutilizar la plantilla en otro entorno o región. (c) Propiedades de solo lectura exportadas que ARM rechaza al desplegar, junto con versiones de API antiguas y valores que deberían ser parámetros —la ubicación, los nombres— incrustados como literales.

Conclusión

Con esta lección se cierra el módulo, y conviene ver cuánto ha cambiado. Entiendes la diferencia entre lo imperativo y lo declarativo, y por qué la idempotencia convierte el despliegue de infraestructura en una comprobación repetible en lugar de una operación de riesgo. Has situado Bicep en el panorama con honestidad —Terraform si hay varias nubes, Bicep si estás solo en Azure, por el estado que no hay que custodiar, los servicios disponibles el día uno y el soporte de primera parte— sabiendo que Bicep compila a ARM y que por eso no llega tarde a nada. Dominas su sintaxis: param con @allowed, @secure y @description, var, resource, output, resourceGroup(), uniqueString() e interpolación; y has construido la plantilla de Contoso paso a paso, desde la cuenta de almacenamiento sola hasta el plan, la aplicación y su ranura, apoyándote en las dependencias implícitas que Bicep deduce solo y separando los valores por entorno en ficheros .bicepparam —la respuesta concreta a «recrearlo todo en North Europe»—.

Sabes componer con módulos y publicarlos versionados en un registro privado de Azure Container Registry, recorrer listas con for, condicionar recursos con if y desplegar en ámbitos de suscripción y grupo de administración para llevar a código las políticas del módulo 4. Sabes desplegar con red: validate, el --what-if que hay que leer siempre, el linter, y la advertencia que no debes olvidar —el modo Complete borra todo lo que no esté en la plantilla, y contra eso están los bloqueos CanNotDelete—. Has montado el pipeline de infraestructura que valida, publica el what-if en la solicitud de cambios para que la revisión sea informada, pide aprobación y aplica, con una conexión de servicio propia y separada de la que despliega la aplicación. Y has cerrado los flancos: Bicep no necesita fichero de estado porque el estado es Azure; los secretos no van en la plantilla sino en kv-contoso-pro leídos con getSecret, o mejor aún no existen porque hay identidad administrada; la desviación de la configuración se caza con un --what-if programado; y lo creado a mano se decompila con az bicep decompile sabiendo que el resultado es un borrador que solo está terminado cuando el what-if dice NoChange en todo.

Recapitulando el módulo entero: Contoso Airlines entró con Diego mandando un ZIP por correo y Marta pegando comandos un viernes por la noche, y sale con un ciclo completo y automatizado. El trabajo se planifica y se rastrea en Boards, con cada confirmación atada a su elemento de trabajo. El código vive en Repos con ramas cortas, revisión obligatoria y directivas que nadie —tampoco un administrador— puede saltarse. Cada cambio se compila, se prueba y se analiza en Pipelines, produciendo un artefacto único que se construye una vez y se despliega muchas. Ese artefacto recorre los entornos desarrollo, preproduccion y produccion con aprobaciones, ventanas de despliegue y un intercambio de ranura que hace la reversión cuestión de segundos. Las piezas compartidas se distribuyen versionadas desde Artifacts, con la cadena de suministro protegida. Y la plataforma entera —redes, aplicaciones, bases de datos, políticas incluidas— es ahora código revisable, versionado y repetible. Las métricas DORA con las que empezó el módulo tienen por fin un camino real de mejora.

Lo que viene ahora ya no es sobre cómo se entrega el software, sino sobre qué se entrega. La plataforma de Contoso está bien construida, pero sigue apoyada en las piezas del módulo 2, y una de ellas destaca: el motor de disponibilidad vive todavía en vm-motor-disponibilidad-dev, una máquina virtual que alguien tiene que parchear, dimensionar y vigilar, y que en producción se resolvió multiplicando instancias en un conjunto de escalado. En el módulo 6, Servicios avanzados de Azure, empezarás precisamente por ahí: empaquetarás ese motor heredado en un contenedor, lo publicarás en Azure Container Registry y lo llevarás a Container Apps, y desde ese punto de partida la plataforma dará un salto —orquestación con Azure Kubernetes Service, funciones sin servidor con Azure Functions, automatización de integraciones con Logic Apps, arquitecturas basadas en mensajes y eventos con Service Bus, Event Grid y Event Hubs, y por último los servicios de Azure AI que permitirán a Contoso ofrecer cosas que hoy no puede—. La entrega ya está resuelta; ahora toca modernizar lo que se entrega.

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