Práticas recomendadas para pipeline de CI/CD: automatize seu caminho para implantações confiáveis

Crie pipelines de CI/CD confiáveis ​​com práticas recomendadas para testes, preparação, automação de implantação, estratégias de reversão e verificação de segurança em fluxos de trabalho de produção.

E
ECOSIRE Research and Development Team
|16 de março de 20267 min de leitura1.6k Palavras|

Práticas recomendadas para pipeline de CI/CD: automatize seu caminho para implantações confiáveis

Equipes com pipelines de CI/CD maduros implantam 208 vezes mais frequentemente do que aquelas sem, enquanto experimentam taxas de falha de alteração 7 vezes menores. A diferença entre um pipeline frágil "funciona principalmente" e um sistema de implantação testado em batalha se resume a um punhado de práticas que separam a automação amadora da infraestrutura de nível de produção.

Este guia aborda práticas concretas, configurações e decisões arquitetônicas que tornam os pipelines de CI/CD confiáveis ​​em escala.

Principais conclusões

  • O tempo de execução do pipeline afeta diretamente a produtividade do desenvolvedor --- meta de menos de 10 minutos para o pacote completo
  • A verificação de segurança em CI detecta 85% das vulnerabilidades antes que elas cheguem à produção
  • Mecanismos de reversão automatizados reduzem o tempo médio de recuperação de horas para minutos
  • Regras de proteção de ramificação e verificações de status necessárias evitam que códigos quebrados cheguem ao principal

Arquitetura de pipeline

O modelo de cinco estágios

Cada pipeline de CI/CD de produção deve implementar cinco estágios:

Etapa 1: Lint e Validação (meta: <2 minutos)

lint:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: 20
        cache: pnpm
    - run: pnpm install --frozen-lockfile
    - run: pnpm lint
    - run: pnpm typecheck

Etapa 2: Teste (meta: <8 minutos)

test:
  runs-on: ubuntu-latest
  services:
    postgres:
      image: postgres:17
      env:
        POSTGRES_PASSWORD: test
        POSTGRES_DB: test
      ports:
        - 5432:5432
      options: >-
        --health-cmd pg_isready
        --health-interval 10s
        --health-timeout 5s
        --health-retries 5
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: 20
        cache: pnpm
    - run: pnpm install --frozen-lockfile
    - run: pnpm test
      env:
        DATABASE_URL: postgresql://postgres:test@localhost:5432/test

Etapa 3: Construção (meta: <5 minutos)

Crie imagens Docker, compile ativos, gere pacotes de produção. Dependências de cache agressivamente.

Estágio 4: Implantar no teste

Implantação automática na mesclagem com o principal. Execute testes de fumaça no ambiente de teste.

Etapa 5: Implantar em produção

Portão de aprovação manual ou automatizado após aprovação de validação de preparação.


Otimização de velocidade

Pipelines lentos matam a produtividade do desenvolvedor. Cada minuto de tempo de espera do CI multiplicado por uma equipe cria horas de perda de tempo de troca de contexto.

Paralelização

Execute trabalhos independentes simultaneamente:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps: [...]

  test-unit:
    runs-on: ubuntu-latest
    steps: [...]

  test-integration:
    runs-on: ubuntu-latest
    steps: [...]

  test-e2e:
    runs-on: ubuntu-latest
    steps: [...]

  build:
    needs: [lint, test-unit, test-integration, test-e2e]
    runs-on: ubuntu-latest
    steps: [...]

Cache de Dependência

- uses: actions/cache@v4
  with:
    path: |
      ~/.pnpm-store
      node_modules
      apps/*/node_modules
      packages/*/node_modules
    key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
    restore-keys: |
      ${{ runner.os }}-pnpm-

Cache da camada Docker

- uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    tags: registry.example.com/app:${{ github.sha }}
    cache-from: type=gha
    cache-to: type=gha,mode=max

Benchmarks de velocidade do pipeline

