Azure DevOps: Pipelines e Repositórios

[324] Azure DevOps: Pipelines e Repositórios

A plataforma que muitas empresas já adotaram antes do GitHub: os cinco componentes do Azure DevOps, a estrutura de stages, jobs e tasks nos pipelines YAML, os templates reutilizáveis, as service connections com federação de identidade, os gates automáticos de aprovação e o critério de quando usar cada plataforma.
DevOps

19 min de leitura

O GitHub Actions foi a plataforma de CI/CD do currículo principal desta série — uma escolha natural para projetos que hospedam código no GitHub. Mas no mercado corporativo, especialmente em empresas que já adotaram o ecossistema Microsoft, o Azure DevOps é frequentemente a plataforma consolidada de desenvolvimento de software. Entender o Azure DevOps é, portanto, uma habilidade prática para quem vai trabalhar nesse ambiente.

Este artigo cobre o Azure DevOps de forma completa: o que é cada componente da plataforma, como os pipelines funcionam com YAML, como integrar com o AKS para deploy, e a discussão sobre quando usar Azure DevOps versus manter o GitHub Actions mesmo em projetos Azure.

O Que É o Azure DevOps

O Azure DevOps não é apenas uma ferramenta de CI/CD — é uma plataforma integrada de gestão do ciclo de vida de software que a Microsoft oferece como serviço (azure.microsoft.com/products/devops) ou instalável on-premises (Azure DevOps Server). Ele tem cinco componentes principais.

O Azure Boards é rastreamento de trabalho com Kanban, Scrum e épicos — equivalente ao GitHub Issues + Projects ou ao Jira, com integração nativa com PRs e commits.

O Azure Repos são repositórios Git (ou TFVC legado) — equivalente ao GitHub Repositories, com pull requests, code review e branch policies.

O Azure Pipelines é o CI/CD declarativo em YAML — o coração do Azure DevOps, equivalente ao GitHub Actions mas com conceitos próprios (stages, jobs, tasks).

O Azure Artifacts é o registro de pacotes (npm, NuGet, Maven, PyPI, Docker) — equivalente ao GitHub Packages ou ao AWS CodeArtifact.

O Azure Test Plans é a gestão de testes manuais e exploratórios — sem equivalente direto no GitHub, mais relevante para times com QA dedicado.

A orientação atual da própria Microsoft é usar GitHub Actions para novos projetos que hospedam código no GitHub, e Azure DevOps Pipelines quando há necessidade de integração profunda com Azure Boards, Azure Artifacts, ou quando o código está no Azure Repos. Muitas organizações usam os dois: código no GitHub, rastreamento de trabalho no Azure Boards, pipeline no GitHub Actions conectado ao Azure.

Estrutura de um Pipeline YAML

O pipeline YAML do Azure DevOps usa conceitos similares ao GitHub Actions mas com nomenclatura diferente. O mapeamento é: GitHub Actions usa workflow → job → step; Azure DevOps usa pipeline → stage → job → step (task ou script).

O Azure DevOps adiciona o conceito de stage como agrupamento de jobs que representam fases do processo (Build, Test, Deploy Staging, Deploy Production). Isso que no GitHub Actions exige conventions e dependências explícitas entre jobs, no Azure DevOps é um conceito de primeira classe.

O arquivo azure-pipelines.yml na raiz do repositório é equivalente aos arquivos em .github/workflows/. O trigger define branches e paths que disparam o pipeline — o pr define o gatilho para pull requests. Variáveis globais são declaradas com variables e podem referenciar grupos de variáveis do Azure DevOps Library (equivalente aos Secrets do GitHub). O pool.vmImage equivale ao runs-on do GitHub Actions.

Um stage de Build contém jobs paralelos para testes unitários, lint e análise de segurança. A task PublishTestResults@2 publica os resultados dos testes no painel do Azure DevOps — um recurso que o GitHub Actions não tem nativamente. A task PublishCodeCoverageResults@1 equivale ao upload para Codecov.

O stage de BuildImagem usa a task Docker@2 com command: buildAndPush para fazer build e push para o ACR em uma única operação. O stage de DeployStaging usa o tipo especial deployment (em vez de job), que habilita rastreamento de deploys no Environment. A task AzureCLI@2 é o equivalente ao step com uses: azure/login@v2 seguido de comandos kubectl.

