Azure DevOps Pipelines: CI/CD na Microsoft Cloud
- ⬜🐙 GitHub Actions: CI/CD profissional do zero(DevOps & Containers)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
Azure DevOps é o guarda-chuva da Microsoft com 5 produtos: Boards (kanban/scrum), Repos (Git hosting), Pipelines (CI/CD), Test Plans (QA), Artifacts (package registry). Este módulo foca em Azure Pipelines— o motor de CI/CD, que evoluiu de “classic pipelines” (UI clicada) para YAML pipelines versionados no repo. Se seu stack é Microsoft (Azure AD, AKS, App Service, SQL Server), ou seu time já usa Azure Boards/Repos, é o caminho de menor atrito. A Microsoft também tem GitHub (compram em 2018), então GH Actions é a aposta estratégica de longo prazo — mas Azure Pipelines continua tendo diferenciais fortes em gate de aprovação, governança e integração com Azure.
Azure DevOps em 30 segundos
Estrutura de um pipeline YAML
# azure-pipelines.yml — pipeline real de uma API Node
trigger:
branches:
include: [main, release/*]
paths:
exclude: [docs/*, README.md]
pr:
branches:
include: [main]
variables:
- group: 'shared-config' # Variable Group da Library
- name: NODE_VERSION
value: '20.x'
pool:
vmImage: 'ubuntu-latest'
stages:
- stage: Build
jobs:
- job: build_test
displayName: 'Build & Test'
steps:
- task: NodeTool@0
inputs: { versionSpec: $(NODE_VERSION) }
- task: Cache@2
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: $(Pipeline.Workspace)/.npm
- script: npm ci --cache $(Pipeline.Workspace)/.npm
displayName: 'Install deps'
- script: npm run lint
displayName: 'Lint'
- script: npm test -- --ci --coverage
displayName: 'Test'
- task: PublishTestResults@2
condition: succeededOrFailed()
inputs: { testResultsFiles: 'junit.xml' }
- task: PublishCodeCoverageResults@2
inputs:
summaryFileLocation: 'coverage/cobertura-coverage.xml'
- script: npm run build
- publish: dist
artifact: appDeploy em AKS com stage + approval
- stage: Deploy_Staging
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
variables:
- group: aks-staging # conn string, KV secrets
jobs:
- deployment: deploy_aks
environment: 'staging.aks' # environment com approval/checks
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: app
- task: KubernetesManifest@1
displayName: 'Deploy to AKS'
inputs:
action: 'deploy'
kubernetesServiceConnection: 'aks-staging-connection'
namespace: 'api'
manifests: |
k8s/deployment.yaml
k8s/service.yaml
containers: |
acrmeusite.azurecr.io/api:$(Build.BuildId)
- stage: Deploy_Prod
dependsOn: Deploy_Staging
jobs:
- deployment: deploy_prod
environment: 'production.aks' # environment exige 2 approvers
strategy:
canary:
increments: [10, 50, 100]
preDeploy:
steps:
- script: echo "Starting canary rollout"
deploy:
steps:
- task: KubernetesManifest@1
inputs:
action: 'deploy'
kubernetesServiceConnection: 'aks-prod-connection'
namespace: 'api'
manifests: k8s/deployment.yaml
containers: acrmeusite.azurecr.io/api:$(Build.BuildId)
postRouteTraffic:
steps:
- script: ./scripts/smoke-test.sh $(canary.percentage)
on:
failure:
steps:
- script: echo "Rollback automático"
success:
steps:
- script: echo "Canary $(canary.percentage)% OK"canary da strategy faz rollout por porcentagem, executa smoke test entre incrementos, e reverte se falhar. GH Actions e Jenkins precisam de plugin ou script caseiro pra mesma coisa.Service Connections e Workload Identity Federation
| Modo | Como funciona | Recomendação |
|---|---|---|
| Azure Resource Manager (auto) | Azure DevOps cria Service Principal automaticamente | Evite — secret estático de 2 anos |
| Service Principal manual | Você cria SP no Azure AD, coloca client secret no ADO | Legado — rotaciona à mão |
| Managed Identity | Pipeline usa identidade do agente self-hosted em Azure VM | Ótimo para self-hosted em Azure |
| Workload Identity Federation | OIDC: ADO emite token por run; Azure AD valida trust (org/project/pipeline) e dá STS | 🏆 Padrão moderno — zero secret |
# Com WIF, o YAML não muda muito — a mágica é na Service Connection
- task: AzureCLI@2
inputs:
azureSubscription: 'prod-wif' # Service Connection configurada como WIF
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az aks get-credentials -n prod-cluster -g prod-rg
kubectl get pods -AVariable Groups + Key Vault
echo $(DB_PASSWORD). ADO masca variáveis declaradas secret, mas derivados (base64, JSON) escapam. Em logs de falha, erros de ferramentas podem despejar env — use ##[command]com parcimônia e configure retention curto pra logs sensíveis.Templates — reuso em escala
# templates/node-build.yml (no repo pipelines-templates)
parameters:
- name: nodeVersion
type: string
default: '20.x'
- name: runCoverage
type: boolean
default: true
steps:
- task: NodeTool@0
inputs: { versionSpec: ${{ parameters.nodeVersion }} }
- script: npm ci
- script: npm run lint
- script: npm test -- --ci
displayName: 'Test'
- ${{ if eq(parameters.runCoverage, true) }}:
- task: PublishCodeCoverageResults@2
inputs: { summaryFileLocation: 'coverage/cobertura-coverage.xml' }# pipeline consumidor
resources:
repositories:
- repository: templates
type: git
name: infra/pipelines-templates
ref: refs/tags/v2.1.0 # pin de versão
jobs:
- job: build
steps:
- template: templates/node-build.yml@templates
parameters:
nodeVersion: '22.x'
runCoverage: trueEnvironments, checks e approvals
Agents: Microsoft-hosted vs Self-hosted
| Dimensão | Microsoft-hosted | Self-hosted |
|---|---|---|
| Setup | Zero — usa vmImage: ubuntu-latest | Instala agent em VM/container/K8s |
| Custo | Free tier limitado; paralelismo comprado | Sua infra |
| Performance | VM compartilhada, sem estado | Disco persistente, cache local acelera |
| Rede | Público | Acessa VPN/VNet — necessário pra recursos privados |
| Ferramentas | Mudam por imagem (upgrades anuais) | Você escolhe |
| Quando usar | Pipelines públicos ou sem rede privada | On-prem, integração com Azure private link |
Monorepo, path filters e matriz
trigger:
branches: { include: [main] }
paths:
include:
- apps/api/**
- libs/**
- azure-pipelines-api.yml
jobs:
- job: test_matrix
strategy:
matrix:
node18_linux:
NODE: '18.x'
VMIMG: 'ubuntu-latest'
node20_linux:
NODE: '20.x'
VMIMG: 'ubuntu-latest'
node20_windows:
NODE: '20.x'
VMIMG: 'windows-latest'
pool:
vmImage: $(VMIMG)
steps:
- task: NodeTool@0
inputs: { versionSpec: $(NODE) }
- script: npm ci
- script: npm testSegurança e governança
Azure DevOps vs GitHub Actions — o dilema Microsoft
| Aspecto | Azure DevOps | GitHub Actions |
|---|---|---|
| Foco | Suíte ALM completa (Boards, Repos, Pipelines, Test, Artifacts) | CI/CD dentro do GitHub |
| Governança | Environments, approvals, checks, área/iteration paths | Environments + protection rules (mais simples) |
| Gate de aprovação | 🏆 Flexível (REST check, business hours, exclusive lock) | Básico (required reviewers, wait) |
| Integração Azure | 🏆 Nativa, WIF, tasks prontas | Boa (actions oficiais), mas menos profunda |
| YAML | Mais verboso, stages explícitos | Mais enxuto, eventos amplos |
| Estratégia Microsoft | Maintenance mode — evolui pouco | 🏆 Roadmap ativo (AI, Copilot) |
| Startup/greenfield | Raro hoje | Default |
| Enterprise Microsoft | 🏆 Onde está investimento existente | Alvo de migração futura |
Decisões
📋 Empresa Microsoft-shop com AD, AKS, App Service e 40 pipelines classic
Classic → YAML é conversão direta com Export to YAML. Introduzir WIF zera secrets. Templates centralizam padrão. Ganho sem mudar de plataforma.
Alt: Migrar pra GH Actions — vale se já tiver decisão estratégica — aí planeje 6-12 meses.
📋 Startup usando Azure mas começando do zero
Ir direto pro futuro. GH Actions + OIDC para AKS/ACR é setup de 1 dia, e você não cria dívida de Azure Boards que ninguém mais usa.
Alt: Azure DevOps completo — só se quiser a suíte ALM integrada (Boards + Repos + Pipelines).
📋 Pipeline com aprovação rigorosa (Sarbanes-Oxley), múltiplos gates
Checks como Business Hours, Invoke REST, Exclusive Lock, e Required Approvers com grupos AAD atendem compliance sem script caseiro.
Alt: GH Actions + action custom — possível mas exige código e manutenção próprios.
Perguntas típicas
❓ Classic pipeline ou YAML?
❓ Posso disparar um pipeline a partir de outro?
resources.pipelines — pipeline B observa pipeline A e dispara quando A completa; (2) REST API — az pipelines run; (3) trigger no repo — mesmo repo consumido por múltiplos pipelines.❓ Como compartilhar artefato entre stages?
publish e download (ou PublishPipelineArtifact@1 / DownloadPipelineArtifact@2). Stages posteriores podem baixar artefatos produzidos por stages anteriores, mesmo em jobs diferentes.❓ Dá para rodar pipeline só em PR que muda uma pasta?
pr.paths.include filtra por caminho. Útil em monorepos: pipeline da API só roda se mudou apps/api/** ou o próprio YAML.❓ Como fazer IaC (Terraform/Bicep) no Azure Pipelines?
Quiz rápido
4 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito