segunda-feira, 16 de fevereiro de 2026

Devops - Melhores práticas do Terraform no Azure Pipelines



Melhores práticas do Terraform no Azure Pipelines

O Azure Pipelines e o Terraform facilitam o início da implantação de infraestrutura a partir de modelos. Mas como transformar um código de exemplo em uma implementação real, integrando fluxos de trabalho Git com as implantações e escalando para várias equipes? Aqui estão 5 práticas recomendadas para você começar com o pé direito.

Como engenheiro na organização Azure Customer Experience (CXP), aconselho clientes com orientações sobre as melhores práticas e análises técnicas aprofundadas para casos de uso específicos. Este artigo baseia-se tanto em temas recorrentes com clientes quanto na minha experiência anterior como Arquiteto Empresarial na Allianz Alemanha, quando iniciamos nossa migração para a nuvem em 2016.

Cinco boas práticas

  1. Use Pipelines YAML, não a interface do usuário.
  2. Use a linha de comando, não tarefas YAML.
  3. Utilizar a configuração parcial do Terraform
  4. Autentique-se com as credenciais da entidade de serviço armazenadas no Azure Key Vault.
  5. Criar uma função personalizada para o Terraform

Resumindo: assista a este resumo de 5 minutos:

Aviso de Privacidade - ao reproduzir o vídeo incorporado, você reconhece e concorda com os cookies definidos pelo YouTube.com.

Dica nº 1 - Use Pipelines YAML, não a interface gráfica.

O serviço Azure DevOps tem suas raízes no Visual Studio Team Foundation Server e, como tal, carrega recursos legados, incluindo Pipelines Clássicos. Se você estiver criando novos pipelines, não comece com pipelines clássicos. Se você já possui pipelines clássicos, planeje migrá-los para YAML. A prática recomendada do setor é criar Pipelines como Código e, no Azure Pipelines, isso significa Pipelines YAML .

Se você usa Pipelines Clássicos, não se preocupe. Eles continuarão disponíveis por um bom tempo. Mas, como você pode ver no cronograma de recursos públicos e no roteiro público , a Microsoft está investindo mais em pipelines YAML. Para garantir maior compatibilidade futura, escolha pipelines YAML.

Dica nº 2 - Use a linha de comando, não tarefas YAML.

Tenho uma relação de amor e ódio com as Tarefas de Pipeline . Como abstração, elas reduzem a barreira de entrada. Tornam as tarefas independentes de plataforma (Windows vs. Linux) e passam códigos de retorno, dispensando a necessidade de manipulação stderrmanual stdout. Veja o repositório de código-fonte no GitHub para outras vantagens.

Mas, como o próprio arquivo README diz:

Se você precisar de funcionalidades personalizadas em sua compilação/lançamento, geralmente é mais simples usar as tarefas de execução de scripts existentes, como as tarefas do PowerShell ou do Bash.

E, de fato, acho mais simples usar comandos CLI básicos no Bash. Com o tempo, à medida que você itera e cria pipelines personalizados além dos exemplos de "Olá, Mundo!", você também pode descobrir que as tarefas se tornam mais uma camada para depurar. Por exemplo, usei a tarefa AzCopy e tive que esperar alguns minutos até que o pipeline falhasse, pois ela só funciona no Windows .

Iterar mais rápido

Se eu usar a linha de comando, posso descobrir exatamente quais parâmetros -vare outras opções preciso passar para terraformobter os resultados desejados na minha máquina local, sem ter que esperar minutos para que cada tarefa do pipeline seja executada e eu saiba se funcionou ou não. Assim que eu estiver confiante nos meus comandos da CLI, posso adicioná-los ao meu pipeline YAML.

Domine a tecnologia, não apenas uma tarefa.

Em geral, recomendo que todo engenheiro aprenda a usar uma tecnologia pela linha de comando. Não aprenda a usar a extensão Git no seu editor de código. Se você aprender algo pela linha de comando, seja Git ou Terraform , você aprenderá como funciona . A depuração será muito menos frustrante, pois você poderá ignorar uma camada de abstração (a tarefa YAML) que não necessariamente facilita sua vida.

Por exemplo, prefiro ignorar este formato detalhado encontrado neste exemplo da documentação do Azure.

# Verbose 😑
- task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
  displayName: 'Run terraform plan'
  inputs:
    command: plan
    workingDirectory: $(terraformWorkingDirectory)
    environmentServiceName: $(serviceConnection)
    commandOptions: -var location=$(azureLocation)

Você pode fazer o mesmo usando Bash e simplesmente passar os parâmetros, por exemplo -var, ou -outcomo está.