OtimizaçãoAntesDepoisMelhoria
Sem cache12 minutos---Linha de base
Cache de dependência12 minutos7 minutos42%
Cache da camada Docker7 minutos4,5 minutos36%
Conjuntos de testes paralelos4,5 minutos3 minutos33%
Cache remoto turbo3 minutos2 minutos33%

Verificação de segurança

Verificação de vulnerabilidade de dependência

security:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4

    - name: Run Snyk to check for vulnerabilities
      uses: snyk/actions/node@master
      env:
        SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
      with:
        args: --severity-threshold=high

    - name: Run Trivy vulnerability scanner
      uses: aquasecurity/trivy-action@master
      with:
        scan-type: fs
        scan-ref: .
        severity: CRITICAL,HIGH
        exit-code: 1

Verificação Secreta

    - name: Detect secrets
      uses: trufflesecurity/trufflehog@main
      with:
        extra_args: --only-verified

SAST (teste estático de segurança de aplicativos)

    - name: CodeQL Analysis
      uses: github/codeql-action/analyze@v3
      with:
        languages: javascript-typescript

Política do portão de segurança

Encontrando a GravidadeComportamento de relações públicasComportamento de produção
CríticoMesclagem de blocosImplantação de bloco
AltoMesclagem de blocosImplantação de bloco
MédioAviso, permitir mesclagemAviso, permitir implantação
BaixoApenas informativoApenas informativo

Estratégia de proteção e fusão de filiais

Verificações de status necessárias

Configure-os como verificações de status necessárias na ramificação principal:

  1. Lint e typecheck devem passar
  2. Todos os testes unitários devem passar
  3. Todos os testes de integração devem passar
  4. A verificação de segurança não deve apresentar descobertas críticas/altas
  5. A construção deve ter sucesso

Estratégia de mesclagem

Use mesclagens de squash para ramificações de recursos para manter um histórico limpo:

main: A --- B --- C --- D (each is a squashed feature)

Exigir pelo menos uma aprovação para PRs. Para caminhos críticos (autenticação, cobrança, migrações de banco de dados), são necessárias duas aprovações.


Estratégias de implantação

Implantação Azul-Verde

Mantenha dois ambientes de produção idênticos. Roteie o tráfego para um enquanto implanta no outro.

#!/bin/bash
# blue-green-deploy.sh

CURRENT=$(kubectl get service production -o jsonpath='{.spec.selector.version}')

if [ "$CURRENT" == "blue" ]; then
  TARGET="green"
else
  TARGET="blue"
fi

echo "Current: $CURRENT, deploying to: $TARGET"

# Deploy to inactive environment
kubectl set image deployment/$TARGET-app app=registry.example.com/app:$TAG

# Wait for rollout
kubectl rollout status deployment/$TARGET-app --timeout=300s

# Run smoke tests against target
curl -sf "http://$TARGET.internal/health" || exit 1

# Switch traffic
kubectl patch service production -p "{\"spec\":{\"selector\":{\"version\":\"$TARGET\"}}}"

echo "Traffic switched to $TARGET"

Implantação contínua

Atualize os pods de forma incremental:

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 25%
    maxUnavailable: 0

maxUnavailable: 0 garante nenhuma perda de capacidade durante a implantação.

Implantação Canário

Direcione uma pequena porcentagem do tráfego para a nova versão:

# Using Istio for traffic splitting
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: app-canary
spec:
  hosts:
    - app.example.com
  http:
    - route:
        - destination:
            host: app-stable
          weight: 95
        - destination:
            host: app-canary
          weight: 5

Para obter mais estratégias de implantação, consulte nosso guia dedicado sobre implantações com tempo de inatividade zero.


Automação de reversão

Reversão automática em caso de falha na verificação de integridade

