Pular para o conteúdo principal

Fundamentação Teórica: Interpret an Azure Resource Manager template or a Bicep file


1. Intuição Inicial​

Imagine que você precisa montar exatamente o mesmo escritório em dez cidades diferentes: mesmo mobiliário, mesma disposição, mesmos equipamentos. Você poderia ir cidade por cidade montando tudo manualmente. Ou poderia criar uma planta detalhada que descreve exatamente como o escritório deve ser, e enviar essa planta para todas as dez cidades ao mesmo tempo.

Um ARM Template ou um arquivo Bicep é essa planta do escritório. É um documento que descreve declarativamente quais recursos Azure devem existir, com quais configurações, em qual ordem e com quais dependências. Em vez de clicar no portal ou executar comandos imperativos um por um, você declara o estado desejado da infraestrutura e o Azure se encarrega de criar, modificar ou deletar recursos para atingir esse estado.

A diferença central entre o pensamento imperativo e o declarativo é: imperativo diz "faça isso, depois faça aquilo, depois faça aquilo outro". Declarativo diz "o resultado final deve ser este". O Azure descobre como chegar lá.


2. Contexto​

ARM Templates e Bicep são os mecanismos nativos do Azure para Infrastructure as Code (IaC). Eles operam diretamente sobre a camada do Azure Resource Manager (ARM), que é a camada de orquestração que processa todas as operações no Azure, seja via portal, CLI, PowerShell ou API.

100%
Scroll para zoom · Arraste para mover · 📱 Pinch para zoom no celular

O Bicep é uma linguagem de domínio específico (DSL) que compila para ARM Template JSON. Toda a funcionalidade do Bicep está disponível em ARM, mas o Bicep tem uma sintaxe muito mais legível e concisa. O AZ-104 exige que você saiba ler e interpretar ambos.


3. Construção dos Conceitos​

3.1 A Estrutura de um ARM Template​

Um ARM Template é um arquivo JSON com uma estrutura bem definida. Cada seção tem um propósito específico:

{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"functions": [],
"resources": [],
"outputs": {}
}
SeçãoObrigatórioPropósito
$schemaSimURL que define a versão do schema do template
contentVersionSimVersão do template definida pelo autor (string livre)
parametersNãoValores de entrada fornecidos no momento do deploy
variablesNãoValores calculados internamente, reutilizados no template
functionsNãoFunções customizadas do template (raramente usado)
resourcesSimOs recursos Azure que serão criados ou modificados
outputsNãoValores retornados após o deploy (ex: IP de uma VM, URL de um app)

3.2 A Seção Parameters​

Parâmetros tornam o template reutilizável. Em vez de hardcodar "brazilsouth" como localização, você define um parâmetro location e passa o valor no momento do deploy.

"parameters": {
"location": {
"type": "string",
"defaultValue": "brazilsouth",
"allowedValues": [
"brazilsouth",
"eastus",
"westeurope"
],
"metadata": {
"description": "Regiao Azure para deploy dos recursos"
}
},
"storageAccountName": {
"type": "string",
"minLength": 3,
"maxLength": 24,
"metadata": {
"description": "Nome da storage account (deve ser globalmente unico)"
}
},
"adminPassword": {
"type": "securestring",
"metadata": {
"description": "Senha do administrador - valor seguro, nao aparece em logs"
}
}
}

Tipos de parâmetro disponíveis: string, securestring, int, bool, object, secureObject, array.

O tipo securestring e secureObject são usados para valores sensíveis: o valor não é logado nem aparece no histórico de deploy.

3.3 A Seção Variables​

Variáveis são valores calculados uma vez e reutilizados. Elas ajudam a evitar repetição e tornam o template mais legível.

"variables": {
"storageAccountSku": "Standard_LRS",
"vnetAddressPrefix": "10.0.0.0/16",
"subnetFrontendPrefix": "10.0.1.0/24",
"uniqueSuffix": "[uniqueString(resourceGroup().id)]",
"storageNameWithSuffix": "[concat(parameters('storageAccountName'), variables('uniqueSuffix'))]"
}

A notação [...] (colchetes) indica uma expressão ARM: um valor calculado em runtime. A diferença entre parâmetros e variáveis é que parâmetros recebem valores de fora (no deploy); variáveis são calculadas internamente.

3.4 Funções ARM​

O ARM Template tem um conjunto rico de funções que podem ser usadas em expressões:

