Como aplicar chaos engineering para validar resiliência

1. Fundamentos do Chaos Engineering e sua relevância para resiliência

Chaos Engineering é a disciplina de experimentar em um sistema distribuído para construir confiança na capacidade do sistema de suportar condições turbulentas em produção. Diferentemente de testes tradicionais (unitários, integração, fim a fim), que verificam comportamentos esperados sob condições controladas, o Chaos Engineering introduz intencionalmente falhas para descobrir fraquezas antes que elas se manifestem como incidentes reais.

Os princípios fundamentais do Chaos Engineering, definidos pelo Principles of Chaos Engineering, incluem:
- Definir o estado estável do sistema: medir métricas como latência, taxa de erro e throughput
- Hipótese sobre estado estável: formular uma hipótese clara sobre o comportamento esperado do sistema diante de uma falha
- Variáveis do mundo real: simular falhas reais como falhas de rede, picos de tráfego, falhas de hardware
- Experimentar em produção: realizar experimentos controlados no ambiente de produção (ou staging realista)

Para sistemas distribuídos e microsserviços, onde a complexidade das interações entre componentes torna impossível prever todos os cenários de falha, o Chaos Engineering é essencial. Ele expõe pontos cegos em estratégias de resiliência, como timeouts mal configurados, circuit breakers ineficazes ou dependências não tratadas.

2. Preparação do ambiente e definição de hipóteses de resiliência

Antes de qualquer experimento, é necessário mapear as dependências críticas do sistema. Um diagrama de arquitetura deve identificar:

Serviço A (API Gateway) → Serviço B (Autenticação) → Banco de Dados MySQL
Serviço A → Serviço C (Catálogo) → Cache Redis
Serviço A → Serviço D (Pagamento) → API Externa (Gateway de Pagamento)
Serviço E (Notificações) → Fila RabbitMQ

Com base nesse mapeamento, estabeleça métricas de estado estável. Exemplo de SLOs:

Métrica de estado estável:
- Latência p95: < 200ms
- Taxa de erro: < 0.1%
- Throughput: > 1000 req/s
- Disponibilidade: > 99.9%

Formule hipóteses específicas e mensuráveis. Exemplo:

Hipótese: "Se o banco de dados MySQL ficar indisponível por 30 segundos,
o serviço de autenticação deve retornar erro 503 (Service Unavailable)
para novas requisições, sem corromper sessões existentes ou perder dados.
A latência p95 não deve exceder 500ms durante o período de falha."

3. Ferramentas e técnicas para execução de experimentos de caos

As ferramentas mais populares incluem:

  • Chaos Monkey (Netflix): encerra instâncias aleatoriamente para testar tolerância a falhas
  • Gremlin: plataforma completa com injeção de falhas de rede, CPU, memória, disco
  • Litmus: framework open-source para Kubernetes, com suporte a workflows de caos
  • Chaos Mesh: ferramenta nativa Kubernetes para simular falhas de pod, rede, disco

Tipos de experimentos comuns:

1. Falha de rede: latência adicional (100ms, 500ms), perda de pacotes (10%, 50%)
2. Falha de nó: kill de processo (SIGKILL), desligamento de container
3. Falha de recurso: stress de CPU (80%, 100%), consumo de memória, disco cheio
4. Falha de dependência: desconexão de banco de dados, falha de API externa

Automação com CI/CD:

Pipeline de experimento:
1. Deploy da aplicação em staging
2. Execução de testes de carga (baseline)
3. Injeção de falha controlada (ex.: desconexão Redis)
4. Monitoramento de métricas (5 minutos)
5. Rollback automático se métricas violarem SLOs
6. Geração de relatório de hipóteses

4. Execução prática de um experimento de Chaos Engineering

Vamos executar um experimento prático: desconexão do banco de dados MySQL do serviço de autenticação.

Passo 1: Escolher a falha e definir escopo

Falha: Desconexão da conexão TCP com MySQL (porta 3306)
Escopo: 30% das requisições para o endpoint /auth/login
Duração: 60 segundos
Ambiente: Staging (réplica de produção)

Passo 2: Executar o experimento

Usando Chaos Mesh (Kubernetes):

# Configuração do experimento de falha de rede
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: mysql-disconnect
spec:
  action: partition
  mode: fixed-percent
  value: "30"
  selector:
    namespaces:
      - staging
    labelSelectors:
      app: auth-service
  direction: both
  target:
    mode: all
    selector:
      namespaces:
        - staging
      labelSelectors:
        app: mysql
  duration: "60s"

Passo 3: Monitoramento em tempo real

Dashboards devem mostrar:

Métrica                | Antes (baseline) | Durante falha | Após recuperação
Latência p95 (ms)      | 45               | 320           | 48
Taxa de erro (%)       | 0.02             | 18.5          | 0.03
Throughput (req/s)     | 850              | 620           | 840
Sessões ativas         | 2340             | 2280          | 2335

Passo 4: Análise de resultados

Hipótese validada? PARCIALMENTE
- O serviço retornou 503 corretamente para novas requisições
- Sessões existentes foram mantidas (usando cache local)
- Porém, a latência p95 atingiu 320ms (acima do limite de 200ms)
- A taxa de erro foi de 18.5% (acima do limite de 0.1%)
- Descoberta: o timeout de conexão com MySQL estava configurado para 30s
  (muito alto), causando retenção de threads e degradação de performance

5. Estratégias para mitigação de falhas descobertas

Com base no experimento, implemente correções:

1. Circuit Breaker (Resilience4j):
   - Configurar circuit breaker para MySQL com limiar de 50% de erros
   - Timeout de 2s, janela de 10s, half-open após 5s

2. Retry com backoff exponencial:
   - Máximo de 3 tentativas, backoff inicial de 100ms
   - Jitter de 50ms para evitar thundering herd

3. Bulkhead:
   - Limitar conexões simultâneas com MySQL a 20 threads
   - Pool separado para operações críticas vs. não críticas

4. Fallback:
   - Cache local (Caffeine) com TTL de 30s para dados de autenticação
   - Modo offline para funcionalidades não essenciais

Exemplo de configuração Resilience4j:

resilience4j.circuitbreaker:
  instances:
    mysqlAuth:
      slidingWindowSize: 10
      failureRateThreshold: 50
      waitDurationInOpenState: 5000
      permittedNumberOfCallsInHalfOpenState: 3
      minimumNumberOfCalls: 5
      timeoutDuration: 2000ms

6. Evolução contínua e cultura de Chaos Engineering

O Chaos Engineering não é um projeto único, mas um ciclo contínuo:

Ciclo de experimentos (recomendação):
- Semanal: experimentos simples (falha de um pod, latência de rede)
- Mensal: experimentos complexos (falha de banco, queda de região)
- Trimestral: Game Day completo (simulação de desastre com equipe)

Game Day típico:
1. Sexta-feira, 14h-17h (horário de baixo tráfego)
2. Cenário: "O provedor de nuvem da região US-East caiu"
3. Equipes on-call e SRE participam ativamente
4. Documentação em tempo real no runbook
5. Retrospectiva na segunda-feira seguinte

Documentação de lições aprendidas:

Lições documentadas após experimento:
1. Timeout de conexão MySQL reduzido de 30s para 2s
2. Circuit breaker implementado para todas as dependências externas
3. Cache local adicionado para dados de sessão
4. Runbook atualizado com procedimento de failover manual
5. Métrica de "tempo de recuperação" adicionada ao dashboard

7. Boas práticas e armadilhas comuns

Boas práticas:

✓ Comece em staging, evolua para produção gradualmente
✓ Tenha observabilidade completa (métricas, logs, tracing)
✓ Defina blast radius (escopo limitado a 10-30% do tráfego)
✓ Automatize rollback com base em métricas de SLO
✓ Obtenha aprovação de stakeholders para experimentos em produção
✓ Promova segurança psicológica: "falhas são aprendizado, não culpa"

Armadilhas comuns:

✗ Realizar experimentos sem hipóteses claras
✗ Ignorar métricas de estado estável (não saber o que é "normal")
✗ Experimentar em produção sem rollback automático
✗ Executar experimentos em horários de pico sem comunicação prévia
✗ Confundir Chaos Engineering com "quebrar tudo de propósito" (falta de foco)
✗ Não documentar descobertas e lições aprendidas

Chaos Engineering, quando aplicado corretamente, transforma a resiliência de sistemas distribuídos de uma esperança em uma propriedade mensurável e continuamente validada.

Referências