O stage de DeployProducao referencia um Environment configurado no portal com aprovação manual — equivalente aos Environments do GitHub com required reviewers. A estratégia runOnce com os blocos preDeploy, deploy, routeTraffic, postRouteTraffic, on.failure e on.success oferece um controle granular do processo de deploy que o GitHub Actions não tem nativamente.

Templates Reutilizáveis

O recurso mais poderoso dos Azure Pipelines para times com múltiplos repositórios são os templates. Ao contrário dos GitHub Actions reutilizáveis, os templates do Azure DevOps podem ser compostos de maneira muito mais granular: templates de steps individuais, de jobs inteiros ou de stages completos.

Um repositório central de templates é referenciado nos pipelines com o bloco resources.repositories, apontando para o nome da organização, repositório e branch. O template é chamado com template: caminho/arquivo.yml@nomeDoRepositorioRemoto, passando parâmetros tipados. Os parâmetros do template declaram tipo (string, number, boolean), nome e valor padrão — o Azure DevOps valida os tipos em tempo de parse, antes de executar.

Service Connections: Autenticando com Azure Resources

As Service Connections do Azure DevOps são o equivalente dos secrets de credenciais no GitHub Actions, mas gerenciadas centralmente e reutilizáveis em múltiplos projetos e pipelines. Para conectar ao Azure, a recomendação é Workload Identity Federation — sem client secrets com prazo de expiração. A criação pode ser feita pelo portal do Azure DevOps ou via az devops service-endpoint azurerm create com --azure-rm-service-principal-authentication-scheme WorkloadIdentityFederation.

Ambientes e Gates Automáticos

Os Environments do Azure DevOps fornecem controle de aprovação para deploys em ambientes críticos. Mas o Azure DevOps vai além do GitHub Actions: suporta múltiplos aprovadores com lógica (qualquer um aprova vs. todos precisam aprovar), timeouts de aprovação, e gates automáticos.

Gates automáticos permitem que o pipeline verifique condições externas antes de prosseguir — por exemplo, verificar que a taxa de erro em staging está abaixo de 1% antes de permitir o deploy em produção, ou que não há alertas críticos ativos no Azure Monitor. Os gates são configurados no portal do Azure DevOps no painel de Approvals and Checks do Environment, e executam periodicamente até a condição ser satisfeita ou o timeout ser atingido.

O tipo deployment com strategy runOnce oferece os blocos preDeploy (antes do deploy), deploy (o deploy em si), routeTraffic (roteamento de tráfego), postRouteTraffic (verificações pós-roteamento), on.failure (rollback automático) e on.success (notificações). Esse ciclo de vida estruturado é exclusivo do Azure DevOps — no GitHub Actions, toda essa lógica precisa ser implementada manualmente com steps condicionais.

Azure Artifacts: Registro Privado de Pacotes

Times que desenvolvem bibliotecas internas compartilhadas entre múltiplos projetos precisam de um registro privado de pacotes. O Azure Artifacts oferece feeds para npm, NuGet, Maven, PyPI e Universal Packages.

A publicação de um pacote npm no pipeline usa a task npmAuthenticate@0 para configurar o arquivo .npmrc com as credenciais do feed do Azure Artifacts, seguida de npm publish. O arquivo .npmrc do projeto aponta para a URL do feed no formato pkgs.dev.azure.com/organização/projeto/_packaging/feed/npm/registry/. Não é necessário configurar tokens manualmente — a task de autenticação cuida disso usando a Service Connection configurada.

Branch Policies via Terraform

As Branch Policies do Azure Repos são o equivalente às Branch Protection Rules do GitHub. O provider Terraform microsoft/azuredevops permite gerenciá-las como código. As políticas mais comuns são: azuredevops_branch_policy_min_reviewers (mínimo de revisores, com opção de impedir que o próprio autor aprove), azuredevops_branch_policy_build_validation (exige que um pipeline específico passe antes de mergear) e azuredevops_branch_policy_comment_resolution (todos os comentários de revisão devem ser resolvidos antes do merge).

Quando Usar o Quê

A decisão entre GitHub Actions e Azure DevOps raramente é binária em organizações de médio e grande porte. O cenário mais comum é uma combinação.

Código no GitHub + Pipeline no GitHub Actions + Azure como infraestrutura é a combinação mais simples para novos projetos. O GitHub Actions autentica no Azure via Workload Identity Federation e faz deploy para o AKS. Toda a gestão de código e pipeline fica em um único lugar.

