Fundamentação Teórica: Modify an existing Azure Resource Manager template
1. Intuição Inicial​
No módulo anterior, aprendemos a ler e interpretar ARM Templates e arquivos Bicep. Agora o objetivo é diferente: dado um template existente, como você o modifica de forma segura e eficaz para atender a novos requisitos?
Pense na analogia da planta do escritório novamente. Você tem uma planta que funcionou perfeitamente para criar escritórios em dez cidades. Agora a empresa quer adicionar uma sala de servidor dedicada, mudar o mobiliário de madeira para metal, e tornar o tamanho da sala de reuniões configurável por cidade. Você não refaz a planta do zero: você modifica a existente de forma controlada, testando as mudanças antes de aplicar em todas as cidades.
Modificar um template existente envolve três habilidades complementares: entender o que o template faz hoje, saber quais tipos de mudança têm que impacto nos recursos existentes, e aplicar as mudanças de forma que o deploy seja seguro e idempotente.
2. Contexto​
A modificação de templates é a atividade mais comum no ciclo de vida de infraestrutura como código. Templates raramente são criados e nunca mais tocados. À medida que os requisitos evoluem, o template precisa evoluir com eles. A diferença entre um time maduro e um time iniciante em IaC está justamente em como gerenciam essas mudanças:
O AZ-104 avalia especificamente a capacidade de pegar um template existente e modificá-lo para adicionar recursos, tornar valores parametrizáveis, adicionar outputs ou corrigir configurações.
3. Construção dos Conceitos​
3.1 Tipos de Modificação e Seu Impacto​
Nem toda modificação tem o mesmo impacto. Entender a categoria da mudança é o primeiro passo para avaliar o risco:
Ponto crÃtico sobre remoção: remover um recurso do template em modo Incremental (padrão) não exclui o recurso no Azure. Em modo Complete, o recurso é excluÃdo. Isso significa que o comportamento de uma remoção é completamente diferente dependendo do modo de deploy.
3.2 Propriedades que Causam Recriação de Recursos​
Algumas propriedades de recursos Azure são imutáveis: se você as alterar, o Azure precisa excluir e recriar o recurso. O --what-if mostrará essas mudanças como - Delete seguido de + Create.
Exemplos de propriedades imutáveis que causam recriação:
| Recurso | Propriedade imutável | Impacto |
|---|---|---|
| Storage Account | kind (ex: Storage para StorageV2) | Recriação com perda de dados |
| Cosmos DB | kind, apiProperties.serverVersion | Recriação |
| AKS | agentPoolProfiles[].vmSize | Recriação do node pool |
| VNet | addressSpace (redução) | Bloqueado se há subnets conflitantes |
| App Service Plan | kind (Windows para Linux) | Recriação |
| SQL Server | Não pode alterar nome | Novo recurso |
Antes de modificar propriedades estruturais de recursos crÃticos, sempre execute --what-if para verificar se o ARM sinalizará uma recriação.
3.3 Modificações Comuns: Padrões e Técnicas​
Converter valor hardcoded em parâmetro​
Antes (ARM Template com localização hardcoded):
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "minhaconta",
"location": "brazilsouth",
...
}
]
Depois (com localização parametrizada):
"parameters": {
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Regiao para deploy dos recursos"
}
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "minhaconta",
"location": "[parameters('location')]",
...
}
]
O valor padrão [resourceGroup().location] usa a localização do resource group onde o template é deployado, o que é uma boa prática para evitar problemas de região.
Adicionar um novo recurso ao template​
O cenário mais comum: um template que cria uma storage account precisa ser modificado para também criar um container de blob dentro dela.
Antes:
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "[parameters('storageAccountName')]",
"location": "[parameters('location')]",
"sku": { "name": "Standard_LRS" },
"kind": "StorageV2",
"properties": {}
}
]
Depois (com blob service e container adicionados):
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "[parameters('storageAccountName')]",
"location": "[parameters('location')]",
"sku": { "name": "Standard_LRS" },
"kind": "StorageV2",
"properties": {}
},
{
"type": "Microsoft.Storage/storageAccounts/blobServices",
"apiVersion": "2023-01-01",
"name": "[concat(parameters('storageAccountName'), '/default')]",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
],
"properties": {
"deleteRetentionPolicy": {
"enabled": true,
"days": 7
}
}
},
{
"type": "Microsoft.Storage/storageAccounts/blobServices/containers",
"apiVersion": "2023-01-01",
"name": "[concat(parameters('storageAccountName'), '/default/', parameters('containerName'))]",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccountName'), 'default')]"
],
"properties": {
"publicAccess": "None"
}
}
]
O padrão de nomes para recursos filhos em ARM usa / como separador: storageAccountName/default/containerName.
A mesma modificação em Bicep​
O Bicep torna esse padrão hierárquico muito mais legÃvel com parent:
// Adicionar ao Bicep existente
resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
parent: storageAccount // referência simbólica ao recurso pai
name: 'default'
properties: {
deleteRetentionPolicy: {
enabled: true
days: 7
}
}
}
param containerName string = 'meu-container'
resource blobContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = {
parent: blobService
name: containerName
properties: {
publicAccess: 'None'
}
}
Adicionar um output​
Um template existente que cria uma storage account pode precisar retornar o endpoint para uso em um pipeline:
// ARM: adicionar à seção outputs
"outputs": {
"blobEndpoint": {
"type": "string",
"value": "[reference(parameters('storageAccountName')).primaryEndpoints.blob]"
},
"storageAccountId": {
"type": "string",
"value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
}
}
// Bicep: adicionar ao final do arquivo
output blobEndpoint string = storageAccount.properties.primaryEndpoints.blob
output storageAccountId string = storageAccount.id
Adicionar tags a todos os recursos​
Tags são importantes para organização e custo. Adicionar um parâmetro de tags e aplicar a todos os recursos:
// ARM: adicionar parâmetro
"parameters": {
"tags": {
"type": "object",
"defaultValue": {
"ambiente": "producao",
"projeto": "sistema-x",
"gerenciado-por": "iac"
}
}
},
// Em cada recurso:
"tags": "[parameters('tags')]"
// Bicep: parâmetro com valor padrão
param tags object = {
ambiente: 'producao'
projeto: 'sistema-x'
geradoPor: 'iac'
}
// Em cada recurso, adicionar:
tags: tags
Usar a função if() para recursos condicionais​
Um cenário comum é querer criar um recurso apenas em produção, não em desenvolvimento:
// ARM: condition no recurso
{
"type": "Microsoft.Insights/diagnosticSettings",
"condition": "[equals(parameters('ambiente'), 'prod')]",
"name": "diagnostic-settings",
"apiVersion": "2021-05-01-preview",
"scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]",
"properties": { ... }
}
// Bicep: condition no recurso
param ambiente string = 'dev'
resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (ambiente == 'prod') {
name: 'diag-storage'
scope: storageAccount
properties: { ... }
}
Usar copy para criar múltiplas instâncias (ARM) ou for (Bicep)​
Criar múltiplas subnets em um VNet:
// ARM: copy element
{
"type": "Microsoft.Network/virtualNetworks/subnets",
"apiVersion": "2023-05-01",
"name": "[concat(parameters('vnetName'), '/', parameters('subnets')[copyIndex()].name)]",
"copy": {
"name": "subnetCopy",
"count": "[length(parameters('subnets'))]"
},
"dependsOn": [
"[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]"
],
"properties": {
"addressPrefix": "[parameters('subnets')[copyIndex()].prefix]"
}
}
// Bicep: for loop (muito mais legÃvel)
param subnets array = [
{ name: 'subnet-frontend', prefix: '10.0.1.0/24' }
{ name: 'subnet-backend', prefix: '10.0.2.0/24' }
{ name: 'subnet-database', prefix: '10.0.3.0/24' }
]
resource subnetResources 'Microsoft.Network/virtualNetworks/subnets@2023-05-01' = [for subnet in subnets: {
parent: vnet
name: subnet.name
properties: {
addressPrefix: subnet.prefix
}
}]
4. Visão Estrutural​
O Processo de Modificação Segura​
5. Funcionamento na Prática​
Obter o ARM Template de um Recurso Existente​
Uma das fontes mais práticas de templates para modificar é exportar o template de recursos já existentes no Azure:
Via portal: Resource Group > Export template (exporta todos os recursos do RG) ou [Recurso especÃfico] > Export template
Via CLI:
# Exportar template de um resource group completo
az group export \
--name rg-producao \
--output-format json > template-exportado.json
# Exportar template de um recurso especÃfico
az resource show \
--ids /subscriptions/<sub-id>/resourceGroups/rg-producao/providers/Microsoft.Storage/storageAccounts/minhaconta \
--output json > recurso-exportado.json
Atenção: templates exportados pelo portal frequentemente contêm valores hardcoded, senhas omitidas (substituÃdas por null), e algumas propriedades de runtime que não devem estar no template. Sempre revise e limpe o template exportado antes de usá-lo como base.
Decompilação: De ARM para Bicep​
Quando você tem um ARM Template existente e quer trabalhar em Bicep:
az bicep decompile --file template-exportado.json
Isso gera um arquivo .bicep equivalente. A qualidade da decompilação é boa mas não perfeita: o arquivo gerado pode precisar de ajustes manuais, especialmente em templates com copy loops, reference() complexos ou propriedades de runtime.
Verificar Mudanças em Propriedades com --what-if​
Após modificar um template que afeta um recurso existente, o --what-if é indispensável:
az deployment group what-if \
--resource-group rg-producao \
--template-file main.bicep \
--parameters @parameters.prod.json \
--result-format FullResourcePayloads
O parâmetro --result-format FullResourcePayloads mostra o payload completo do recurso após as mudanças, útil para verificar exatamente quais propriedades serão alteradas.
O resultado codificado por cores:
+ Create: recurso novo~ Modify: recurso existente que será alterado- Delete: recurso que será excluÃdo (apenas em modo Complete)= Nochange: recurso sem alteraçõesx Ignore: recurso ignorado pelo ARM (gerenciado externamente)
6. Formas de Implementação​
6.1 VS Code com Extensões​
Quando usar: edição do dia a dia. O VS Code com as extensões corretas transforma a experiência de editar templates.
Extensões essenciais:
- Bicep (Microsoft): autocomplete, validação em tempo real, visualização de recursos para Bicep
- ARM Tools (Microsoft): schema validation, snippets e IntelliSense para ARM JSON
- Azure Resource Manager (ARM) Template Viewer: visualização gráfica do template
Vantagens: feedback imediato de erros, autocomplete de tipos de recurso, propriedades e valores, linting integrado.
6.2 Azure CLI: Validação e Deploy​
# Validar o template modificado ANTES de deployar
az deployment group validate \
--resource-group rg-producao \
--template-file main.bicep \
--parameters @parameters.prod.json
# What-If para ver o impacto ANTES de aplicar
az deployment group what-if \
--resource-group rg-producao \
--template-file main.bicep \
--parameters @parameters.prod.json
# Deploy com confirmação interativa do what-if
az deployment group create \
--resource-group rg-producao \
--template-file main.bicep \
--parameters @parameters.prod.json \
--confirm-with-what-if
O flag --confirm-with-what-if executa o what-if primeiro e pede confirmação antes de prosseguir com o deploy. É o fluxo mais seguro para deploys em produção interativos.
6.3 Portal do Azure: Editor de Templates​
O portal tem um editor integrado em Create a resource > Deploy a custom template > Build your own template in the editor. Útil para edições menores ou para testes rápidos, mas não substitui o fluxo de controle de versão para mudanças em produção.
7. Controle e Segurança​
Controle de Versão para Templates​
Templates devem estar em Git com o mesmo rigor que código de aplicação:
- Cada modificação em branch separada
- Pull Request com revisão antes de merge
- CI/CD pipeline que valida e deploya automaticamente após merge
- Tags para versões estáveis dos templates
Nunca modifique um template de produção diretamente sem passar pelo processo de PR.
Manter o PrincÃpio da Idempotência​
Um template modificado deve continuar sendo idempotente: deployar o mesmo template múltiplas vezes deve sempre resultar no mesmo estado, sem erros no segundo deploy.
Problemas comuns de idempotência após modificações:
- Usar
newGuid()em um nome de recurso: gera nome diferente a cada deploy, criando recursos duplicados - Verificar condições em recursos que devem ser únicos: se a condição muda entre deploys, o recurso pode ser criado ou excluÃdo inesperadamente
- Propriedades que o ARM não consegue comparar corretamente, gerando "updates" desnecessários
Testar a Modificação em Ambiente Isolado​
Antes de aplicar uma modificação em produção, sempre deploy em um resource group de teste dedicado:
# Criar RG de teste temporário
az group create \
--name rg-teste-modificacao \
--location brazilsouth
# Deploy do template modificado no RG de teste
az deployment group create \
--resource-group rg-teste-modificacao \
--template-file main-modificado.bicep \
--parameters @parameters.dev.json
# Verificar resultado
az resource list \
--resource-group rg-teste-modificacao \
--output table
# Limpar após teste
az group delete --name rg-teste-modificacao --yes --no-wait
8. Tomada de Decisão​
Quando modificar o template vs. criar um novo?​
| Situação | Decisão | Motivo |
|---|---|---|
| Adicionar configuração a recurso existente | Modificar | Menor impacto, evolução incremental |
| Adicionar novos recursos ao ambiente | Modificar | Manter tudo no mesmo template para coerência |
| Mudança arquitetural radical (ex: trocar LB por App Gateway) | Novo template ou branch experimental | Risco de impacto em recursos existentes |
| Refatoração para usar módulos | Nova estrutura com módulos + testes | Mudança estrutural que não afeta recursos finais |
| Template de terceiro que não pode ser editado | Template Spec ou módulo wrapper | Encapsular o template externo |
Modificar ARM JSON vs. converter para Bicep?​
| Situação | Decisão | Motivo |
|---|---|---|
| Template simples, time usa ARM | Modificar o ARM JSON | Menor esforço |
| Template complexo com muita duplicação | Converter para Bicep | Bicep reduz drasticamente a verbosidade |
| Time novo em IaC | Converter para Bicep | Melhor DX e curva de aprendizado |
| Integração que só aceita ARM JSON | Manter ARM, usar Bicep como fonte | Bicep compila para ARM; usar Bicep como fonte e compilar |
9. Boas Práticas​
Use --confirm-with-what-if em todos os deploys de produção: o custo de revisar o what-if é segundos; o custo de uma modificação inesperada pode ser horas de recovery.
Mantenha um arquivo de parâmetros por ambiente, nunca misture valores: parameters.dev.json, parameters.staging.json, parameters.prod.json. Nunca use o mesmo arquivo para ambientes diferentes, mesmo que apenas um valor mude.
Ao parametrizar um valor hardcoded, preserve o valor original como default: se o template tinha "location": "brazilsouth" hardcoded e você vai parametrizar, faça "defaultValue": "brazilsouth". Isso garante que um deploy sem passar o parâmetro novo continue funcionando com o mesmo comportamento do template original.
Documente cada modificação com comentários (em Bicep, use //):
// Adicionado em 2024-03: configuração de soft delete para blobs
// Issue #123: requisito de compliance para retenção de 7 dias
resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
parent: storageAccount
name: 'default'
properties: {
deleteRetentionPolicy: {
enabled: true
days: 7
}
}
}
Verifique a apiVersion ao adicionar novas propriedades: uma propriedade nova pode exigir uma apiVersion mais recente. Se você adicionar uma propriedade e o deploy falhar com erro de propriedade não reconhecida, verifique se a apiVersion do recurso suporta essa propriedade.
10. Erros Comuns​
Adicionar parâmetro sem valor default e quebrar deploys de CI/CD existentes
Um novo parâmetro newFeatureEnabled é adicionado ao template sem defaultValue. O pipeline de CI/CD não passa esse parâmetro e o deploy falha com erro de parâmetro obrigatório não fornecido. Sempre adicione defaultValue em novos parâmetros que não são fornecidos por todos os pipelines existentes.
Alterar o nome de um recurso existente sem entender o impacto
O template tinha "name": "minha-storage" e foi modificado para "name": "[parameters('storageName')]", com o parâmetro recebendo "nova-storage". No próximo deploy, o ARM cria nova-storage como novo recurso e o recurso minha-storage permanece (modo Incremental) ou é excluÃdo (modo Complete). Não é uma "renomeação": é uma criação de novo recurso. Recursos Azure não podem ser renomeados.
Modificar a apiVersion de um recurso sem verificar breaking changes
Atualizar apiVersion de 2020-01-01 para 2023-05-01 pode mudar o comportamento de algumas propriedades ou tornar propriedades obrigatórias que antes eram opcionais. Consulte sempre o changelog da API do resource provider ao atualizar a versão.
Remover uma propriedade esperando que o valor volte ao padrão
Em muitos recursos Azure, remover uma propriedade do template não reseta o valor para o padrão: o ARM mantém o valor atual do recurso. Para resetar uma propriedade para o padrão, você precisa defini-la explicitamente com o valor padrão no template.
Editar diretamente o template exportado do portal sem limpar
Templates exportados pelo portal frequentemente contêm: propriedades de runtime (como etags, ids gerados), propriedades que o ARM não aceita na criação, e valores hardcoded que deveriam ser parâmetros. Usar o template exportado diretamente sem revisão pode causar erros de deploy ou criar dependências acidentais em valores especÃficos do ambiente de origem.
11. Operação e Manutenção​
Rastrear o Histórico de Modificações​
O histórico de deploy do ARM registra cada deploy com nome, timestamp e status. Para rastrear modificações ao longo do tempo, use nomes de deploy descritivos:
az deployment group create \
--resource-group rg-producao \
--template-file main.bicep \
--parameters @parameters.prod.json \
--name "add-blob-container-issue-123"
Com nomes descritivos, o histórico de deploys se torna uma timeline das modificações:
az deployment group list \
--resource-group rg-producao \
--query "[].{Nome:name, Data:properties.timestamp, Status:properties.provisioningState}" \
--output table
Comparar o Estado Atual com o Template​
Para verificar se o estado atual dos recursos está sincronizado com o template (drift detection):
az deployment group what-if \
--resource-group rg-producao \
--template-file main.bicep \
--parameters @parameters.prod.json \
--mode Complete
Se o what-if em modo Complete mostrar mudanças, significa que o estado atual divergiu do template (alguém modificou recursos manualmente ou via portal). As mudanças indicadas são o que seria necessário para sincronizar o estado real com o template.
12. Integração e Automação​
Pipeline de Validação de Template no PR​
Um pipeline que roda em cada Pull Request para validar modificações:
# .github/workflows/validate-template.yml
name: Validate Template Changes
on:
pull_request:
paths: ['infrastructure/**']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Lint Bicep
run: az bicep build --file infrastructure/main.bicep
- name: Validate against dev
run: |
az deployment group validate \
--resource-group rg-dev \
--template-file infrastructure/main.bicep \
--parameters @infrastructure/parameters.dev.json
- name: What-If against staging
run: |
az deployment group what-if \
--resource-group rg-staging \
--template-file infrastructure/main.bicep \
--parameters @infrastructure/parameters.staging.json \
--output table
Esse pipeline garante que nenhuma modificação com erros de sintaxe ou que quebre o deploy em staging chegue à branch principal.
Módulos Reutilizáveis para Padronização​
Ao modificar múltiplos templates que compartilham padrões, extraia o padrão para um módulo:
Quando você modifica o módulo storage.bicep, todos os templates que o importam recebem automaticamente a modificação no próximo deploy, garantindo consistência.
13. Resumo Final​
Pontos essenciais:
- Modificar um template existente requer entender o impacto da mudança: adicionar é baixo risco, alterar propriedades pode causar recriação de recursos, remover tem comportamento diferente em modo Incremental vs. Complete.
- O
--what-ifé indispensável antes de qualquer deploy de template modificado em produção. Ele mostra exatamente quais recursos serão criados (+), modificados (~), excluÃdos (-) ou não alterados (=). - Ao parametrizar um valor hardcoded, sempre preserve o valor original como
defaultValuepara não quebrar deploys existentes que não passam o novo parâmetro.
Diferenças crÃticas:
- Renomear um recurso no template não renomeia o recurso Azure: cria um novo com o novo nome e o antigo permanece (modo Incremental) ou é excluÃdo (modo Complete).
- Remover uma propriedade do template frequentemente não reseta o valor para o padrão: o ARM mantém o valor atual do recurso. Para resetar, defina explicitamente o valor padrão.
az deployment group validateverifica sintaxe e schema mas não simula o impacto.--what-ifsimula o impacto real nos recursos existentes.- Template exportado do portal contém propriedades de runtime e valores hardcoded que precisam de limpeza antes do uso.
O que precisa ser lembrado:
- Ao adicionar um novo recurso filho (ex: blob container dentro de storage account), use
parent:em Bicep ou o padrão de nomepai/filhoem ARM, e garanta a dependência explÃcita viadependsOnoureference(). - Recursos com propriedades imutáveis (ex:
kindem storage accounts) causam recriação quando alterados. O--what-ifsinaliza isso como-Delete++Create. - O comando
az bicep decompileconverte ARM JSON para Bicep como ponto de partida, mas o resultado sempre precisa de revisão manual. - Sempre use
--confirm-with-what-ifem deploys interativos de produção para ter uma última chance de revisar o impacto antes de confirmar. - O histórico de deploys tem limite de 800 entradas por resource group. Use nomes de deploy descritivos para rastreabilidade e configure limpeza automática em ambientes com deploys frequentes.