Pular para conteúdo

Engenharia do Caos

O BoxOffice agora tem timeouts, um retry com jitter, um circuit breaker, um bulkhead, um fallback, uma região em warm standby e uma política de backups. Eles funcionam? Os testes unitários passam. Mas ninguém nunca viu o breaker do pagamento abrir com tráfego real, ninguém nunca promoveu o banco standby, e ninguém sabe quanto tempo o failover regional realmente leva. A primeira vez em que algo disso vai importar será durante uma abertura de vendas real no estádio, com dois milhões de fãs assistindo. O único jeito de saber é causar as falhas você mesmo, de propósito, enquanto observa e está pronto.

A engenharia do caos é a disciplina de fazer experimentos em um sistema para ganhar confiança na sua capacidade de suportar condições turbulentas em produção1. Em vez de esperar as falhas acontecerem, você as injeta deliberadamente, de forma controlada, e usa o que aprende para corrigir fraquezas antes que incidentes reais as encontrem.

"The best way to avoid failure is to fail constantly." — John Ciancutti, Netflix, 20104


De onde veio

A ideia é mais antiga que o nome. Na Amazon, no começo dos anos 2000, Jesse Robbins conduzia exercícios de GameDay que causavam deliberadamente falhas grandes em produção para expor fraquezas escondidas — no software e nos times que o operavam3.

A Netflix deu nome e ferramentas à prática. Depois de sair do próprio data center para a AWS, por volta de 2010 — onde qualquer instância pode sumir a qualquer momento —, ela criou o Chaos Monkey, um serviço que encerra instâncias de produção aleatórias em horário comercial, para que todo time precisasse construir serviços que sobrevivem à perda de instâncias, e os engenheiros estivessem em suas mesas para ver o que quebrava4. Veio então todo um Simian Army: o Latency Monkey injetava atrasos nas chamadas, o Chaos Gorilla simulava a perda de uma zona de disponibilidade inteira. Em 2016, engenheiros da Netflix formalizaram a disciplina em um artigo da IEEE Software2, de onde vêm os princípios abaixo.

A percepção central: falha não é um caso especial a evitar; é o ambiente em que os sistemas de produção vivem, e o único jeito de saber se um sistema sobrevive a ela é testá-lo ali.


Princípios

Os Principles of Chaos Engineering1 definem a disciplina com cinco ideias:

1. Construa uma hipótese sobre o comportamento em estado estável : Descreva o "normal" com saídas mensuráveis que importam para os usuários — os SLIs (SLOs): taxa de sucesso das compras, p99 das reservas, atualidade dos e-mails. A hipótese: "o estado estável continua mesmo quando X acontece."

2. Varie eventos do mundo real : Injete falhas que de fato acontecem: perda de instâncias e de zonas, latência, perda de pacotes, uma dependência devolvendo erros, disco cheio, relógio desajustado, um pico de tráfego, um certificado expirado.

3. Rode experimentos em produção : A homologação raramente tem o formato do tráfego, o volume de dados ou o comportamento das dependências da produção. Produção é o objetivo — alcançado aos poucos, não o primeiro passo.

4. Automatize os experimentos para rodarem continuamente : Sistemas mudam todo dia; uma fraqueza corrigida hoje pode voltar com o deploy da semana que vem. Repita os experimentos automaticamente.

5. Minimize o raio de impacto : Afete o menor número possível de usuários, tenha um botão de abortar e amplie o escopo só depois que experimentos menores passarem.


O ciclo de vida de um experimento

Cada experimento segue o método científico: prever, mudar uma variável, medir, comparar5.

flowchart LR
    H["1. Hipótese<br/>estado estável + comportamento esperado"] --> D["2. Projeto<br/>falha, escopo, condições de aborto"]
    D --> I["3. Injeção<br/>da falha controlada"]
    I --> O["4. Observação<br/>SLIs, traces, estado do breaker"]
    O --> A["5. Análise<br/>esperado vs real"]
    A --> F["6. Melhoria<br/>corrigir, atualizar runbooks"]
    F -->|"repetir com escopo maior"| H

Um experimento que confirma a hipótese é evidência de que uma defesa funciona. Um que a refuta é uma descoberta: uma fraqueza encontrada no seu cronograma, com o time pronto, em vez de às 10:00 de um dia de vendas.

Experimento: o provedor de pagamento fica lento

