Pular para conteúdo

3. Game Day

Os padrões do passo 2 foram testados um de cada vez, por quem os escreveu, sabendo o que ia quebrar. Incidentes reais não são assim. Um game day é um incidente ensaiado: um time injeta falhas num sistema em funcionamento, sem contar aos operadores quais, e verifica duas hipóteses ao mesmo tempo — que o sistema degrada do jeito que o projeto diz, e que as pessoas percebem, diagnosticam e mitigam a tempo. É o lado prático da engenharia do caos.

Papéis (grupos de 3–4)

Papel Faz Não pode
Mestre do jogo escolhe e injeta as falhas com o Toxiproxy, controla o relógio contar aos outros o que foi injetado
Operador(es) acompanha o Grafana (lab 8) e os logs, diagnostica, mitiga olhar o terminal do mestre do jogo
Escriba escreve a linha do tempo: cada observação e decisão, com o horário consertar qualquer coisa

Troque os papéis entre as rodadas, para que todos operem uma vez.

Antes de começar

  1. Estado estável. Rode o load/pay-under-chaos.js com DURATION=30m e anote os valores normais do dashboard RED: taxa, % de erros e p95 do order e do payment. Essa é a linha de base com que todo experimento é comparado.
  2. Hipóteses. Para cada experimento abaixo, o time escreve antes de injetar o que espera ver (por exemplo, "com o payment +2 s, o p95 do pay no order fica abaixo de 1,5 s e o GET /orders/{id} não é afetado").
  3. Raio de impacto e aborto. Só o BoxOffice do seu laptop. Condição de aborto: qualquer coisa fora do experimento quebra (por exemplo, erros no catalog). O botão de abortar é um comando: curl -s -X POST localhost:8474/reset (remove todo toxic e reativa todo proxy).
  4. Detecção. O alerta de burn rate do lab 8 observa só o catalog. Adicione as mesmas duas regras para o order (o arquivo do lab 8 com catalog trocado por order, mostrado abaixo) e recrie o otel-lgtm com docker compose up -d --force-recreate --wait otel-lgtm: Alerting → Alert rules agora lista quatro regras na pasta BoxOffice, todas Normal, para que os operadores possam ser acionados. Recriar (e não só reiniciar) também esvazia as métricas: senão os erros dos experimentos do passo 2, ainda dentro das janelas de 1 hora e de 6 horas, fazem as regras do order dispararem na hora. O dashboard e as regras são arquivos, então voltam.
infra/grafana/alerting/slo-order.yaml — o arquivo completo
infra/grafana/alerting/slo-order.yaml
# Lab 9: slo-catalog.yaml of lab 8 with "catalog" replaced by "order", so the game day can page.
# Multiwindow, multi-burn-rate alerts for the order availability SLO (Google SRE workbook, ch. 5).
# SLI: share of order requests (health checks excluded) NOT answered with 5xx. SLO: 99.5% / 28 days.
apiVersion: 1

groups:
  - orgId: 1
    name: order-slo
    folder: BoxOffice
    interval: 30s
    rules:
      - uid: order-availability-fast-burn
        title: "order availability SLO: fast burn"
        condition: C
        data:
          - refId: A          # burn rate over the long window (1 h)
            relativeTimeRange: { from: 3600, to: 0 }
            datasourceUid: prometheus
            model:
              refId: A
              instant: true
              expr: |
                (
                  sum(rate(http_server_request_duration_seconds_count{service_name="order", http_route!~"/actuator.*", http_response_status_code=~"5.."}[1h]))
                  / sum(rate(http_server_request_duration_seconds_count{service_name="order", http_route!~"/actuator.*"}[1h]))
                ) / 0.005
          - refId: B          # burn rate over the short window (5 min): stops paging soon after recovery
            relativeTimeRange: { from: 300, to: 0 }
            datasourceUid: prometheus
            model:
              refId: B
              instant: true
              expr: |
                (
                  sum(rate(http_server_request_duration_seconds_count{service_name="order", http_route!~"/actuator.*", http_response_status_code=~"5.."}[5m]))
                  / sum(rate(http_server_request_duration_seconds_count{service_name="order", http_route!~"/actuator.*"}[5m]))
                ) / 0.005
          - refId: C
            datasourceUid: __expr__
            model:
              refId: C
              type: math
              expression: "$A > 14.4 && $B > 14.4"
        for: 1m
        noDataState: OK
        execErrState: Error
        labels:
          severity: page
          slo: order-availability
        annotations:
          summary: "order is burning its 28-day error budget 14.4x too fast"
          description: 'Burn rate {{ printf "%.1f" $values.A.Value }} (1 h) and {{ printf "%.1f" $values.B.Value }} (5 min): at this pace about 2% of the 28-day budget is gone in one hour.'

      - uid: order-availability-slow-burn
        title: "order availability SLO: slow burn"
        condition: C
        data:
          - refId: A          # 6 h window
            relativeTimeRange: { from: 21600, to: 0 }
            datasourceUid: prometheus
            model:
              refId: A
              instant: true
              expr: |
                (
                  sum(rate(http_server_request_duration_seconds_count{service_name="order", http_route!~"/actuator.*", http_response_status_code=~"5.."}[6h]))
                  / sum(rate(http_server_request_duration_seconds_count{service_name="order", http_route!~"/actuator.*"}[6h]))
                ) / 0.005
          - refId: B          # 30 min window
            relativeTimeRange: { from: 1800, to: 0 }
            datasourceUid: prometheus
            model:
              refId: B
              instant: true
              expr: |
                (
                  sum(rate(http_server_request_duration_seconds_count{service_name="order", http_route!~"/actuator.*", http_response_status_code=~"5.."}[30m]))
                  / sum(rate(http_server_request_duration_seconds_count{service_name="order", http_route!~"/actuator.*"}[30m]))
                ) / 0.005
          - refId: C
            datasourceUid: __expr__
            model:
              refId: C
              type: math
              expression: "$A > 6 && $B > 6"
        for: 5m
        noDataState: OK
        execErrState: Error
        labels:
          severity: ticket
          slo: order-availability
        annotations:
          summary: "order is burning its error budget 6x too fast"
          description: 'Burn rate {{ printf "%.1f" $values.A.Value }} (6 h) and {{ printf "%.1f" $values.B.Value }} (30 min): about 5% of the 28-day budget in six hours.'