Código no Azure Repos + Pipeline no Azure Pipelines + AKS é o padrão em organizações que já adotaram o Azure DevOps como plataforma corporativa, especialmente aquelas com controles de compliance que exigem que o código não saia do ambiente Microsoft.

Código no GitHub + Rastreamento no Azure Boards + Pipeline misto é uma combinação comum em organizações que usam o GitHub pela experiência do desenvolvedor mas mantêm o Azure Boards para gestão de projetos por causa da integração com Power BI e Microsoft Teams.

Com os quatro artigos desta extensão, a transição de AWS para Azure está coberta nos pontos mais importantes: conceitos e equivalências (E1), compute, storage e redes (E2), Kubernetes com AKS (E3) e CI/CD com Azure DevOps (E4). O engenheiro que domina ambas as plataformas tem uma versatilidade de mercado significativa — e percebe, na prática, que os fundamentos são sempre os mesmos: automação, observabilidade, segurança por padrão e infraestrutura como código.

Referências para Aprofundamento

Exercícios

Exercício 1

O artigo indica o provider microsoft/azuredevops para gerenciar Branch Policies como código e cita os recursos azurerm_branch_policy_min_reviewers, azurerm_branch_policy_build_validation e azurerm_branch_policy_comment_resolution. Um engenheiro copia esses nomes e o terraform init falha. Por quê?

Ver resposta

✓ Resposta: Os nomes estão com o prefixo do provider errado. Recursos do Terraform são nomeados a partir do provider a que pertencem, e azurerm_ é o prefixo do provider AzureRM — o que gerencia a infraestrutura Azure (VMs, redes, AKS). As Branch Policies pertencem ao provider AzureDevOps, cujo prefixo é azuredevops_:

resource "azuredevops_branch_policy_min_reviewers" "main" { … }
resource "azuredevops_branch_policy_build_validation" "main" { … }
resource "azuredevops_branch_policy_comment_resolution" "main" { … }

O erro é Invalid resource type — o Terraform informa que o provider azurerm não suporta aquele tipo. É uma falha rápida de diagnosticar quando se conhece a regra, e confusa quando não: a mensagem aponta para o provider errado, não para o nome errado, e a reação natural é procurar problema de versão.

São dois providers distintos, com autenticação separada, e ambos são necessários em um projeto que gerencia infraestrutura e repositórios juntos:

terraform {
  required_providers {
    azurerm     = { source = "hashicorp/azurerm",     version = "~> 3.0" }
    azuredevops = { source = "microsoft/azuredevops", version = "~> 1.0" }
  }
}

provider "azuredevops" {
  org_service_url       = "https://dev.azure.com/minha-org"
  personal_access_token = var.azdo_pat
}

Repare que o provider do Azure DevOps autentica de forma própria — com PAT ou service principal contra a organização —, e não pela assinatura do Azure. São planos de controle diferentes que apenas compartilham a marca.

Vale o hábito geral: o prefixo do recurso identifica o provider, e essa é a primeira coisa a conferir ao trazer um trecho de Terraform de qualquer fonte. O mesmo cuidado evita confundir aws_ com awscc_ (o provider Cloud Control da AWS), que oferecem recursos parecidos com esquemas incompatíveis.

Exercício 2

Para as Service Connections, o artigo recomenda Workload Identity Federation — "sem client secrets com prazo de expiração". Que problema concreto isso elimina, e por que ele é especialmente difícil de diagnosticar?

Ver resposta

✓ Resposta: Elimina o pipeline que quebra sozinho no dia em que o segredo expira.

Uma Service Connection tradicional usa um service principal com client secret, e todo client secret tem validade — tipicamente de 6 meses a 2 anos. Ao vencer, todos os pipelines que dependem daquela conexão passam a falhar com AADSTS7000222: The provided client secret keys are expired.

É difícil de diagnosticar por três motivos que se somam:

  • Nada mudou. Não houve commit, merge ou alteração de configuração — o pipeline que funcionou ontem falha hoje. O instinto de investigar "o que mudou" não leva a lugar nenhum, porque o que mudou foi o calendário.
  • O prazo é longo demais para ser lembrado. Quem criou a conexão há dois anos provavelmente não está mais no time, e o vencimento não aparece em nenhum lugar que alguém consulte.
  • Atinge tudo de uma vez. Como a Service Connection é compartilhada entre projetos e pipelines, a expiração derruba todos os deploys da organização simultaneamente — e costuma ser tratada como incidente de plataforma antes de alguém pensar em credencial.

