MÓDULO 12 · CONCEITO 04 DE 12

CI/CD Pipelines — GitHub Actions, trunk-based e deployment seguro

Trunk-based development como pré-requisito. GitHub Actions: workflows, environments, OIDC para cloud auth. Pipeline completo: lint → test → SAST → build → push → deploy. Feature flags. DORA metrics.

Tempo de leitura ~22 min Pré-requisito 03 · Kubernetes em Produção · Módulo 11 · Segurança (OIDC) Próximo 05 · GitOps →

Em 2020, o time de engenharia do Etsy publicou uma análise de seus 10.000 deploys por ano — cerca de 27 deploys por dia em produção. Não era caos: era o resultado de uma filosofia de entrega contínua que data de 2011, quando a empresa pioneirou o conceito de "deploy everything, anytime". O insight central: deploys frequentes são mais seguros que deploys raros, porque cada deploy é menor, o diff de mudança é menor, e quando algo dá errado o rollback é mais simples. A ansiedade em torno de deploys é sintoma de um processo ruim — não de risco inerente ao deploy em si.

CI/CD (Continuous Integration / Continuous Delivery) é o conjunto de práticas e ferramentas que tornam esse volume de deploys possível e seguro. Mas CI/CD sem as práticas de engenharia corretas é automação de caos: se o código tem branches de longa duração, testes lentos ou ausentes, e deploys manuais, automatizar esse processo só acelera os problemas.

Trunk-based development — o pré-requisito

CI/CD real — onde cada commit no branch principal pode ir para produção — requer que o time trabalhe em trunk-based development (TBD): todos os desenvolvedores integram commits pequenos e frequentes diretamente ao branch principal (main), com branches de curta duração (horas, não dias).

O argumento contra TBD é "e features incompletas?". A resposta é feature flags: o código entra em produção, mas a feature está desabilitada para usuários até estar pronta. O código fica integrado, testado, e sem conflitos de merge — a feature é ativada por configuração quando estiver completa.

Branching de longa duração (GitFlow com release branches, develop branch) cria os problemas que CI/CD supostamente resolve:

GitHub Actions — estrutura completa

GitHub Actions é baseado em workflows YAML que definem quando rodar (on), quais jobs executar (jobs), e os steps de cada job. Um pipeline completo para um serviço containerizado:

# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'
          cache: true

      - name: Lint
        uses: golangci/golangci-lint-action@v6
        with:
          version: latest

      - name: Test com cobertura
        run: go test -race -coverprofile=coverage.out ./...

      - name: Upload cobertura
        uses: codecov/codecov-action@v4
        with:
          files: ./coverage.out

  sast:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - uses: actions/checkout@v4
      - name: CodeQL Analysis
        uses: github/codeql-action/analyze@v3
        with:
          languages: go

  build-and-push:
    needs: [lint-and-test, sast]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write
      id-token: write    # para OIDC cloud auth
    outputs:
      image-digest: ${{ steps.build.outputs.digest }}
    steps:
      - uses: actions/checkout@v4

      - name: Login no GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build e push com digest
        id: build
        uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Assinar imagem com Cosign (Sigstore)
        uses: sigstore/cosign-installer@v3
        run: |
          cosign sign --yes \
            ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

OIDC para autenticação cloud — sem chaves permanentes

O padrão de armazenar AWS access keys ou GCP service account JSON como secrets no repositório é um risco de segurança conhecido: se o repositório vazar, as chaves vazam. O padrão moderno usa OIDC (OpenID Connect) para autenticação federada:

  1. O GitHub Actions emite um JWT OIDC assinado pelo GitHub para cada job
  2. A AWS (ou GCP/Azure) valida o JWT contra o OIDC provider do GitHub
  3. Se válido, assume a IAM role configurada e emite credenciais temporárias
  4. As credenciais expiram ao fim do job — nunca ficam armazenadas
# IAM Role Trust Policy (Terraform) para OIDC com GitHub Actions
resource "aws_iam_role" "github_actions" {
  name = "github-actions-deploy"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = {
        Federated = "arn:aws:iam::${var.account_id}:oidc-provider/token.actions.githubusercontent.com"
      }
      Action = "sts:AssumeRoleWithWebIdentity"
      Condition = {
        StringEquals = {
          "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
        }
        StringLike = {
          # Restringir ao repositório e branch específicos
          "token.actions.githubusercontent.com:sub" = "repo:minha-org/meu-repo:ref:refs/heads/main"
        }
      }
    }]
  })
}