Passo Conteúdo
Estado estável Sucesso das compras ≥ 99,9% e p99 das reservas < 500 ms; p99 do catálogo < 300 ms (o dashboard RED do Hands-on 8).
Hipótese "Se o provedor de pagamento responder em 3 s em vez de 200 ms, o circuit breaker do payment abre em até 30 s, as compras falham rápido com 'pagamento temporariamente indisponível, lugares reservados', o bulkhead limita as chamadas ao pagamento a 50, e o catálogo e as reservas mantêm sua latência."
Injeção Toxiproxy entre o payment e o stub do provedor: um toxic de latency de 3 s ± 0,5 s, sob uma carga k6 de 100 compras/s.
Escopo e aborto Primeiro no ambiente Compose; depois em um pod canary. Abortar se o p99 do catálogo passar de 1 s ou se o error budget de compras queimar mais rápido que 10×.
Observação resilience4j_circuitbreaker_state, permissões livres do bulkhead, taxa de erros e p99 por rota, threads ocupadas do order, traces das compras lentas.
Descobertas possíveis O timeout de leitura nunca foi configurado, então chamadas lentas só contam depois de 60 s; o retry no gateway multiplica as chamadas; a mensagem do fallback é um 500 cru; o bulkhead é compartilhado com as chamadas ao inventory.

No código do BoxOffice: o estado estável como thresholds do k6 (lab 9)

O Hands-on 9 — Meça escreve a hipótese como código. Dois cenários de modelo aberto rodam ao mesmo tempo: pay (20/s, chama o payment) e browse (20/s, GET /orders/{id}, nunca chama o payment). Os thresholds de browse — p95 abaixo de 200 ms, menos de 1% de erros — são a hipótese "a falha não chega a quem não usa o provedor"; uma execução que os quebra é uma descoberta. Com o payment 3 s lento, o lab mediu um p95 de browse de 13,17 s sem proteção e 805 ms com todos os padrões ligados (só os primeiros segundos, antes de o breaker abrir, foram lentos).

load/pay-under-chaos.js (two scenarios and the thresholds)
export const options = {
  scenarios: {
    pay: {
      executor: 'constant-arrival-rate',
      exec: 'pay',
      rate: Number(__ENV.PAY_RATE || 20),
      timeUnit: '1s',
      duration: DURATION,
      preAllocatedVUs: 50,
      maxVUs: 600,
    },
    browse: {
      executor: 'constant-arrival-rate',
      exec: 'browse',
      rate: 20,
      timeUnit: '1s',
      duration: DURATION,
      preAllocatedVUs: 20,
      maxVUs: 300,
    },
  },
  summaryTrendStats: ['avg', 'med', 'p(95)', 'p(99)', 'max'],
  thresholds: {
    'http_req_duration{name:pay}': ['p(95)<1500'],
    'http_req_duration{name:browse}': ['p(95)<200'], // the collateral damage
    'http_req_failed{name:browse}': ['rate<0.01'],
  },
};

Injeção de falhas

Categoria Exemplos Ferramentas
Instância / processo Matar um pod, OOM-kill de um processo, encerrar uma VM kubectl delete pod, PodChaos do Chaos Mesh, LitmusChaos, AWS FIS
Rede Latência, perda de pacotes, limite de banda, partição, falha de DNS Toxiproxy, NetworkChaos do Chaos Mesh, tc netem
Dependência Erros ou respostas lentas de um serviço ou terceiro Toxiproxy, WireMock, uma feature flag no cliente
Recurso Pressão de CPU ou memória, disco cheio, esgotamento de descritores de arquivo stress-ng, StressChaos do Chaos Mesh
Estado e tempo Relógio desajustado, certificado expirado, entrada de cache corrompida, failover do banco TimeChaos do Chaos Mesh, "reboot with failover" do banco gerenciado
Infraestrutura Perder uma zona de disponibilidade ou uma região AWS FIS, Azure Chaos Studio, mudanças de roteamento, o exercício de DR

Toxiproxy: caos no seu notebook

O Toxiproxy é um proxy TCP com uma API para adicionar "toxics" — latência, timeouts, limites de banda, resets de conexão — a uma conexão. Coloque-o entre o payment e o stub do provedor no Docker Compose, e aponte o payment para o proxy:

# docker-compose.yaml (excerpt)
services:
  toxiproxy:
    image: ghcr.io/shopify/toxiproxy:2.9.0
    ports:
      - "8474:8474"           # Toxiproxy API
  payment:
    environment:
      PROVIDER_URL: http://toxiproxy:8666   # instead of http://provider-stub:8080
# create the proxy: toxiproxy:8666 -> provider-stub:8080
toxiproxy-cli create -l 0.0.0.0:8666 -u provider-stub:8080 provider

# experiment 1: 3 s of latency (+/- 500 ms) on every response
toxiproxy-cli toxic add -t latency -a latency=3000 -a jitter=500 provider

# experiment 2: connections hang forever (a "black hole")
toxiproxy-cli toxic add -t timeout -a timeout=0 provider

# end of the experiment
toxiproxy-cli toxic remove -n latency_downstream provider
toxiproxy-cli toxic remove -n timeout_downstream provider

No código do BoxOffice: o Toxiproxy e o script do mestre do jogo (lab 9)

O Hands-on 9 — Pagamento, provedor e Toxiproxy carrega os proxies de um arquivo na inicialização em vez de criá-los pela CLI: um na frente do payment (o order chama toxiproxy:18085) e um na frente do psp, o provedor falso (o payment chama toxiproxy:18095). Dois proxies dão dois lugares para quebrar, então o operador precisa descobrir qual salto está lento — nosso adaptador ou o provedor. Os toxics entram pela API HTTP na porta 8474; o game day embrulha seis deles num script para o mestre do jogo, e o reset (remove todo toxic, reativa todo proxy) é o botão de abortar.

infra/toxiproxy/toxiproxy.json
[
  { "name": "payment", "listen": "0.0.0.0:18085", "upstream": "payment:8085", "enabled": true },
  { "name": "psp",     "listen": "0.0.0.0:18095", "upstream": "psp:8080",     "enabled": true }
]
scripts/game-day.sh (the game master's injections)
toxic() { # proxy, json
  curl -s -X POST "$API/proxies/$1/toxics" -d "$2" > /dev/null
}
echo "$(date +%T) experiment $1"
case "$1" in
  1) toxic payment '{"name":"gd","type":"latency","stream":"downstream","attributes":{"latency":2000,"jitter":300}}' ;;
  2) toxic psp '{"name":"gd","type":"timeout","stream":"downstream","attributes":{"timeout":0}}' ;;
  3) toxic payment '{"name":"gd","type":"reset_peer","toxicity":0.4,"attributes":{"timeout":0}}' ;;
  4) curl -s -X POST "$API/proxies/payment" -d '{"enabled":false}' > /dev/null ;;
  5) docker compose pause db ;;
  6) toxic payment '{"name":"gd","type":"bandwidth","stream":"downstream","attributes":{"rate":1}}' ;;
  reset) curl -s -X POST "$API/reset" > /dev/null; docker compose unpause db 2> /dev/null || true ;;
  *) echo "unknown experiment"; exit 1 ;;
esac

Rode o k6 antes, durante e depois de cada toxic e compare os números — esse é o passo "Meça" do Hands-on 9.

Chaos Mesh: caos no Kubernetes

No Kubernetes, os experimentos são declarados como recursos, versionados no Git e limitados no tempo:

apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: payment-latency
  namespace: boxoffice
spec:
  action: delay
  mode: one                  # affect a single pod: small blast radius
  selector:
    namespaces: [boxoffice]
    labelSelectors:
      app: payment
  delay:
    latency: "3s"
    jitter: "500ms"
  duration: "10m"            # the experiment ends by itself

Controlando o raio de impacto

O raio de impacto de um experimento é o conjunto de usuários e sistemas que ele pode afetar. Controlá-lo é o que separa engenharia de imprudência.

flowchart LR
    S1["1. Local / Compose<br/>nenhum usuário"] -->|"a hipótese se mantém"| S2["2. Homologação sob carga k6<br/>usuários internos"]
    S2 -->|"sem descobertas"| S3["3. Um pod canary em produção<br/>alguns % do tráfego"]
    S3 -->|"os SLIs se mantêm"| S4["4. Produção mais ampla<br/>uma zona, depois uma região"]
  • Condições de aborto ligadas aos SLOs: pare automaticamente se o burn rate do error budget passar de um limite ou se um SLI-chave cair (o AWS FIS e o Chaos Mesh suportam condições de parada; no mínimo, uma pessoa com um botão).
  • Limitado no tempo: todo experimento tem duração e se limpa sozinho.
  • Horário comercial, nunca durante uma abertura de vendas: o time precisa estar presente; a política de error budget decide se dá para pagar o custo.
  • Avise: o plantão e o time de suporte sabem que um experimento está rodando.

Game days

Um game day é um exercício planejado, com o time todo, em que falhas são injetadas e as pessoas respondem como num incidente real. Experimentos automatizados testam o software; game days testam também os runbooks, os dashboards, os alertas e a comunicação.

Game day da flash sale do BoxOffice

Objetivo: ensaiar uma abertura de vendas do estádio em homologação, em escala de produção (o k6 reproduzindo o pico das 10:00), e verificar que o caminho de compra cumpre seus SLOs sob cinco falhas injetadas uma de cada vez.

