AZ-104
Deep Dive
Los ARM Templates y Bicep son las herramientas nativas de Azure para Infrastructure as Code. El AZ-104 evalúa la estructura de templates, modos de despliegue, funciones clave y la diferencia entre Complete e Incremental — una trampa frecuente.
Contenido
Azure ofrece varias opciones para IaC. El AZ-104 se centra en las herramientas nativas de Azure.
| Herramienta | Formato | Nativo Azure? | Cuándo usar |
|---|---|---|---|
| ARM Templates | JSON | ✅ Sà | Estándar histórico. Verboso pero completo. Aparece en examen AZ-104. |
| Bicep | Bicep (DSL) | ✅ Sà (transpila a ARM) | Sintaxis simplificada de ARM. Microsoft lo recomienda sobre JSON. |
| Terraform | HCL | ⌠Terceros | Multi-cloud. No evaluado en AZ-104 pero común en empresas. |
| Azure CLI / PowerShell | Imperativo | ✅ Sà | Scripts de automatización. No declarativo — no gestiona estado. |
Un ARM template es un archivo JSON que describe los recursos a desplegar. Tiene secciones obligatorias y opcionales. Solo $schema, contentVersiony resources son obligatorios.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": { // Valores configurables en deploy-time
"storageAccountName": {
"type": "string",
"defaultValue": "mystorage",
"minLength": 3,
"maxLength": 24,
"metadata": { "description": "Storage account name" }
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
}
},
"variables": { // Valores calculados internamente
"storageAccountId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
},
"functions": [], // Funciones UDF (raro en examen)
"resources": [ // Recursos a desplegar (obligatorio)
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "[parameters('storageAccountName')]",
"location": "[parameters('location')]",
"sku": { "name": "Standard_LRS" },
"kind": "StorageV2",
"properties": {
"supportsHttpsTrafficOnly": true
},
"dependsOn": [] // Dependencias explÃcitas
}
],
"outputs": { // Valores retornados tras el deploy
"storageEndpoint": {
"type": "string",
"value": "[reference(parameters('storageAccountName')).primaryEndpoints.blob]"
}
}
}$schema✅URL del esquema JSON que valida el template. Diferente para subscription vs RG scope.
contentVersion✅Versión semántica del template. Convención: "1.0.0.0". No tiene funcionalidad automática.
resources✅Array de recursos a crear. Puede estar vacÃo [] pero debe existir.
parametersâŒValores configurables en deploy time. Pueden tener defaultValue, allowedValues, min/maxLength.
variablesâŒValores calculados internamente. No configurables desde fuera — solo son útiles dentro del template.
outputsâŒValores retornados al finalizar el deploy. Útiles para pasar datos entre templates (linked templates).
Las funciones ARM se usan dentro de expresiones [...] para construir valores dinámicamente. Son fundamentales para templates reutilizables.
| Función | Uso | Ejemplo |
|---|---|---|
| parameters('name') | Accede al valor de un parámetro | [parameters('location')] |
| variables('name') | Accede al valor de una variable | [variables('storageId')] |
| resourceGroup() | Propiedades del RG actual (name, location, id) | [resourceGroup().location] |
| subscription() | Propiedades de la suscripción (id, tenantId, etc.) | [subscription().subscriptionId] |
| resourceId(type, name) | ID único de un recurso Azure | [resourceId('Microsoft.Storage/storageAccounts', 'mystorage')] |
| reference(name) | Propiedades en runtime de un recurso desplegado | [reference('mystorage').primaryEndpoints.blob] |
| concat(a, b, c) | Concatena strings o arrays | [concat('storage', uniqueString(resourceGroup().id))] |
| uniqueString(seed) | Genera string único de 13 chars basado en seed | [uniqueString(resourceGroup().id)] |
| toLower(str) | Convierte string a minúsculas | [toLower(parameters('name'))] |
| format(template, args) | Formatea un string con valores | [format('{0}-storage', parameters('env'))] |
| if(cond, true, false) | Condicional ternario | [if(equals(parameters('env'), 'prod'), 'Premium_LRS', 'Standard_LRS')] |
| copyIndex() | Ãndice actual en un loop de copy | [copyIndex()] |
El modo de despliegue determina cómo Azure maneja los recursos existentes en el RG que NO están en el template. Esta es la trampa más frecuente del AZ-104 sobre ARM Templates.
Incremental (por defecto)
--mode Incremental (default)
Complete
--mode Complete
Ejemplo de trampa de examen
Tienes un RG con VM-A, VM-B y StorageAccount-1. Despliegas un template en modo Completeque solo define VM-A. Resultado: VM-B y StorageAccount-1 son ELIMINADOS automáticamente.
Nested Templates
El template del recurso hijo está embebido inline dentro del template padre bajo properties.template. No requiere URL accesible. Útil para templates pequeños o cuando no tienes almacenamiento externo.
Linked Templates
El template hijo está en una URL accesible (Storage Account, GitHub). Se referencia via properties.templateLink.uri. Permite templates modulares y reutilizables. Requiere que la URL sea accesible durante el deploy.
// Referencia a linked template
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2022-09-01",
"name": "storageDeployment",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "https://mystorageaccount.blob.core.windows.net/templates/storage.json",
"contentVersion": "1.0.0.0"
},
"parameters": {
"storageAccountName": { "value": "[parameters('storageName')]" }
}
}
}Bicep es un DSL (Domain Specific Language) que compila a ARM JSON. Misma API, mismos recursos — solo sintaxis más simple. Azure CLI y Portal pueden desplegar Bicep directamente sin pre-compilar.
| Concepto | ARM JSON | Bicep |
|---|---|---|
| Parámetro | "parameters": { "name": { "type": "string" } } | param name string |
| Variable | "variables": { "id": "valor" } | var id = 'valor' |
| Recurso | "type": "Microsoft.Storage/...", "name": "mystorage" | resource mystorage 'Microsoft.Storage/storageAccounts@2023-01-01' |
| Output | "outputs": { "ep": { "type": "string", "value": "..." } } | output ep string = mystorage.properties.primaryEndpoints.blob |
| Referencia | [reference('name').property] | existingResource.properties.property |
| Módulo (=linked) | Microsoft.Resources/deployments + templateLink | module storage './storage.bicep' = { ... } |
| Loop | "copy": { "name": "loop", "count": 3 } | resource vm 'Microsoft.Compute/virtualMachines@...' = [for i in range(0,3): {...}] |
| Condicional | "condition": "[equals(param, 'prod')]" | resource vm '...' = if (env == 'prod') { ... } |
// Bicep equivalente al ARM JSON del ejemplo anterior
param storageAccountName string = 'mystorage'
param location string = resourceGroup().location
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: storageAccountName
location: location
sku: { name: 'Standard_LRS' }
kind: 'StorageV2'
properties: {
supportsHttpsTrafficOnly: true
}
}
output storageEndpoint string = storageAccount.properties.primaryEndpoints.blob// Azure CLI — despliegues en distintos scopes
# Resource Group scope (más común en AZ-104) az deployment group create \ --resource-group myRG \ --template-file main.bicep \ --parameters storageAccountName=mystorage \ --mode Incremental # Subscription scope az deployment sub create \ --location westeurope \ --template-file main.bicep # Management Group scope az deployment mg create \ --management-group-id myMG \ --location westeurope \ --template-file main.bicep # Tenant scope az deployment tenant create \ --location westeurope \ --template-file main.bicep
// PowerShell equivalente
# Resource Group scope New-AzResourceGroupDeployment ` -ResourceGroupName myRG ` -TemplateFile main.bicep ` -storageAccountName mystorage ` -Mode Incremental # Subscription scope New-AzDeployment ` -Location westeurope ` -TemplateFile main.bicep
What-if — previsualizar cambios
Muestra qué recursos serÃan creados, modificados o eliminados SIN ejecutar el deploy. Esencial para validar antes de un deploy en modo Complete.
az deployment group what-if \
--resource-group myRG \
--template-file main.bicep
+ Create: VM-new (nuevos)
~ Modify: StorageAccount-1 (cambios)
- Delete: OldVM (en modo Complete)
= NoChange: VNet-1 (sin cambios)
Deployment Stacks
Gestión de ciclo de vida de recursos desplegados como grupo. Permite crear, actualizar y eliminar el stack completo como unidad. Evita recursos huérfanos cuando un template se borra.
Complete mode elimina recursos que NO están en el template
Esta es la trampa más frecuente sobre ARM. Si despliegas en modo Complete un template que no incluye todos los recursos del RG, los recursos ausentes son ELIMINADOS. Por defecto el modo es Incremental (no elimina nada que no esté en el template).
Bicep transpila a ARM — misma API, mismos recursos
Bicep NO es un servicio separado ni tiene su propio motor de despliegue. Se compila a ARM JSON y Azure despliega el ARM JSON resultante. La diferencia es solo sintaxis. El AZ-104 puede preguntar si Bicep soporta cierta caracterÃstica de ARM — sÃ, porque es el mismo resultado.
dependsOn es necesario para dependencias implÃcitas no detectables
ARM detecta automáticamente dependencias cuando usas reference() o resourceId() en propiedades. Sin embargo, si la dependencia es externa (ej: script que espera a que esté lista una DB), debes usar dependsOn explÃcito con el nombre del recurso.
parameters() NO funciona en variables de la sección "variables"
Falso — sà funciona. Las variables pueden referenciar parámetros. Lo que NO puede hacer una variable es referenciar otra variable que ya depende de ella (referencia circular). parameters() en variables es un patrón legÃtimo y común.
Linked templates requieren URL accesible durante el deploy
El ARM engine descarga el template vinculado durante la ejecución del deploy. Si el archivo está en un Storage Account privado, el deploy fallará. Solución: usar una SAS URL temporal o alojar en GitHub público / Template Specs.
¿Entendiste este tema?
Pon a prueba lo que acabas de aprender
Un administrador despliega un ARM template en modo Complete sobre un Resource Group que contiene: VM-A, VM-B, VNet-1 y StorageAccount-1. El template solo define VM-A y VNet-1. ¿Cuál es el resultado?