# No workflow GitHub Actions
- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789:role/github-actions-deploy
    aws-region: us-east-1

Environments e aprovação manual

GitHub Environments permitem configurar regras de proteção para deploys — como aprovação manual de um reviewer antes de aplicar a produção:

jobs:
  deploy-staging:
    environment: staging   # sem proteção — deploy automático
    steps: [...]

  deploy-prod:
    needs: deploy-staging
    environment: producao   # configurado com required reviewers no GitHub UI
    steps:
      - name: Deploy para produção
        run: kubectl set image deployment/meu-app meu-app=ghcr.io/...${{ needs.build.outputs.image-digest }}

O workflow pausa no job deploy-prod até que um reviewer aprove no GitHub UI. Isso cria uma barreira humana para produção sem quebrar a automação do restante do pipeline.

Feature flags como substituto de branches de release

Feature flags (também chamados feature toggles) permitem que código incompleto entre em produção mas fique inativo para usuários. Existem vários tipos:

// Go — integração simples com LaunchDarkly
import "github.com/launchdarkly/go-server-sdk/v7"

func handleCheckout(user User) {
    flagValue, _ := ldClient.BoolVariation("nova-pagina-checkout", user.ToLDContext(), false)
    if flagValue {
        return renderNovaCheckout(user)
    }
    return renderCheckoutAntigo(user)
}

Feature flags introduzem dívida técnica se não forem removidos após a feature estar completamente deployed. A regra é: cada feature flag tem um ticket de limpeza criado junto com ela, com data de expiração. Flags antigas são mais perigosos que bugs — eles tornam o código impossível de entender.

Pipeline de segurança — SAST, dependency scan e container scan

Um pipeline de qualidade para produção inclui etapas além de testes:

# Exemplo de pipeline de segurança completo
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # SAST — análise estática do código fonte
      - name: Semgrep SAST
        uses: semgrep/semgrep-action@v1
        with:
          config: p/golang p/owasp-top-ten

      # Dependency scan — vulnerabilidades em bibliotecas
      - name: Govulncheck
        run: |
          go install golang.org/x/vuln/cmd/govulncheck@latest
          govulncheck ./...

      # Container scan — vulnerabilidades na imagem
      - name: Trivy scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'ghcr.io/${{ github.repository }}:${{ github.sha }}'
          exit-code: '1'    # falha o pipeline se houver HIGH ou CRITICAL
          severity: 'HIGH,CRITICAL'

DORA metrics — medindo a saúde do pipeline

O DORA Research Program (DevOps Research and Assessment) identificou quatro métricas que discriminam times de alta performance de times de baixa performance:

Métrica Elite Alta Média Baixa
Deployment Frequency Múltiplos/dia 1/dia a 1/semana 1/semana a 1/mês >1/mês
Lead Time for Changes <1 hora 1 dia a 1 semana 1 semana a 1 mês >1 mês
Change Failure Rate 0-5% 5-10% 10-15% >15%
MTTR (Mean Time to Restore) <1 hora <1 dia 1 dia a 1 semana >1 semana

Esses números são contraintuitivos: times que fazem mais deploys têm menor change failure rate — não maior. A relação causal é que deployers frequentes têm deploys menores, que têm menos superfície de erro, e que são mais fáceis de rollback quando falham.

Lead time para changes é o tempo desde o commit até estar em produção. Um lead time de semanas indica branches de longa duração, processo de review lento, ou pipeline manual. Um lead time de horas indica TBD com CI/CD automatizado.

o que realmente importa

CI/CD não é sobre a ferramenta (GitHub Actions, GitLab CI, Jenkins, CircleCI — todas são equivalentes para a maioria dos casos). É sobre as práticas de engenharia que a ferramenta serve. Trunk-based development, feature flags, testes rápidos e confiáveis, e cultura de deploy frequente são o que produz as métricas DORA de elite. A ferramenta executa essas práticas — não as substitui.

Decisões de engenharia