# Less noise 👌
- bash: terraform plan -out deployment.tfplan
  displayName: Terraform Plan (ignores drift)

Como não uso tarefas, nunca preciso consultar documentação adicional sobre o que environmentServiceNameos atributos fazem e esperam. Só preciso conhecer o Terraform, o que me permite focar no meu código em vez de depurar uma dependência — mesmo que seja fornecida pela Microsoft.

Não instale o Terraform - mantenha-se atualizado com a versão mais recente.

Existem muitos exemplos de Azure Pipeline com tarefas de "instalação", incluindo exemplos oficiais. Embora o versionamento de dependências seja importante, considero o Terraform uma das tecnologias mais estáveis, que raramente apresenta alterações que quebram a compatibilidade. Antes de se comprometer com uma versão específica, considere sempre usar a versão mais recente. Em geral, é mais fácil fazer alterações e correções incrementais do que ter que realizar grandes refatorações posteriormente, que podem bloquear o desenvolvimento de novos recursos.

Você pode ver qual versão está instalada nos agentes de compilação hospedados pela Microsoft no GitHub, por exemplo, Ubuntu 18.04 . Observe que esses agentes de compilação são usados ​​tanto pelo Azure Pipelines quanto pelo GitHub Actions .

A CLI é independente de fornecedor

Essa preferência por dominar a linha de comando em vez de tarefas YAML não é específica do Terraform. Se você navegar pelos meus diversos exemplos no GitHub , verá que geralmente prefiro usar Docker e Node.js na linha de comando em vez das tarefas YAML equivalentes.

O setor é dinâmico e acelerado. Usar a CLI também facilita a migração para novos fornecedores. Se, no futuro, quando o GitHub Actions estiver mais consolidado e você quiser migrar do Azure Pipelines, não precisará migrar a camada de abstração de tarefas YAML. Use a CLI e facilite sua vida futura.

Mas então, como faço para me autenticar no Azure?

Essa é uma pergunta frequente dos meus clientes. Continue lendo. Isso está na última seção deste artigo, que também aborda o gerenciamento de segredos em pipelines.

Dica nº 3 - Use a configuração parcial do Terraform

Este tópico merece um artigo próprio. Mas vou mencionar os pontos mais importantes. Você precisará de um arquivo de estado ao colaborar com outros engenheiros ou ao implantar a partir de um agente de compilação sem interface gráfica.

Comece pelo Estado Local

Se você não sabe como sua infraestrutura deve ser estruturada, experimente localmente, ou seja, não use um backend remoto para evitar o tempo de espera de minutos do CI/CD.

Ao experimentar, você provavelmente vai quebrar alguma coisa. Nessa fase, em vez de tentar consertar, eu simplesmente desmontaria tudo rm -rf .terraforme começaria do zero.

Assim que a arquitetura da sua infraestrutura estiver estável, prossiga para a criação de um arquivo de estado remoto.

Crie uma conta de armazenamento para o seu arquivo estadual.

O Terraform precisa de uma conta do Armazenamento de Blobs do Azure. Dica: crie a conta de armazenamento manualmente usando a CLI do Azure.

$ az storage account create \
  --name mystorageaccountname \
  --resource-group myresourcegroupname \
  --kind StorageV2 \
  --sku Standard_LRS \
  --https-only true \
  --allow-blob-public-access false

Como os arquivos de estado do Terraform armazenam tudo, incluindo segredos, em texto não criptografado, tome precauções extras para protegê-los. Confirme se você desativou o acesso público aos blobs .

Por favor, não confie em uma tarefa de pipeline para criar a conta para você! Existe uma tarefa que faz isso, mas a conta de armazenamento está configurada para permitir acesso público a arquivos blob por padrão. Os arquivos de estado individuais em si são protegidos. Mas as configurações padrão não são seguras , o que representa um risco de segurança iminente.

Não utilize a configuração padrão.

Ao usar um backend remoto, você precisa informar ao Terraform onde o arquivo de estado está localizado. Exemplos de configuração da documentação oficial são semelhantes a este:

# Don't do this
terraform {
  backend "azurerm" {
    resource_group_name  = "StorageAccount-ResourceGroup"
    storage_account_name = "abcd1234"
    container_name       = "tfstate"
    key                  = "prod.terraform.tfstate"

    # Definitely don't do this!
    access_key           = ""
  }
}

Usar configuração parcial

Mais adiante na documentação, o Terraform recomenda remover essas propriedades e usar a Configuração Parcial :

