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:
- Merge conflicts acumulados por dias de trabalho divergente
- Integração de código acontece tarde — problemas são descobertos tarde
- O branch de release vira um buffer de risco, não uma garantia de qualidade
- Deploys raramente — cada deploy carrega mudanças de semanas
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:
- O GitHub Actions emite um JWT OIDC assinado pelo GitHub para cada job
- A AWS (ou GCP/Azure) valida o JWT contra o OIDC provider do GitHub
- Se válido, assume a IAM role configurada e emite credenciais temporárias
- 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:
- Release toggle: habilita a feature para todos quando estiver pronta
- Experiment toggle: A/B test — mostra para X% dos usuários
- Ops toggle: desabilita feature durante incidente sem deploy
- Permission toggle: habilita para usuários beta ou premium
// 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.
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
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
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.
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.
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.
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.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.
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.
Referências
- book Nicole Forsgren et al. — Accelerate: The Science of Lean Software and DevOps
- book Jez Humble & David Farley — Continuous Delivery
- book Gene Kim et al. — The DevOps Handbook
- docs GitHub Actions — Using OIDC with reusable workflows
- docs GitHub Actions — Using environments for deployment
- article Martin Fowler — Feature Toggles (aka Feature Flags)
- article Paul Hammond & John Allspaw — 10+ Deploys Per Day: Dev and Ops Cooperation at Flickr
- article Martin Fowler — Trunk Based Development
- article Google DORA — State of DevOps Report 2023
- docs Semgrep — Static Analysis Rules for CI/CD
- docs Sigstore — Cosign: container signing and verification
- article AWS — Using OIDC with GitHub Actions to authenticate to AWS