CategoriaFunções principaisExemplo
Stringconcat, toLower, toUpper, substring, length, trimconcat('storage', uniqueString(resourceGroup().id))
ResourceresourceGroup, subscription, resourceId, referenceresourceGroup().location
Arraylength, first, last, concat, unionlength(parameters('allowedIPs'))
Mathadd, sub, mul, div, modadd(parameters('instanceCount'), 1)
Logicalif, and, or, not, boolif(equals(parameters('env'), 'prod'), 'Premium', 'Standard')
Deploymentdeployment, environmentdeployment().name
UniqueuniqueString, newGuiduniqueString(resourceGroup().id)

A função uniqueString() é muito importante: ela gera um hash determinístico de 13 caracteres a partir do valor de entrada. Se você passa resourceGroup().id, o resultado é sempre o mesmo para o mesmo resource group, tornando-o útil para gerar nomes únicos por resource group.

A função reference() permite ler propriedades de um recurso já criado, incluindo recursos criados no mesmo template. Por exemplo, reference(resourceId('Microsoft.Network/publicIPAddresses', 'meu-ip')).ipAddress retorna o IP atribuído ao IP público após sua criação.

3.5 A Seção Resources: O Coração do Template​

Cada recurso tem uma estrutura obrigatória:

"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "[variables('storageNameWithSuffix')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2",
"properties": {
"accessTier": "Hot",
"minimumTlsVersion": "TLS1_2",
"allowSharedKeyAccess": false
}
}
]
CampoObrigatórioPropósito
typeSimO tipo do recurso Azure (Microsoft.Provider/resourceType)
apiVersionSimA versão da API do resource provider
nameSimO nome do recurso
locationSim (para a maioria)A região onde o recurso é criado
skuVariaO tier de serviço
kindVariaO subtipo do recurso
propertiesVariaConfigurações específicas do recurso
dependsOnNãoDependências explícitas de outros recursos
tagsNãoMetadados de organização

3.6 DependsOn: Controle de Ordem​

O ARM processa recursos em paralelo por padrão. Para garantir que um recurso seja criado antes de outro, use dependsOn:

{
"type": "Microsoft.Compute/virtualMachines",
"name": "minha-vm",
"dependsOn": [
"[resourceId('Microsoft.Network/networkInterfaces', 'nic-minha-vm')]",
"[resourceId('Microsoft.Compute/availabilitySets', 'as-web')]"
],
...
}

Comportamento importante: quando você usa a função reference() ou resourceId() para referenciar outro recurso no template, o ARM infere automaticamente a dependência. dependsOn explícito só é necessário quando a dependência não pode ser inferida via referência direta.

3.7 A Seção Outputs​

Outputs retornam informações sobre os recursos criados para uso posterior (em pipelines, scripts, módulos aninhados):

"outputs": {
"storageAccountEndpoint": {
"type": "string",
"value": "[reference(variables('storageNameWithSuffix')).primaryEndpoints.blob]"
},
"vmPublicIpAddress": {
"type": "string",
"value": "[reference(resourceId('Microsoft.Network/publicIPAddresses', 'pip-minha-vm')).ipAddress]"
}
}

3.8 A Estrutura de um Arquivo Bicep​

O Bicep tem uma sintaxe mais concisa e legível. Veja a comparação:

ARM Template (JSON):

{
"$schema": "...",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string",
"defaultValue": "brazilsouth"
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "minhaconta",
"location": "[parameters('location')]",
"sku": { "name": "Standard_LRS" },
"kind": "StorageV2",
"properties": {
"accessTier": "Hot"
}
}
]
}

Bicep (equivalente):

param location string = 'brazilsouth'

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: 'minhaconta'
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
}
}

A redução de verbosidade é considerável. O Bicep elimina colchetes de expressão, aspas desnecessárias e a estrutura JSON aninhada.

3.9 Elementos Principais do Bicep​

Parâmetros:

@description('Regiao para deploy')
@allowed(['brazilsouth', 'eastus'])
param location string = 'brazilsouth'

@description('Senha segura')
@secure()
param adminPassword string

@minLength(3)
@maxLength(24)
param storageAccountName string

Variáveis:

var uniqueSuffix = uniqueString(resourceGroup().id)
var fullStorageName = '${storageAccountName}${uniqueSuffix}'
var storageSkuName = environment == 'prod' ? 'Standard_GRS' : 'Standard_LRS'

Outputs:

output blobEndpoint string = storageAccount.properties.primaryEndpoints.blob
output storageAccountId string = storageAccount.id

Referência a outros recursos (dependência implícita):

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: fullStorageName
location: location
sku: { name: 'Standard_LRS' }
kind: 'StorageV2'
properties: {}
}

resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
parent: storageAccount // dependencia implícita via 'parent'
name: 'default'
properties: {
deleteRetentionPolicy: {
enabled: true
days: 7
}
}
}

Módulos (equivalente a templates aninhados no ARM):

module vnetModule './modules/vnet.bicep' = {
name: 'vnet-deploy'
params: {
vnetName: 'vnet-producao'
addressPrefix: '10.0.0.0/16'
location: location
}
}

// Acessar output do módulo
output vnetId string = vnetModule.outputs.vnetId

4. Visão Estrutural​

100%
Scroll para zoom · Arraste para mover · 📱 Pinch para zoom no celular

5. Funcionamento na Prática​

O Modo de Deploy: Incremental vs. Complete​

Este é um dos comportamentos mais críticos para entender e que mais causa impactos inesperados:

100%
Scroll para zoom · Arraste para mover · 📱 Pinch para zoom no celular
  • Incremental (padrão): o template define recursos que devem existir. Recursos existentes no resource group mas ausentes no template são preservados.
  • Complete: o template define o estado completo desejado. Recursos existentes no resource group mas ausentes no template são excluídos.

O modo Complete é poderoso para manter o resource group exatamente como definido no template, mas altamente destrutivo se usado sem cuidado.

What-If: Pré-visualização das Mudanças​

O comando --what-if (e --confirm-with-what-if) permite ver exatamente quais mudanças serão feitas antes de executar o deploy:

az deployment group what-if \
--resource-group rg-producao \
--template-file main.bicep \
--parameters @parameters.json

O resultado mostra cada recurso com seu status previsto: + Create, ~ Modify, - Delete, = Nochange, x Ignore.

Escopos de Deploy​

Templates podem ser deployados em diferentes escopos:

EscopoComando CLIO que pode criar
Resource Groupaz deployment group createRecursos dentro do RG especificado
Subscriptionaz deployment sub createResource Groups, recursos em nível de subscription (policies, RBAC)
Management Groupaz deployment mg createPolíticas em nível de MG, RBAC
Tenantaz deployment tenant createConfigurações de tenant, Management Groups

O targetScope no Bicep define o escopo do arquivo:

targetScope = 'subscription'  // ou 'resourceGroup' (padrão), 'managementGroup', 'tenant'

6. Formas de Implementação​

6.1 Portal do Azure: Template Specs e Deploy​

Deploy direto de template no portal: Create a resource > Deploy a custom template

Criar Template Spec (biblioteca de templates reutilizáveis versionados no Azure):

az ts create \
--name template-vnet-padrao \
--resource-group rg-templates \
--version "1.0" \
--template-file vnet.bicep \
--display-name "VNet Padrao Corporativo"

6.2 Azure CLI​

Deploy de ARM Template:

az deployment group create \
--resource-group rg-producao \
--template-file main.json \
--parameters @parameters.prod.json \
--parameters adminPassword="MinhasenhaSegura123" \
--mode Incremental \
--name "deploy-$(date +%Y%m%d%H%M%S)"

Deploy de Bicep (a CLI compila automaticamente):

az deployment group create \
--resource-group rg-producao \
--template-file main.bicep \
--parameters location=brazilsouth \
storageAccountName=minhastorage \
--mode Incremental

Compilar Bicep para ARM Template manualmente:

# Compilar Bicep para ARM JSON
az bicep build --file main.bicep

# Descompilar ARM JSON para Bicep (melhor esforço, pode precisar de ajustes)
az bicep decompile --file main.json

Verificar sintaxe sem deploy:

# Validar template antes de deployar
az deployment group validate \
--resource-group rg-producao \
--template-file main.bicep \
--parameters @parameters.json

Acompanhar o histórico de deploys:

# Listar deploys de um resource group
az deployment group list \
--resource-group rg-producao \
--output table

# Ver detalhes e resultado de um deploy específico
az deployment group show \
--resource-group rg-producao \
--name "deploy-20240315120000"

# Ver outputs de um deploy
az deployment group show \
--resource-group rg-producao \
--name "deploy-20240315120000" \
--query properties.outputs

6.3 PowerShell​

# Deploy de Bicep
New-AzResourceGroupDeployment `
-ResourceGroupName "rg-producao" `
-TemplateFile "./main.bicep" `
-TemplateParameterFile "./parameters.prod.json" `
-Mode Incremental `
-Name "deploy-$(Get-Date -Format 'yyyyMMddHHmmss')" `
-Verbose