# This is better
terraform {
  backend "azurerm" {
  }
}

Criar e ignorar o arquivo de configuração do backend

Em vez de usar chaves de acesso da conta de armazenamento do Azure, uso tokens de assinatura de acesso compartilhado (SAS) de curta duração . Então, crio um azure.confarquivo local com a seguinte aparência:

# azure.conf, must be in .gitignore
storage_account_name="azurestorageaccountname"
container_name="storagecontainername"
key="project.tfstate"
sas_token="?sv=2019-12-12…"

Verifique três vezes se o seu arquivo azure.conffoi adicionado .gitignorepara que não seja incluído no seu repositório de código.

Não há problema em usar um arquivo no ambiente de desenvolvimento local.

Na minha máquina local, eu inicializo o Terraform passando o arquivo de configuração completo.

$ terraform init -backend-config=azure.conf

Observação: um dos motivos pelos quais uso tokens SAS é que geralmente só preciso trabalhar com o arquivo de estado remoto na fase inicial de um projeto. Em vez de deixar uma chave de acesso por aí, tenho um token SAS expirado na minha máquina local.

Sua configuração NÃO deve ser um arquivo .tfvars

Variáveis ​​com .tfvarsextensão são carregadas automaticamente, o que é um acidente à espera de acontecer. É assim que as pessoas, sem querer, incluem credenciais no Git. Não seja essa pessoa ou empresa. Adicione um pouco de atrito e use a -backend-config=azure.confopção de linha de comando.

Você também pode atribuir uma .hclextensão ao arquivo para que seu editor faça o realce de sintaxe. Eu uso, .confpor convenção, para sinalizar um aviso de que este arquivo pode conter informações confidenciais e deve ser protegido.

Utilize pares de chave-valor em builds de CI/CD

Pessoalmente, não uso Arquivos Seguros no Azure Pipelines porque não quero ter minhas credenciais em mais um lugar que preciso encontrar e depurar . Para resolver o primeiro problema, uso o Key Vault (continue lendo).

Para resolver o segundo problema, passo a configuração como variáveis ​​individuais para o terraform initcomando:

$ terraform init \
  -backend-config="storage_account_name=$TF_STATE_BLOB_ACCOUNT_NAME" \
  -backend-config="container_name=$TF_STATE_BLOB_CONTAINER_NAME" \
  -backend-config="key=$TF_STATE_BLOB_FILE" \
  -backend-config="sas_token=$TF_STATE_BLOB_SAS_TOKEN"

Se você não estiver usando tokens SAS, poderá passar a chave de acesso da conta de armazenamento com-backend-config="access_key=…"

Ao usar pares de chave-valor, sou explícito, o que me obriga a fazer verificações de sanidade em cada etapa e aumenta a rastreabilidade. Você agradecerá a si mesmo no futuro. Observe também que minhas variáveis ​​são nomeadas com o TF_prefixo para facilitar a depuração.

Portanto, a etapa completa em YAML se parece com isto:

# Load secrets from Key Vault
variables:
  - group: e2e-gov-demo-kv

# Initialize with explicitly mapped secrets
steps:
- bash: |
    terraform init \
      -backend-config="storage_account_name=$TF_STATE_BLOB_ACCOUNT_NAME" \
      -backend-config="container_name=$TF_STATE_BLOB_CONTAINER_NAME" \
      -backend-config="key=$TF_STATE_BLOB_FILE" \
      -backend-config="sas_token=$TF_STATE_BLOB_SAS_TOKEN"
  displayName: Terraform Init
  env:
    TF_STATE_BLOB_ACCOUNT_NAME:   $(kv-tf-state-blob-account)
    TF_STATE_BLOB_CONTAINER_NAME: $(kv-tf-state-blob-container)
    TF_STATE_BLOB_FILE:           $(kv-tf-state-blob-file)
    TF_STATE_BLOB_SAS_TOKEN:      $(kv-tf-state-sas-token)

Continue a leitura para saber como funciona a integração do Key Vault. Também usaremos essa estratégia para autenticar no Azure e gerenciar nossa infraestrutura.

Dica nº 4 - Autentique-se com as credenciais da entidade de serviço armazenadas no Azure Key Vault.

Costumamos comemorar quando finalmente conseguimos fazer algo funcionar em nossa máquina local. Infelizmente, pode ser cedo demais para festejar. Transpor esses mesmos passos para fluxos de trabalho automatizados exige mais esforço, que, conceitualmente, às vezes é difícil de entender.

Por que az loginnão funciona em CI/CD?