deploy-production:
  runs-on: ubuntu-latest
  steps:
    - name: Deploy
      run: |
        kubectl set image deployment/app app=${{ env.IMAGE }}
        kubectl rollout status deployment/app --timeout=300s

    - name: Smoke tests
      run: |
        sleep 30
        STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://app.example.com/health)
        if [ "$STATUS" != "200" ]; then
          echo "Health check failed with status $STATUS"
          kubectl rollout undo deployment/app
          exit 1
        fi

    - name: Monitor error rate
      run: |
        # Check error rate over 5 minutes
        ERROR_RATE=$(curl -s "http://prometheus:9090/api/v1/query?query=rate(http_requests_total{status=~'5..'}[5m])/rate(http_requests_total[5m])" | jq '.data.result[0].value[1]' -r)
        if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )); then
          echo "Error rate $ERROR_RATE exceeds threshold"
          kubectl rollout undo deployment/app
          exit 1
        fi

Otimização de pipeline Monorepo

Para projetos monorepo (como aqueles que usam Turborepo), execute apenas o que mudou:

- name: Determine affected packages
  id: affected
  run: |
    AFFECTED=$(npx turbo run build --filter='...[HEAD~1]' --dry-run=json | jq -r '.packages[]')
    echo "packages=$AFFECTED" >> $GITHUB_OUTPUT

- name: Test affected packages
  if: steps.affected.outputs.packages != ''
  run: npx turbo run test --filter='...[HEAD~1]'

Isso reduz o tempo de CI em 60-80% para alterações que afetam apenas um único pacote em um grande monorepo.


Perguntas frequentes

Com que frequência devemos implantar na produção?

Implante com a frequência que seu pipeline permitir. Equipes de alto desempenho implantam várias vezes por dia. O objetivo são mudanças pequenas e incrementais que sejam fáceis de revisar, testar e reverter. Se a implantação parecer arriscada, isso é um sinal de que seu pipeline precisa de mais testes automatizados e melhores mecanismos de reversão, e não de menos implantações.

Devemos usar desenvolvimento baseado em tronco ou ramificações de recursos?

Filiais de recursos com vida útil curta (1 a 3 dias) funcionam melhor para a maioria das equipes. O desenvolvimento baseado em tronco requer infraestrutura de testes e sinalizadores de recursos mais maduros. O importante é que as ramificações tenham vida curta – ramificações de recursos de longa duração criam conflitos de mesclagem e atrasam o feedback.

Como lidamos com migrações de banco de dados em CI/CD?

Execute migrações como uma etapa separada do pipeline antes da implantação do aplicativo. Certifique-se de que as migrações sejam compatíveis com versões anteriores (a versão antiga do aplicativo deve funcionar com o novo esquema). Use o padrão de expansão e contração: adicione novas colunas primeiro, implante o código que grava tanto no antigo quanto no novo, migre os dados e, em seguida, remova as colunas antigas em uma versão subsequente.

Qual é a pirâmide de teste correta para CI?

Para uma aplicação web típica: 70% de testes unitários (rápidos, isolados), 20% de testes de integração (endpoints de API, consultas de banco de dados), 10% de testes E2E (fluxos de usuários críticos). Os testes unitários são executados em cada commit. Testes de integração executados em PR. Os testes E2E são executados na mesclagem com o principal ou antes da implantação da produção.


O que vem a seguir

Um pipeline de CI/CD bem projetado é a base para todas as outras práticas de DevOps. Com a automação confiável implementada, você pode buscar com segurança infraestrutura como código, monitoramento de produção e teste de carga.

Entre em contato com a ECOSIRE para projeto e implementação de pipeline de CI/CD ou explore nosso guia DevOps para pequenas empresas para obter o roteiro completo de infraestrutura.


Publicado pela ECOSIRE – ajudando empresas a implantar software com confiança.

E

Escrito por

ECOSIRE Research and Development Team

Construindo produtos digitais de nível empresarial na ECOSIRE. Compartilhando insights sobre integrações Odoo, automação de e-commerce e soluções de negócios com IA.

Converse no WhatsApp