Horário Injeção Comportamento esperado Medida
10:00 Começa o pico (sem falha) A sala de espera admite na taxa configurada; SQL < 70% SLIs de referência
10:10 Latência de 3 s no provedor de pagamento O breaker abre em < 30 s; lugares reservados; catálogo não afetado Tempo para detectar (alerta), taxa de erros das compras
10:25 Um pod do order morto por minuto O readiness o remove; a capacidade N+1 absorve Erros durante os reinícios
10:40 Um broker Kafka parado Os produtores fazem retry; o backlog do outbox cresce e escoa; nenhum e-mail perdido Idade do outbox, consumer lag, SLI de atualidade dos e-mails
10:55 Failover do banco para o standby ~30 s de erros nas escritas, depois recuperação RTO real vs esperado
11:10 Cache Redis esvaziado A proteção contra stampede se mantém; o pico de carga no SQL fica abaixo do limite CPU do SQL, p99 do catálogo

Papéis: um mestre do jogo (injeta, controla o tempo, decide abortar), os responsáveis do plantão (que não conhecem o roteiro), observadores por serviço e um escriba (linha do tempo). Depois: um debrief sem culpados (o que esperávamos, o que aconteceu, o que nos surpreendeu), com itens de ação com responsável e data.

Experimente no playground

Carregue o cenário flash-sale com o perfil chaos (300 s, caos no mínimo medium): o simulador injeta falhas aleatórias — quedas, degradações de latência, indisponibilidade do provedor — enquanto o tráfego da abertura roda. Aperte Run: o desenho inicial falha quando a venda abre (t = 60 s), faça o caos o que fizer, porque não tem sala de espera — um experimento de caos precisa primeiro de um estado estável que passe. Então abra no playground completo (↗), aperte Mostrar uma solução possível (as configurações, inclusive a seed 42, vão junto) e rode: a sala de espera absorve a venda, e a aba Eventos lista cada falha injetada. Quais delas o projeto aguenta, e qual componente sozinho ainda deixa uma meta vermelha — e como você o tornaria redundante em produção? Por fim troque o perfil para load para confirmar que o estado estável passa sem caos.

Cultura sem culpados é pré-requisito

Experimentos de caos e game days só funcionam se as pessoas puderem expor problemas com segurança. Se uma descoberta leva a culpa, os times param de relatar com honestidade e as fraquezas continuam escondidas. O resultado de um game day é uma lista de melhorias, não uma lista de culpados (veja Resposta a incidentes).


Verificação contínua

Depois que um experimento passa, mantenha-o passando:

  • No CI: testes de integração com Toxiproxy (o Testcontainers tem um módulo de Toxiproxy) verificam que o cliente desiste depois do timeout e que o breaker abre — barato, em todo build.
  • Agendado em homologação: workflows do Chaos Mesh ou do LitmusChaos rodam os experimentos padrão toda noite contra o último deploy.
  • Em produção, pequeno: um pod-kill recorrente num canary, uma restauração mensal de backup, um exercício de failover regional duas vezes por ano (Recuperação de desastres).

Observabilidade primeiro

Caos sem observabilidade é só quebrar coisas. Antes de injetar qualquer coisa, você precisa conseguir:

  1. Ver o estado estável — dashboards de SLI com valores de referência.
  2. Detectar o desvio — alertas que disparam em minutos (alertas de burn rate).
  3. Explicá-lo — traces que mostram qual chamada ficou lenta, métricas de breakers e pools.
  4. Pará-lo — um botão de abortar que funciona em segundos.

Observabilidade primeiro, depois padrões de resiliência, depois experimentos de caos para provar que os padrões funcionam.

É também a ordem dos labs: o lab 8 instrumenta o BoxOffice antes de o lab 9 quebrá-lo de propósito, e os operadores do game day trabalham com o dashboard e os alertas do lab 8.

Ferramenta Mantida por Pontos fortes
Toxiproxy Shopify (open source) Proxy TCP simples; ideal para testes locais e no CI
Chaos Mesh CNCF Nativo de Kubernetes; falhas de rede, pod, estresse, tempo, I/O; workflows
LitmusChaos CNCF Nativo de Kubernetes; hub de experimentos; amigável a GitOps
AWS Fault Injection Service Amazon Gerenciado; falhas no nível da AWS (energia de AZ, erros de API); condições de parada
Azure Chaos Studio Microsoft Caos gerenciado para recursos do Azure
Gremlin Gremlin Inc. Plataforma comercial; biblioteca ampla de falhas; pontuação de confiabilidade
Chaos Monkey Netflix Encerramento aleatório de instâncias; o original