O Workload Identity Federation remove a credencial da equação: em vez de guardar um segredo, o Azure DevOps apresenta um token OIDC de vida curta emitido no momento da execução, e o Entra ID o troca por um token de acesso após verificar a confiança federada. Não há segredo armazenado, portanto não há o que expirar, vazar ou rotacionar.

É exatamente o mesmo mecanismo do Workload Identity no AKS e do IRSA no EKS, aplicado agora ao pipeline em vez do pod — e é também o que o aws-actions/configure-aws-credentials com id-token: write faz no GitHub Actions ao longo do capstone. O padrão é o mesmo em toda parte: substituir credenciais de longa duração por prova de identidade verificável no momento do uso. Onde ainda existirem conexões com client secret, vale ao menos alertar sobre o vencimento com antecedência — mas migrar é melhor que monitorar.

Exercício 3

O artigo descreve os gates automáticos: o pipeline verifica, por exemplo, que a taxa de erro em staging está abaixo de 1% antes de permitir o deploy em produção, reexecutando a checagem até a condição ser satisfeita ou o timeout expirar. Compare com a verificação pós-deploy implementada no capstone (sleep 120 seguido de consulta ao Prometheus). Qual a diferença de fundo?

Ver resposta

✓ Resposta: A diferença é onde o dano acontece. O gate é preventivo; a verificação do capstone é detectiva.

No capstone, a sequência é: deployar em produção, esperar dois minutos, consultar as métricas e — se estiverem ruins — tentar reverter. A versão defeituosa chegou aos usuários, e a única questão é por quanto tempo. Some-se que o rollback depende de um comando que, como visto, entra em conflito com o selfHeal do ArgoCD.

Com um gate, a ordem se inverte: a condição é avaliada antes de qualquer coisa entrar em produção. Se staging está com taxa de erro alta, a promoção simplesmente não ocorre — nenhum usuário é exposto, e não há o que reverter.

Há uma segunda diferença, mais sutil e igualmente importante: o gate reexecuta periodicamente até a condição ser satisfeita ou o timeout ser atingido, enquanto o capstone faz uma única medição após uma espera fixa. Um sleep 120 é um chute: dois minutos podem ser pouco para as métricas estabilizarem depois de um rollout progressivo, ou tempo demais de exposição. O gate, por ser uma condição avaliada em ciclo, dispensa adivinhar a duração — ele espera o que for necessário, dentro do limite definido.

Nada disso é exclusivo do Azure DevOps, e é essa a lição transferível: o mesmo desenho pode ser construído no GitHub Actions, com um job de verificação entre os ambientes que consulta as métricas em laço e só termina quando a condição se confirma, tendo o environment: production como aprovação subsequente. O que o Azure DevOps oferece é o mecanismo pronto e declarativo, configurado no Environment e não em código de pipeline — o que também significa que ele vale para todos os pipelines que promovem para aquele ambiente, sem depender de cada autor lembrar de implementá-lo.

A contrapartida honesta é que essa configuração vive no portal, e não no repositório: não passa por revisão de código, não aparece no git log e pode ser afrouxada por quem tiver permissão sem deixar rastro óbvio — o mesmo problema já observado com o environment do GitHub.

Exercício 4

Os jobs do tipo deployment com estratégia runOnce expõem os blocos preDeploy, deploy, routeTraffic, postRouteTraffic, on.failure e on.success. Mapeie o fluxo de deploy do capstone nesses blocos. O que se ganha ao usar a estrutura em vez de steps condicionais?

Ver resposta

✓ Resposta: O mapeamento é quase direto — o que revela que o capstone estava reimplementando à mão um ciclo de vida que já existe pronto:

blocoequivalente no capstone
preDeployclonar o repositório GitOps, validar pré-requisitos
deploykustomize edit set image + push + argocd app wait
routeTraffica promoção do tráfego para a versão nova
postRouteTrafficsleep 120 + consulta de métricas no Prometheus
on.failureo passo de rollback com if: failure()
on.successa notificação no Slack

O ganho não é sintático. São três coisas concretas:

O rollback deixa de ser opcional. No capstone, o rollback é um step comum com if: failure() && steps.verificar-metricas.outputs.rollback == 'true' — uma condição composta, fácil de escrever errado e que só é exercitada quando algo dá errado, ou seja, raramente testada. Como on.failure, ele é parte declarada do ciclo de vida: sempre executa na falha, sem depender de uma expressão booleana correta.