GitHub Actions vs GitLab CI vs self-hosted
GitHub Actions quando o repositório está no GitHub — integração nativa, marketplace rico, OIDC funciona out-of-the-box com AWS/GCP/Azure. GitLab CI quando o repositório está no GitLab com CI/CD nativo e necessidade de self-hosted runners em rede privada. Self-hosted (Jenkins, Buildkite) apenas quando há requisitos de compliance que proíbem execução em cloud pública, ou quando os runners precisam de hardware especializado (GPU para ML, acesso a hardware físico).
OIDC vs secrets permanentes para cloud auth
OIDC sempre que o provedor cloud suportar (AWS, GCP, Azure — todos suportam). Credenciais temporárias expiram ao fim do job, não ficam armazenadas, e a blast radius de um vazamento de token OIDC é zero. Secrets permanentes (AWS access keys, GCP service account JSON) apenas para provedores que não suportam OIDC ou para integrações legadas onde não é possível configurar o trust policy. Nunca rotacione manualmente chaves permanentes esperando que isso resolva o risco estrutural.
Trunk-based development vs GitFlow
TBD para times que querem CI/CD real com deploy frequente — é pré-requisito para lead time abaixo de 1 dia. GitFlow (com release branches e develop separado) apenas para produtos com múltiplas versões em manutenção simultânea — ex: biblioteca OSS que mantém v1.x, v2.x e v3.x em paralelo com patches de segurança em versões antigas. Para SaaS ou serviços web onde só existe "a versão mais recente", GitFlow adiciona complexidade sem benefício e piora todas as métricas DORA.
Feature flags: quando usar e quando não usar
Use feature flags para: release toggles (feature incompleta em produção, ativada quando pronta), experiment toggles (A/B test ou rollout gradual), ops toggles (kill switch para feature problemática sem deploy). Não use para: configuração que deveria estar em env vars, substituir testes de integração, ou contornar revisão de código. Flag antiga sem dono é pior que bug — torna o código impossível de raciocinar. Toda flag deve ter ticket de limpeza criado junto, com data de expiração.

Perguntas de entrevista

Por que deploys mais frequentes tendem a ter menor taxa de falha, e não maior?

A intuição de que "mais deploys = mais risco" é inversa à realidade empiricamente observada pelo DORA research. Deploys frequentes são menores em escopo — cada deploy carrega mudanças de horas, não de semanas. Um diff menor tem menos superfície de erro: menos interações inesperadas entre mudanças, mais fácil de identificar qual linha introduziu o bug, e rollback trivial porque a distância entre "antes" e "depois" é pequena.

Deploys raros, por contraste, acumulam mudanças de muitos engenheiros ao longo de semanas. Quando algo falha — e em deploys grandes, algo quase sempre falha — o diagnóstico é difícil (qual das 400 mudanças?), o rollback é custoso (reverte 400 mudanças, algumas já dependências de downstream), e o stress é alto. Times que sofrem com deploys dolorosos tendem a fazê-los menos — criando um ciclo vicioso onde o próprio medo do deploy piora as métricas.

A solução é tratamento contínuo: TBD com feature flags garante que cada deploy seja pequeno; pipeline automatizado garante que cada deploy é testado; cultura de deploys frequentes cria o músculo de resposta a incidentes.

O que é OIDC no contexto de CI/CD e por que é superior a armazenar credenciais cloud como secrets?

OIDC (OpenID Connect) é um protocolo de autenticação federada que permite que um sistema (GitHub Actions) prove sua identidade para outro (AWS) sem trocar credenciais permanentes. O GitHub emite um JWT assinado para cada job, contendo claims como o repositório, o branch, e o evento que disparou o job. A AWS valida esse JWT contra o OIDC provider público do GitHub e, se as condições do trust policy baterem, emite credenciais temporárias via STS.

A superioridade é estrutural: credenciais temporárias têm TTL de minutos a horas e expiram automaticamente — mesmo que um token seja capturado por um atacante, ele será inútil em breve. Não há secret para vazar: nenhum arquivo de configuração, nenhum repositório comprometido, nenhum desenvolvedor que recebe acesso ao secret. O trust policy restringe o escopo por repositório e branch — um fork do repositório não pode assumir a mesma IAM role.

AWS access keys permanentes, por contraste, não expiram a menos que sejam rotacionadas manualmente, têm scope amplo, e aparecem em logs, builds e histórico de git quando alguém comete o erro clássico de commitar credenciais.

Como feature flags permitem trunk-based development sem expor features incompletas para usuários?

