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.
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ção | Obrigatório | Propósito |
|---|---|---|
$schema | Sim | URL que define a versão do schema do template |
contentVersion | Sim | Versão do template definida pelo autor (string livre) |
parameters | Não | Valores de entrada fornecidos no momento do deploy |
variables | Não | Valores calculados internamente, reutilizados no template |
functions | Não | Funções customizadas do template (raramente usado) |
resources | Sim | Os recursos Azure que serão criados ou modificados |
outputs | Não | Valores 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:
| Categoria | Funções principais | Exemplo |
|---|---|---|
| String | concat, toLower, toUpper, substring, length, trim | concat('storage', uniqueString(resourceGroup().id)) |
| Resource | resourceGroup, subscription, resourceId, reference | resourceGroup().location |
| Array | length, first, last, concat, union | length(parameters('allowedIPs')) |
| Math | add, sub, mul, div, mod | add(parameters('instanceCount'), 1) |
| Logical | if, and, or, not, bool | if(equals(parameters('env'), 'prod'), 'Premium', 'Standard') |
| Deployment | deployment, environment | deployment().name |
| Unique | uniqueString, newGuid | uniqueString(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
}
}
]
| Campo | Obrigatório | Propósito |
|---|---|---|
type | Sim | O tipo do recurso Azure (Microsoft.Provider/resourceType) |
apiVersion | Sim | A versão da API do resource provider |
name | Sim | O nome do recurso |
location | Sim (para a maioria) | A região onde o recurso é criado |
sku | Varia | O tier de serviço |
kind | Varia | O subtipo do recurso |
properties | Varia | Configurações especÃficas do recurso |
dependsOn | Não | Dependências explÃcitas de outros recursos |
tags | Não | Metadados 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​
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:
- 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:
| Escopo | Comando CLI | O que pode criar |
|---|---|---|
| Resource Group | az deployment group create | Recursos dentro do RG especificado |
| Subscription | az deployment sub create | Resource Groups, recursos em nÃvel de subscription (policies, RBAC) |
| Management Group | az deployment mg create | PolÃticas em nÃvel de MG, RBAC |
| Tenant | az deployment tenant create | Configuraçõ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:
- Parameter file não versionado (
.gitignorepara arquivosparameters.prod.jsoncom segredos) - 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ção | Escolha | Motivo |
|---|---|---|
| Novo projeto, nova infraestrutura | Bicep | Sintaxe mais limpa, melhor DX, tooling moderno |
| Template existente em ARM JSON | Manter ARM ou decompile | Decompile como ponto de partida, revisar antes de usar |
| Integração com sistema que só aceita JSON | ARM Template | Bicep compila para ARM, use o JSON gerado |
| Módulos e composição | Bicep | Módulos Bicep são muito mais simples que linked templates ARM |
| Time sem experiência em nenhum | Bicep | Curva de aprendizado menor |
Modo Incremental vs. Complete: quando usar?​
| Situação | Modo | Cuidado |
|---|---|---|
| Deploy de novos recursos | Incremental | Seguro, preserva o que existe |
| Atualização de recursos existentes | Incremental | Seguro |
| Garantir que o RG está exatamente como o template | Complete | Perigoso: exclui recursos não listados |
| Cleanup de recursos legados | Complete com revisão | Execute --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:
- Ter o template e os parâmetros da versão anterior no controle de versão
- 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:
// 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
resourcesdo template define o que será criado.parametersrecebem valores externos.variablescalculam valores internos.outputsretornam resultados. - O ARM infere dependências automaticamente via
reference()eresourceId().dependsOnexplÃ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.
securestringvs.string:securestringnão aparece em logs do ARM;stringsim. Usesecurestringpara qualquer valor sensÃvel.variablesvs.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. targetScopeno Bicep define se o arquivo deploya em resource group (padrão), subscription, management group ou tenant.