A intenção fica legível. Um bloco chamado postRouteTraffic declara quando aquilo roda. No capstone, saber se a verificação acontece antes ou depois do tráfego mudar exige ler a ordem dos steps e entender o comportamento do ArgoCD. A estrutura documenta o processo.

A plataforma sabe o que está acontecendo. Como o job é do tipo deployment associado a um Environment, o Azure DevOps registra o histórico de implantações — qual versão está em cada ambiente, quem aprovou, quando —, o que alimenta a tela de Environments e viabiliza rollback pela interface. Um step genérico é opaco para a plataforma.

A observação que fica é sobre o padrão, não sobre a ferramenta: quando um pipeline acumula muitos steps condicionais coordenando ordem e falha, ele está reimplementando um ciclo de vida. Vale checar se a plataforma já oferece esse ciclo — no Azure DevOps são as estratégias de deployment; no Kubernetes, o Argo Rollouts com steps, analysis e promoção automática, visto no artigo de deploys avançados.

Exercício 5

O time publica a biblioteca interna @empresa/core em um feed do Azure Artifacts, com o .npmrc apontando para o feed. Esse feed tem upstream source configurado para o npm público, para que dependências externas também passem por ele. Que risco de segurança essa configuração cria, e como mitigá-lo?

Ver resposta

✓ Resposta: Cria a exposição ao dependency confusion — um ataque de cadeia de suprimentos em que o pacote público substitui o interno.

O mecanismo é simples e por isso perigoso. Quando um feed tem upstream source, ele resolve um pacote procurando primeiro localmente e, se não encontrar, no registro público. Um atacante que descubra o nome @empresa/core — em um package.json vazado, em um repositório aberto por engano, em um log de build público — pode publicar um pacote com esse mesmo nome no npm público, com número de versão bem alto, como 99.0.0. Se a resolução considerar a versão mais recente disponível entre as fontes, o build passa a baixar o pacote do atacante, cujo postinstall roda com as credenciais do pipeline.

É o ataque que, em 2021, um pesquisador usou para executar código em ambientes de build de dezenas de grandes empresas — e a razão de ele funcionar é que não explora nenhuma falha: usa o comportamento normal do gerenciador de pacotes.

As mitigações, em ordem de eficácia:

  • Escopo reservado no registro público. Registrar a organização @empresa no npmjs.com impede que qualquer outra pessoa publique sob esse escopo. É a defesa mais forte, e é gratuita.
  • Upstream apenas para o que é externo. O Azure Artifacts permite políticas que impedem um pacote de vir do upstream quando já existe uma versão local com aquele nome — cortando a substituição na origem.
  • Lockfile respeitado no CI. Usar npm ci, e não npm install, garante que apenas as versões exatas do package-lock.json sejam instaladas, junto com os hashes de integridade. Um pacote substituído não bate o hash.
  • Escopo apontado explicitamente no .npmrc, de modo que os pacotes internos nunca sejam procurados fora:
@empresa:registry=https://pkgs.dev.azure.com/org/proj/_packaging/feed/npm/registry/
registry=https://registry.npmjs.org/

Vale notar o que a npmAuthenticate@0 faz e o que ela não faz: ela injeta no .npmrc um token com permissão de leitura e escrita no feed. Como esse token fica disponível ao processo de build, qualquer script postinstall de qualquer dependência pode lê-lo — e usá-lo para publicar pacotes maliciosos no feed interno, transformando um comprometimento pontual em persistente. É por isso que ignore-scripts no CI, com exceções explícitas, é uma prática que vale o incômodo.

Comentários

Mais em DevOps

Instalação Manual do MySQL no Debian, Arch, Fedora e openSUSE
Instalação Manual do MySQL no Debian, Arch, Fedora e openSUSE

Instalação do MySQL pelo tarball genérico da Oracle em Debian, Arch, Fedora e…

Processos, Serviços e o Comando `systemctl`
Processos, Serviços e o Comando `systemctl`

Processos e serviços no Linux: leitura de ps aux e top, busca com pgrep, a…

Escrevendo um Dockerfile do Zero
Escrevendo um Dockerfile do Zero

Escrevendo um Dockerfile do zero: o papel de cada instrução, por que a ordem…