Resumindo, não funciona porque um agente de compilação não possui interface gráfica. Ele não é um humano. Não pode interagir com o Terraform (ou com o Azure, aliás) de forma interativa. Alguns clientes tentam autenticar via CLI e me perguntam como fazer o agente sem interface gráfica passar pela autenticação multifator (MFA) que suas organizações implementaram. É exatamente por isso que não usamos a CLI do Azure para fazer login. Como explica a documentação do Terraform.

Recomendamos o uso de uma Entidade de Serviço ou de uma Identidade de Serviço Gerenciada ao executar o Terraform de forma não interativa (como ao executar o Terraform em um servidor de CI) e a autenticação usando a CLI do Azure ao executar o Terraform localmente.

Portanto, vamos nos autenticar na API do Azure Resource Manager definindo o segredo do cliente da nossa entidade de serviço como variáveis ​​de ambiente:


- bash: terraform apply -auto-approve deployment.tfplan
  displayName: Terraform Apply
  env:
    ARM_SUBSCRIPTION_ID: $(kv-arm-subscription-id)
    ARM_CLIENT_ID:       $(kv-arm-client-id)
    ARM_CLIENT_SECRET:   $(kv-arm-client-secret)
    ARM_TENANT_ID:       $(kv-arm-tenant-id)

Os nomes das variáveis ​​de ambiente, por exemplo, ARM_CLIENT_IDpodem ser encontrados nesta documentação do Terraform . Alguns de vocês podem estar se perguntando: variáveis ​​de ambiente são seguras? Sim. Aliás, a tarefa oficial da CLI do Azure faz exatamente a mesma coisa, como vocês podem ver na linha 43 do código-fonte da tarefa.

Para sermos claros, autenticamos agentes de compilação headless definindo IDs de cliente e segredos como variáveis ​​de ambiente, o que é uma prática comum. A melhor prática envolve proteger esses segredos.

Verifique novamente se você está usando segredos de pipeline.

No Azure Pipelines, ter credenciais no seu ambiente só é seguro se você marcar as variáveis ​​do seu pipeline como segredos, o que garante:

  • A variável é criptografada em repouso.
  • O Azure Pipelines irá mascarar os valores ***(com base no princípio da melhor tentativa).
Procure o ícone de cadeado para garantir que você marcou suas variáveis ​​como secretas.
Procure o ícone de cadeado para garantir que você marcou suas variáveis ​​como secretas.

Se você mudar para texto sem formatação, a senha não aparecerá. Nesse caso, você precisará redefini-la.

A ressalva quanto ao uso de segredos é que você precisa mapear explicitamente cada segredo para uma variável de ambiente em cada etapa do pipeline . Pode ser tedioso, mas é intencional e deixa as implicações de segurança claras. É como realizar uma pequena revisão de segurança a cada implantação. Essas revisões têm o mesmo propósito das listas de verificação que comprovadamente salvam vidas . Seja explícito para garantir a segurança.

Vá além - Integração com o Key Vault

Garantir que você esteja usando Segredos de Pipeline pode ser suficiente. Se quiser ir além, recomendo integrar o Key Vault por meio de variáveis ​​secretas — e não por meio de uma tarefa YAML.

Use a opção "Vincular segredos..." para integrar o Key Vault.
Use a opção "Vincular segredos..." para integrar o Key Vault.

Observe que "assinatura do Azure" aqui se refere a uma conexão de serviço. Uso esse nome msdn-sub-reader-sp-e2e-governance-demopara indicar que a entidade de serviço subjacente tem apenas acesso de leitura aos meus recursos do Azure.

Estas são algumas razões pelas quais grandes empresas e corporações podem optar por este caminho:

  • Reutilize segredos entre projetos e organizações do Azure DevOps. Você só pode compartilhar conexões de serviço entre projetos.
  • Segurança reforçada com o Azure Key Vault. Com as permissões adequadas da entidade de serviço e a política de acesso do Key Vault, torna-se impossível alterar ou excluir um segredo do Azure DevOps.
  • Rotação de segredos escalável . Prefiro tokens de curta duração a credenciais de longa duração. Como o Azure Pipelines busca os segredos no início da execução do build, eles estão sempre atualizados. Se eu rotacionar as credenciais regularmente, preciso alterá-las em apenas um lugar: o Key Vault.
  • Superfície de ataque reduzida . Se eu colocar a credencial no Key Vault, o segredo do cliente para minha entidade de serviço fica armazenado em apenas dois locais: A) Azure Active Directory, onde reside, e B) Azure Key Vault.
    Se eu usar uma Conexão de Serviço, aumento minha superfície de ataque para três locais. Pensando como um antigo Arquiteto Empresarial... Confio no Azure DevOps como um serviço gerenciado para proteger meus segredos. No entanto, como organização, podemos comprometê-los acidentalmente quando alguém configura as permissões incorretamente.