Feature flags separam o deploy do release: o código pode ir para produção (deploy) sem que a feature seja ativada para usuários (release). Isso resolve o problema central do TBD — features demoram mais de um dia para ficar prontas, mas TBD exige commits diários ao trunk.

O padrão é simples: a feature nova é envolta em uma condicional que checa o valor do flag. Enquanto o flag está desabilitado (default: false), todos os usuários veem o comportamento antigo. O código da feature nova vive em produção, é executado nos testes de integração, e não tem conflitos de merge com o trabalho de outros desenvolvedores. Quando a feature está completa e testada, o flag é ativado — sem deploy.

O sistema de flags pode ser simples (env var, banco de dados) ou sofisticado (LaunchDarkly, Unleash) para suportar rollout gradual (5% → 20% → 100%), targeting por usuário ou segmento, e kill switches de emergência. O custo é manutenção dos flags: toda flag deve ter data de expiração e ser removida quando a feature está 100% deployed.

Quais são as quatro métricas DORA, o que cada uma mede, e por que elas são consideradas indicadores de saúde organizacional?

Deployment Frequency mede com que frequência o time faz deploys para produção. Times elite fazem múltiplos deploys por dia; times de baixa performance fazem menos de um por mês. É indicador direto de trunk-based development e pipeline automatizado.

Lead Time for Changes mede o tempo desde o primeiro commit até o código estar em produção. Times elite têm lead time abaixo de 1 hora; times de baixa performance levam mais de um mês. É indicador de process overhead: code review lento, pipeline demorado, aprovações manuais, branches de longa duração.

Change Failure Rate mede a porcentagem de deploys que causam incidentes ou requerem rollback. Times elite ficam abaixo de 5%; times de baixa performance acima de 15%. Contraintuitivamente, times com alta deployment frequency têm menor failure rate — deploys menores têm menos superfície de erro.

MTTR (Mean Time to Restore) mede quanto tempo leva para recuperar de um incidente. Times elite resolvem em menos de 1 hora; times de baixa performance levam mais de 1 semana. É indicador de observabilidade, runbooks, e cultura de resposta a incidentes. O DORA research mostra que as quatro métricas são correlacionadas — times que melhoram uma tendem a melhorar todas.

O que um engenheiro sênior deve verificar ao fazer code review de um novo arquivo de workflow GitHub Actions?

Permissões de token: O workflow usa permissions: read-all por padrão (que é perigoso) ou declara permissões mínimas explicitamente? Cada job deveria ter apenas as permissões necessárias — contents: read, packages: write, id-token: write apenas quando necessário.

Pinning de actions: As actions externas usam SHA hash (actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29) ou tag (actions/checkout@v4)? Tags podem ser movidas — um atacante que compromete a conta do criador da action pode alterar o que v4 aponta. SHA hash é imutável.

Secrets vs OIDC: O workflow armazena credenciais cloud como secrets (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) ou usa OIDC? Se usa secrets permanentes para cloud, isso deve ser refatorado.

Escopo do trigger: O workflow roda em pull_request de forks? Workflows que executam com permissões elevadas em PRs de forks são um vetor de attack — um fork malicioso pode exfiltrar secrets. O padrão seguro é separar CI (que roda em PRs de forks com permissões mínimas) de CD (que roda apenas em push ao main).

Aprovação para produção: O deploy para produção requer aprovação manual via GitHub Environment, ou é automático? Para ambientes de produção, a barreira humana é uma camada de segurança importante.

Exercícios práticos

01 · Pipeline GitHub Actions completo para serviço Go

Crie um repositório com um serviço Go simples (HTTP handler com um endpoint) e configure um workflow GitHub Actions com os jobs em sequência: lint (golangci-lint), test (go test -race com coverage), sast (CodeQL), build-and-push (docker/build-push-action para ghcr.io com cache GHA). Configure o build-and-push para rodar apenas em push ao main (não em PRs). Use needs para dependências corretas entre jobs.

Critério: Um PR abre e executa lint + test + sast em paralelo; um push ao main executa todos os jobs em sequência e publica a imagem em ghcr.io com a tag do SHA do commit.
02 · Configurar OIDC entre GitHub Actions e AWS