Os experimentos

O mestre do jogo escolhe três destes, em qualquer ordem, de 5 a 8 minutos cada, com tempo de recuperação entre eles (pelo menos 5 minutos, até a regra de queima rápida voltar a Normal: a sua janela de 5 minutos precisa esquecer o experimento anterior, ou o próximo é "detectado" por um alerta que nunca parou de disparar):

# Injeção (mestre do jogo) O que um bom sistema faz O que um bom operador faz
1 payment +2 s de latência timeouts + breaker: 503s rápidos no pay, browse não é afetado encontra o payment no trace, vê o breaker abrir
2 buraco negro no psp (toxic timeout, 0) o timeout de leitura do payment (2 s) faz as chamadas falharem; o breaker do order abre segue o trace um salto adiante, até o provedor
3 40% de reset_peer no payment os retries escondem a maioria dos erros; a taxa de erros sobe só um pouco percebe os retries nas métricas, não entra em pânico
4 proxy do payment desativado breaker aberto, 503 rápido, os pedidos ficam em PENDING_PAYMENT comunica "pagamentos fora do ar, pedidos a salvo"
5 db pausado (docker compose pause db) tudo fica lento; o alerta de SLO dispara identifica a dependência compartilhada, não o sintoma
6 bandwidth de 1 KB/s no payment pay cerca de 0,5 s mais lento (algumas centenas de bytes a 1 KB/s), abaixo do timeout de leitura: sem erros, só latência distingue "lento" de "fora do ar"

Comandos de injeção:

scripts/game-day.sh
#!/bin/sh
# Game master helper. Usage: sh scripts/game-day.sh <experiment 1..6 | reset>
API=localhost:8474
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

Durante a rodada

Os operadores trabalham como num incidente real (veja a aula de resposta a incidentes): declaram o incidente quando um alerta dispara ou um sintoma aparece, designam um incident commander, diagnosticam pelo caminho dashboard → trace → logs, mitigam primeiro (por exemplo, parar de mandar tráfego para um caminho quebrado, aumentar um timeout, escalar) e investigam depois. O escriba registra os horários neste formato:

Horário Quem Observação / decisão
10:02:10 MJ (escondido) experimento 1 injetado
10:04:30 Op p95 do order subindo no dashboard; nenhum alerta ainda
10:05:05 Op o trace mostra o span do payment com ~2 s; breaker OPEN no actuator
…

Da linha do tempo saem os dois números que toda revisão de incidente pede: tempo para detectar (injeção → primeira observação correta) e tempo para mitigar (injeção → usuários não mais afetados).

Depois: uma revisão sem culpados

Escreva uma revisão por rodada em results/lab9-gameday.md. Sem culpados (blameless) significa que a revisão explica como o sistema e o processo permitiram o resultado — nunca quem é o culpado.

results/lab9-gameday.md (template)
# Game day — round <n>

**Experiment:** <what was injected, when> · **Duration:** <mm:ss>
**Hypothesis:** <what we expected>
**Result:** <confirmed | refuted> — <one sentence>

## Impact
- Users affected: <which requests, how many, for how long> (from the RED dashboard)
- SLO budget spent: <% of the 28-day budget>

## Timeline
| Time | Event |
|---|---|

## Detection and mitigation
- Time to detect: <min> — how: <alert | dashboard | trace>
- Time to mitigate: <min> — how: <...>

## What went well / what was hard

## Action items
| Action | Type (prevent / detect / mitigate) | Owner |
|---|---|---|

Armadilhas

  • Sem hipótese, não há experimento. Quebrar coisas para "ver o que acontece" ensina pouco; uma hipótese refutada é o resultado valioso.
  • Comece pequeno. Latência antes de quedas, uma dependência antes de duas, um laptop antes da produção.
  • O botão de abortar precisa funcionar antes da primeira injeção. Teste o reset primeiro.
  • Resolva os itens de ação. Um game day cujas descobertas ficam num documento será repetido, com as mesmas descobertas, por um incidente real.

Próximo: Meça