Dica: Todas as variáveis ​​acima têm o prefixo `keyv`, kv-uma convenção de nomenclatura que uso para indicar que esses valores estão armazenados no Key Vault.

Dica nº 5: Crie uma função personalizada para o Terraform.

A melhor prática de segurança e RBAC é conceder apenas o acesso necessário para minimizar os riscos. Portanto, a qual função do Azure devemos atribuir a entidade de serviço usada pelo Terraform? Proprietário ou Colaborador ?

Nenhuma das duas. Como estamos implantando infraestrutura, provavelmente também precisaremos definir permissões, por exemplo, criar uma Política de Acesso ao Key Vault , que requer permissões elevadas. Para ver quais permissões os Colaboradores não possuem, podemos executar este comando da CLI do Azure:

az role definition list \
  --name "Contributor" \
  --output json \
  --query '[].{actions:permissions[0].actions, notActions:permissions[0].notActions}'

que produzirá a seguinte saída:

[
  {
    "actions": [
      "*"
    ],
    "notActions": [
      "Microsoft.Authorization/*/Delete",
      "Microsoft.Authorization/*/Write",
      "Microsoft.Authorization/elevateAccess/Action",
      "Microsoft.Blueprint/blueprintAssignments/write",
      "Microsoft.Blueprint/blueprintAssignments/delete"
    ]
  }
]

Para criar uma Política de Acesso ao Cofre de Chaves, nossa entidade de serviço precisará "Microsoft.Authorization/*/Write"de permissões. A solução mais fácil é atribuir à entidade de serviço a função de Proprietário. Mas isso equivale a ter privilégios de administrador.

Consequências da exclusão

Existem diferenças sutis, porém importantes, não apenas para grandes empresas, mas também para setores que precisam cumprir regulamentações. Portanto, se você é uma pequena startup de Fintech, isso também se aplica a você. Alguns dados não podem ser excluídos por lei, como, por exemplo, dados financeiros necessários para auditorias fiscais. Devido à gravidade e às consequências legais da perda desses dados, é uma prática comum na nuvem aplicar bloqueios de gerenciamento a um recurso para impedir que ele seja excluído.

Ainda queremos que o Terraform crie e gerencie nossa infraestrutura, então concedemos Writeas permissões necessárias. Mas não concederemos essas Deletepermissões porque:

  • A automação é poderosa. E com grande poder vem grande responsabilidade, que não queremos conceder a um agente de compilação sem interface gráfica (e, portanto, sem cérebro).
  • É importante entender que o Git (mesmo com commits assinados) oferece rastreabilidade técnica , mas em sua organização isso pode não atender aos requisitos de auditoria legal .

Portanto, mesmo que você tenha protegido seu fluxo de trabalho com Pull Requests e branches protegidas, isso pode não ser suficiente. Sendo assim, vamos mover a Deleteação da camada Git para a camada de gerenciamento em nuvem, ou seja, o Azure, para fins de auditoria, utilizando bloqueios de gerenciamento.

Então, crie uma função personalizada e certifique-se de ter o seguinte notActions:

{
  "notActions": [
    "Microsoft.Authorization/*/Delete"
  ]
}

O código não especifica o Azure Blueprints . Use o mesmo raciocínio acima para determinar se, no seu caso de uso, você precisa de acesso e quando restringi-lo.

Resumo

Neste guia completo, abordamos algumas práticas recomendadas gerais do Azure Pipelines para usar Pipelines como Código (YAML) e a linha de comando, o que ajuda você a dominar o Terraform e outras tecnologias. Também mostramos como proteger adequadamente seu arquivo de estado e autenticar com o Azure, abordando as armadilhas mais comuns. Por fim, abordamos os dois últimos tópicos: integração com o Key Vault e criação de uma função personalizada para o Terraform.

Se este artigo apresentar informações excessivas sobre segurança, não se preocupe. Não tente implementar todas as práticas de uma vez. Pratique uma de cada vez. Com o tempo, ao longo de alguns meses, as melhores práticas de segurança se tornarão um hábito natural.

Este artigo focou especificamente nas melhores práticas para o uso do Azure Pipelines. Fique atento para um próximo artigo sobre melhores práticas gerais, onde explicarei como usar fluxos de trabalho Git e gerenciar infraestrutura em diferentes ambientes.