🧠FFVAcademy
🔷

Azure DevOps Pipelines: CI/CD na Microsoft Cloud

18 min de leitura·+80 XP
Pré-requisitos (0/1)0%

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

OrganizationTop-level. Corresponde a uma empresa ou unidade.
ProjectDentro da org. Tem Repos, Boards, Pipelines, Artifacts próprios.
Agent PoolConjunto de workers que executam pipelines. Microsoft-hosted (Linux/Win/Mac) ou Self-hosted.
Service ConnectionCredencial para serviços externos: Azure, AWS, Docker registry, Kubernetes, SonarCloud.
EnvironmentAlvo de deploy (staging, prod). Tem approvals, checks, histórico.
LibraryVariable Groups + Secure Files + ligação com Key Vault.

Estrutura de um pipeline YAML

📄azure-pipelines.ymlroot do repo
Trigger + variables + stages. Versionado junto do código.
evento
🎯trigger / pr / scheduleswhen
branches, paths, schedules (cron), pull_request.
executa
🏛️stagesfases
Build → Test → Deploy_Staging → Deploy_Prod. Cada um com seu environment.
contém
🧩jobsparalelo
Unidades que rodam em um agent. dependsOn orquestra.
roda
🔢stepssequencial
script, task, checkout, template. Em ordem no mesmo agent.
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: app

Deploy em AKS com stage + approval

yaml
- 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 nativo: o tipo 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

ModoComo funcionaRecomendação
Azure Resource Manager (auto)Azure DevOps cria Service Principal automaticamenteEvite — secret estático de 2 anos
Service Principal manualVocê cria SP no Azure AD, coloca client secret no ADOLegado — rotaciona à mão
Managed IdentityPipeline usa identidade do agente self-hosted em Azure VMÓtimo para self-hosted em Azure
Workload Identity FederationOIDC: ADO emite token por run; Azure AD valida trust (org/project/pipeline) e dá STS🏆 Padrão moderno — zero secret
yaml
# 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 -A

Variable Groups + Key Vault

Library → Variable GroupNome + pares chave/valor. Marcar cadeado = secret (não visível depois de salvo).
Link com Azure Key VaultVariable Group pode apontar pra um KV. Valores atualizam automaticamente; nada fica no ADO.
PermissãoPor Variable Group. Sem acesso, pipeline falha ao requisitar — evita vazamento cruzado entre times.
Uso no YAMLvariables: [{ group: shared-config }, { name: SENTRY_DSN, value: "$(SENTRY_DSN)" }]
Secure FilesArquivos (certificados, kubeconfig) que o pipeline baixa com DownloadSecureFile@1.
⚠️
Nunca use 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

yaml
# 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' }
yaml
# 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: true

Environments, checks e approvals

EnvironmentEntidade em Pipelines → Environments. Tem histórico de deploys e status.
ApprovalsLista de approvers (pessoas ou grupos AAD). Pipeline pausa até aprovação.
Business Hours checkDeploy só permitido em janela (ex.: 9h-18h dias úteis). Evita deploy de sexta à noite acidental.
Invoke REST API checkChama um endpoint externo (PagerDuty, ServiceNow) e só prossegue com status OK.
Exclusive LockApenas um deploy por vez no mesmo environment — evita corrida.
Branch controlDeploy no environment "prod" só aceita de refs/heads/main.

Agents: Microsoft-hosted vs Self-hosted

DimensãoMicrosoft-hostedSelf-hosted
SetupZero — usa vmImage: ubuntu-latestInstala agent em VM/container/K8s
CustoFree tier limitado; paralelismo compradoSua infra
PerformanceVM compartilhada, sem estadoDisco persistente, cache local acelera
RedePúblicoAcessa VPN/VNet — necessário pra recursos privados
FerramentasMudam por imagem (upgrades anuais)Você escolhe
Quando usarPipelines públicos ou sem rede privadaOn-prem, integração com Azure private link

Monorepo, path filters e matriz

yaml
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 test

Segurança e governança

Branch policiesRepos → Branches → branch policy: exige PR, build success, minimum reviewers, linked work item.
Build validationPolicy que dispara pipeline no PR e bloqueia merge se falhar.
Project-level permissionsGroups: Readers, Contributors, Build Admins, Project Admins. Use grupos Azure AD, não users.
Approver ≠ autorEnvironment check "require approvers from specific group" e policy do repo "disallow self-approval".
Secret rotationWIF elimina. Se precisar Service Principal, rode playbook de rotação mensal via script automatizado.
AuditoriaAuditing API do ADO. Exporte pra SIEM — quem aprovou deploy, quem mudou environment, quem editou pipeline.

Azure DevOps vs GitHub Actions — o dilema Microsoft

AspectoAzure DevOpsGitHub Actions
FocoSuíte ALM completa (Boards, Repos, Pipelines, Test, Artifacts)CI/CD dentro do GitHub
GovernançaEnvironments, approvals, checks, área/iteration pathsEnvironments + 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 prontasBoa (actions oficiais), mas menos profunda
YAMLMais verboso, stages explícitosMais enxuto, eventos amplos
Estratégia MicrosoftMaintenance mode — evolui pouco🏆 Roadmap ativo (AI, Copilot)
Startup/greenfieldRaro hojeDefault
Enterprise Microsoft🏆 Onde está investimento existenteAlvo de migração futura
💡
A Microsoft tem declarado que GitHub Actions é a plataforma estratégica e Azure DevOps está em modo de manutenção (sem novas features grandes). Para projetos novos, GH Actions com Azure OIDC é o caminho. Para times já investidos em ADO, continua valendo a pena — migração é trabalho de meses.

Decisões

📋 Empresa Microsoft-shop com AD, AKS, App Service e 40 pipelines classic

Modernizar para YAML + WIF + templates

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 Actionsvale se já tiver decisão estratégica — aí planeje 6-12 meses.

📋 Startup usando Azure mas começando do zero

GitHub Actions + Azure OIDC

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 completosó se quiser a suíte ALM integrada (Boards + Repos + Pipelines).

📋 Pipeline com aprovação rigorosa (Sarbanes-Oxley), múltiplos gates

Azure DevOps Environments

Checks como Business Hours, Invoke REST, Exclusive Lock, e Required Approvers com grupos AAD atendem compliance sem script caseiro.

Alt: GH Actions + action custompossível mas exige código e manutenção próprios.

Perguntas típicas

Classic pipeline ou YAML?

YAML, sempre. Classic é legado. Export to YAML na UI gera um esqueleto — você limpa e versiona no repo. Código revisado = menos bugs em pipeline.

Posso disparar um pipeline a partir de outro?

Sim, 3 formas: (1) 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?

Use 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?

Sim. 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?

Stages: init → validate → plan → (approval) → apply. WIF pra autenticar na Azure. Estado remoto em Storage Account com lock. Bicep tem vantagem no stack Microsoft por ser nativo; Terraform quando multi-cloud.
Take-aways.(1) Azure DevOps = 5 produtos; Pipelines é o CI/CD. (2) YAML versionado > classic clicado. (3) Stages representam fases de entrega com environments, approvals e histórico — diferencial forte vs GH Actions. (4) Workload Identity Federation zera secrets estáticos — migre hoje. (5) Variable Group + Key Vault centraliza config e secret com rotação automática. (6) Templates + repos externos = reuso DRY com pin de versão. (7) Microsoft sinalizou GH Actions como futuro estratégico — planeje migração em horizonte de 1-3 anos se for greenfield, mantenha e modernize se for legado.
🧩

Quiz rápido

4 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito

Continue lendo