# What-If
Get-AzResourceGroupDeploymentWhatIfResult `
-ResourceGroupName "rg-producao" `
-TemplateFile "./main.bicep" `
-TemplateParameterFile "./parameters.prod.json"

7. Controle e Segurança​

Gerenciamento de Segredos em Templates​

Templates não devem conter segredos hardcoded. A prática correta é usar parâmetros securestring e fornecer os valores via:

  1. Parameter file não versionado (.gitignore para arquivos parameters.prod.json com segredos)
  2. Key Vault reference no parameter file:
// parameters.prod.json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"reference": {
"keyVault": {
"id": "/subscriptions/<sub-id>/resourceGroups/rg-secrets/providers/Microsoft.KeyVault/vaults/meu-keyvault"
},
"secretName": "vm-admin-password"
}
}
}
}

Com essa abordagem, o valor da senha nunca aparece no arquivo de parâmetros nem no histórico de deploy.

Locks e Proteção de Recursos em Templates​

Templates podem criar resource locks como qualquer outro recurso:

resource lock 'Microsoft.Authorization/locks@2020-05-01' = {
name: 'lock-storage-producao'
scope: storageAccount
properties: {
level: 'CanNotDelete'
notes: 'Storage de producao protegido contra exclusao acidental'
}
}

8. Tomada de Decisão​

ARM Template vs. Bicep: qual usar?​

SituaçãoEscolhaMotivo
Novo projeto, nova infraestruturaBicepSintaxe mais limpa, melhor DX, tooling moderno
Template existente em ARM JSONManter ARM ou decompileDecompile como ponto de partida, revisar antes de usar
Integração com sistema que só aceita JSONARM TemplateBicep compila para ARM, use o JSON gerado
Módulos e composiçãoBicepMódulos Bicep são muito mais simples que linked templates ARM
Time sem experiência em nenhumBicepCurva de aprendizado menor

Modo Incremental vs. Complete: quando usar?​

SituaçãoModoCuidado
Deploy de novos recursosIncrementalSeguro, preserva o que existe
Atualização de recursos existentesIncrementalSeguro
Garantir que o RG está exatamente como o templateCompletePerigoso: exclui recursos não listados
Cleanup de recursos legadosComplete com revisãoExecute --what-if antes para ver o que será excluído

9. Boas Práticas​

Versione templates junto com o código da aplicação: templates de infraestrutura devem estar no mesmo repositório Git que o código da aplicação que usam, com as mesmas práticas de branching, code review e CI/CD.

Use parâmetros para tudo que muda entre ambientes: localização, SKUs, nomes, contagens de instâncias. Nunca hardcode valores que diferem entre dev, staging e produção.

Prefira uniqueString() para gerar nomes únicos em vez de timestamps: uniqueString(resourceGroup().id) é determinístico para o mesmo resource group, gerando o mesmo resultado em deploys repetidos. Um timestamp geraria um nome diferente a cada deploy, potencialmente criando recursos duplicados.

Use --what-if antes de qualquer deploy em produção: especialmente antes de usar modo Complete ou antes de deploys que modificam recursos críticos. Revise cada mudança prevista antes de confirmar.

Documente parâmetros e outputs: use a propriedade description (em ARM) e o decorator @description() (em Bicep) para documentar o propósito de cada parâmetro e output. Isso facilita o uso do template por outros membros da equipe.


10. Erros Comuns​

Usar modo Complete sem verificar o que será excluído

Um template com 5 recursos é deployado com --mode Complete em um resource group que tem 8 recursos (os 5 do template mais 3 criados manualmente depois). Os 3 recursos adicionais são excluídos silenciosamente. Sempre use --what-if antes de qualquer deploy Complete.

Hardcodar o apiVersion e não atualizar

Uma apiVersion desatualizada pode não suportar as propriedades mais recentes de um recurso, ou pode estar próxima de ser descontinuada. Verifique regularmente a documentação do resource provider para usar versões suportadas e atuais.

Confundir variables com parameters

Parâmetros recebem valores externos no deploy; variáveis são calculadas internamente. Um erro comum é colocar em variáveis valores que deveriam ser parâmetros (perdendo a capacidade de personalizar), ou colocar em parâmetros valores que são sempre iguais (adicionando complexidade desnecessária).

Esperar que dependsOn seja sempre necessário

O ARM infere dependências automaticamente quando você usa reference() ou resourceId() referenciando outro recurso do template. Adicionar dependsOn explícito além das referências implícitas é redundante e aumenta a verbosidade sem benefício. Use dependsOn apenas quando a dependência existe mas não pode ser expressa via referência direta.

Usar securestring mas logar o valor na aplicação

O tipo securestring protege o valor no ARM, mas se a aplicação logar a variável de ambiente que contém o segredo, o valor fica exposto nos logs. A proteção do securestring se limita ao plano de controle do ARM.


11. Operação e Manutenção​

Histórico de Deploys​

Cada deploy cria uma entrada no histórico do resource group, visível por 90 dias. O nome do deploy é importante para rastreabilidade:

# Listar últimos 10 deploys com status
az deployment group list \
--resource-group rg-producao \
--query "[0:10].{Nome:name, Estado:properties.provisioningState, Horario:properties.timestamp}" \
--output table

Limite importante: o histórico de deploys tem um limite de 800 entradas por resource group. Após atingir esse limite, novos deploys falham com erro de limite de histórico. Em ambientes com muitos deploys (CI/CD frequente), configure limpeza automática do histórico ou use nomes de deploy que substituem entradas antigas (nome fixo como deploy-main que é sobrescrito).

Rollback de Deploy​

O ARM não tem rollback automático. Para reverter para um estado anterior, você precisa:

  1. Ter o template e os parâmetros da versão anterior no controle de versão
  2. Fazer um novo deploy com a versão anterior
# Rollback: redeployar versão anterior
git checkout <commit-anterior>
az deployment group create \
--resource-group rg-producao \
--template-file main.bicep \
--parameters @parameters.prod.json

12. Integração e Automação​

Pipeline CI/CD com GitHub Actions​

# .github/workflows/infrastructure.yml
name: Deploy Infrastructure

on:
push:
branches: [main]
paths: ['infrastructure/**']

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

- name: What-If
run: |
az deployment group what-if \
--resource-group rg-producao \
--template-file infrastructure/main.bicep \
--parameters @infrastructure/parameters.prod.json

- name: Deploy
run: |
az deployment group create \
--resource-group rg-producao \
--template-file infrastructure/main.bicep \
--parameters @infrastructure/parameters.prod.json \
--name "deploy-${{ github.run_id }}"

Template Specs: Biblioteca Centralizada​

Template Specs permitem armazenar templates versionados no Azure e reutilizá-los:

100%
Scroll para zoom · Arraste para mover · 📱 Pinch para zoom no celular
// Referenciar um Template Spec em um Bicep
module vnet 'ts:/subscriptions/<sub-id>/resourceGroups/rg-templates/providers/Microsoft.Resources/templateSpecs/vnet-padrao/versions/1.0' = {
name: 'vnet-deploy'
params: {
vnetName: 'vnet-producao'
location: location
}
}

13. Resumo Final​

Pontos essenciais:

  • ARM Templates (JSON) e Bicep são formas de Infrastructure as Code declarativo no Azure. Você define o estado desejado; o ARM decide como chegar lá.
  • O Bicep compila para ARM Template JSON. Todo o Bicep é suportado no ARM, mas Bicep tem sintaxe mais concisa e moderna.
  • A seção resources do template define o que será criado. parameters recebem valores externos. variables calculam valores internos. outputs retornam resultados.
  • O ARM infere dependências automaticamente via reference() e resourceId(). dependsOn explícito só é necessário quando a dependência não pode ser inferida.

Diferenças críticas:

  • Modo Incremental (padrão): recursos do template são criados/atualizados; recursos existentes mas ausentes no template são preservados.
  • Modo Complete: recursos do template são criados/atualizados; recursos existentes mas ausentes no template são excluídos.
  • securestring vs. string: securestring não aparece em logs do ARM; string sim. Use securestring para qualquer valor sensível.
  • variables vs. parameters: variáveis são calculadas internamente e não podem ser alteradas no deploy; parâmetros recebem valores externos e personalizam o template.
  • uniqueString(resourceGroup().id) é determinístico (mesmo resultado para o mesmo RG em deploys repetidos). Um timestamp seria único mas diferente a cada deploy.

O que precisa ser lembrado:

  • A expressão ARM usa colchetes [...] para denotar valores calculados. Tudo fora de colchetes é string literal.
  • --what-if é obrigatório antes de qualquer deploy em produção, especialmente com modo Complete.
  • O histórico de deploys tem limite de 800 entradas por resource group. Em pipelines frequentes, use nomes de deploy fixos ou configure limpeza.
  • Para segredos em parâmetros, use referência ao Key Vault no arquivo de parâmetros em vez de colocar o valor diretamente.
  • O Bicep usa parent: para expressar hierarquia de recursos (ex: blob service dentro de uma storage account), o que cria automaticamente a dependência e o nome hierárquico correto.
  • targetScope no Bicep define se o arquivo deploya em resource group (padrão), subscription, management group ou tenant.