Configure autenticação OIDC entre GitHub Actions e AWS sem usar access keys permanentes. Crie via Terraform: o OIDC provider do GitHub na conta AWS, uma IAM role com trust policy restrita ao seu repositório e branch main (usando StringLike no claim sub), e uma IAM policy com permissões mínimas (ex: S3 read-only em um bucket específico). No workflow, use aws-actions/configure-aws-credentials@v4 com role-to-assume.

Critério: O job executa aws s3 ls s3://meu-bucket com sucesso sem que nenhuma variável AWS_ACCESS_KEY_ID ou AWS_SECRET_ACCESS_KEY esteja configurada como secret no repositório.
03 · Implementar feature flag com rollout gradual

Em um serviço web existente, identifique uma feature que poderia ser lançada de forma gradual. Implemente o toggle usando uma biblioteca de feature flags (Unleash, Flipt, ou LaunchDarkly em trial). Configure o flag para: desabilitado por padrão, habilitado para 10% dos usuários (baseado em user ID ou IP), com possibilidade de habilitar/desabilitar via dashboard sem deploy. Crie o ticket de limpeza com data de expiração de 30 dias.

Critério: É possível alterar o percentual de rollout do flag em produção sem nenhum deploy de código; o serviço registra em log qual variante cada requisição recebeu para auditoria do experimento.
04 · Environments com aprovação manual e rollback automatizado

Configure GitHub Environments com dois ambientes: staging (sem proteção — deploy automático em push ao main) e production (com required reviewers). No job de deploy para produção, adicione um smoke test pós-deploy que verifica o health endpoint do serviço. Se o smoke test falhar, execute rollback automático para a versão anterior (image digest anterior commitado no manifesto). Registre o resultado do deploy (success/failure) como GitHub Deployment.

Critério: Um push ao main deploys automaticamente para staging; o deploy para production pausa aguardando aprovação; se o smoke test falhar após deploy em production, o rollback é executado automaticamente sem intervenção humana.
05 · Medir e melhorar métricas DORA de um repositório real

Usando a API do GitHub (ou a ferramenta four-keys do Google DORA), colete dados de um repositório real dos últimos 90 dias e calcule as quatro métricas DORA: deployment frequency, lead time for changes, change failure rate e MTTR. Identifique a métrica mais fraca e proponha (e implemente se possível) uma mudança de processo ou pipeline que a melhore — ex: se lead time é alto, identifique o job mais lento no pipeline e otimize com caching ou paralelização.

Critério: Documento com as quatro métricas calculadas e o benchmark DORA correspondente (elite/alta/média/baixa); para a métrica escolhida para melhoria, comparação antes/depois com dados reais coletados.

Referências

  1. book Nicole Forsgren et al. — Accelerate: The Science of Lean Software and DevOps IT Revolution · 2018 · a pesquisa DORA original sobre métricas de performance de entrega de software
  2. book Jez Humble & David Farley — Continuous Delivery Addison-Wesley · 2010 · a obra que definiu os princípios de entrega contínua
  3. book Gene Kim et al. — The DevOps Handbook IT Revolution · 2016 · práticas de DevOps para organizações de tecnologia
  4. docs GitHub Actions — Using OIDC with reusable workflows docs.github.com · guia oficial de autenticação OIDC com provedores cloud
  5. docs GitHub Actions — Using environments for deployment docs.github.com · configuração de environments com required reviewers e deployment protection rules
  6. article Martin Fowler — Feature Toggles (aka Feature Flags) martinfowler.com · 2016 · taxonomia completa de feature flags e quando usar cada tipo
  7. article Paul Hammond & John Allspaw — 10+ Deploys Per Day: Dev and Ops Cooperation at Flickr Velocity Conference 2009 · a talk que originou o movimento DevOps
  8. article Martin Fowler — Trunk Based Development martinfowler.com · 2023 · por que trunk-based development é pré-requisito para CI real
  9. article Google DORA — State of DevOps Report 2023 dora.dev · relatório anual de performance de entrega de software com dados de 36.000 profissionais
  10. docs Semgrep — Static Analysis Rules for CI/CD semgrep.dev · regras de SAST prontas para uso em pipelines de CI
  11. docs Sigstore — Cosign: container signing and verification docs.sigstore.dev · assinatura de imagens de container com keyless signing via OIDC
  12. article AWS — Using OIDC with GitHub Actions to authenticate to AWS aws.amazon.com/blogs · guia prático de configuração de OIDC trust policy para GitHub Actions