Na entrevista

Depois de propor mecanismos de resiliência, diga como você os verificaria: "eu rodaria um experimento de caos com uma hipótese de estado estável sobre o SLO de compra — injetar 3 s de latência no provedor de pagamento em um pod canary, esperar que o breaker abra em até 30 s e que a latência do catálogo fique estável, com aborto automático se o burn rate passar de 10×". Isso mostra que você sabe que failover e fallbacks não testados são o motivo mais comum de projetos aparentemente resilientes falharem.

Boas práticas

  • Escreva o estado estável como thresholds que uma ferramenta verifica — passar ou falhar vira um fato, não uma opinião. No BoxOffice: os thresholds de browse do pay-under-chaos.js (quadro acima).
  • Teste o comando de abortar antes da primeira injeção — o abortar precisa funcionar quando o sistema não funciona. No BoxOffice: curl -s -X POST localhost:8474/reset remove todo toxic (Hands-on 9 — Game day).
  • Mantenha os experimentos como código versionado, com fim embutido — um script ou um manifesto com duration pode ser revisado, repetido e não pode ficar esquecido rodando. No BoxOffice: scripts/game-day.sh.
  • Injete falhas abaixo da biblioteca cliente, na rede — um proxy TCP exercita os timeouts e pools de conexão reais; um mock no código testa só o mock. No BoxOffice: um proxy do Toxiproxy por salto (quadro acima).
  • Cronometre detecção e mitigação em todo game day e compare entre rodadas — a tendência desses dois números é a evidência de que o time, e não só o código, melhorou. No BoxOffice: a tabela de resultados do Hands-on 9.
  • Transforme cada descoberta num experimento de regressão no CI — uma fraqueza corrigida hoje volta com o deploy da semana que vem; o módulo Toxiproxy do Testcontainers torna a verificação barata.

Armadilhas comuns

  • Caos sem hipótese: se você não disse o que deveria acontecer, não aprende com o que aconteceu.
  • Começar em produção com um raio de impacto grande — produção é o destino, não o ponto de partida.
  • Sem botão de abortar, ou com um aborto que depende do sistema sendo testado.
  • Rodar durante uma abertura de vendas ou com o error budget esgotado.
  • Descobertas que nunca viram tickets: o experimento consumiu orçamento e não ensinou nada.
  • Testar só crashes: latência e falhas parciais são as que causam cascatas.

Como se conecta

flowchart LR
    SS["estado estável = SLIs<br/>→ SLOs"] --> HYP["hipótese"]
    HYP --> INJ["injetar: Toxiproxy,<br/>Chaos Mesh, AWS FIS"]
    LOAD["carga do k6 durante o teste<br/>→ Performance"] --> INJ
    INJ --> PAT["defesas sob teste:<br/>breaker, bulkhead, fallback"]
    INJ --> DR["failover, restauração<br/>→ Recuperação de desastres"]
    OBS["métricas, traces, alertas de burn rate<br/>→ Observabilidade"] --> OA["observar e abortar"]
    PAT --> OA
    DR --> OA
    OA --> BR["ampliar o raio de impacto:<br/>local → canário → zona"]
    OA --> GD["game day + debrief sem culpados<br/>→ Resposta a incidentes"]
    GD --> HYP

Componentes da paleta: thirdparty (o stub do provedor atrás do Toxiproxy) · app (pods de order derrubados durante o game day) · kafka (um broker parado) · sql (failover para o standby) · cache (Redis esvaziado: proteção contra stampede) · metrics · tracing · alerting (sem eles não há caos possível). Cenários: flash-sale (perfil chaos), cascading-failure. Continua em: Observabilidade — resposta a incidentes, Performance — testes de carga, Cloud-native — Kubernetes, Hands-on 9 — pagamento e Toxiproxy, Projeto — game day.



  1. Principles of Chaos Engineering. principlesofchaos.org. ↩↩

  2. BASIRI, A. et al. Chaos Engineering. IEEE Software, v. 33, n. 3, p. 35–41, 2016. ↩

  3. ROBBINS, J. et al. Resilience Engineering: Learning to Embrace Failure. ACM Queue, v. 10, n. 9, 2012. ↩

  4. CIANCUTTI, J. 5 Lessons We've Learned Using AWS. Netflix Tech Blog, dez. 2010. ↩↩

  5. ROSENTHAL, C.; JONES, N. Chaos Engineering: System Resiliency in Practice. O'Reilly, 2020. ↩