Signal Path · SRE e observabilidade

Signal Path: Da métrica ao post-mortem

Um percurso prático de SRE e observabilidade: você instrumenta um serviço real, mede confiabilidade com SLO e error budget, correlaciona métricas, traces e logs, e fecha o ciclo com alertas, runbook, incidente e post-mortem. Tudo sobre Docker Compose, para rodar no seu próprio VPS (Hetzner, Oracle, Hostinger) com Coolify ou Portainer.

módulos
10
fases
5
projeto final
1

Prometheus Grafana Loki Tempo OpenTelemetry Alertmanager Docker Compose

Compartilhar WhatsAppLinkedInX

Módulo 1 · Fase 1 · Fundamentos

Fundamentos de SRE e observabilidade

Antes de instalar qualquer coisa: qual problema estamos resolvendo e com quais sinais.

Monitoring não é observabilidade

Monitoring responde perguntas que você já sabia que faria: o contêiner está no ar?, a CPU passou de 80%?, o endpoint responde? Observabilidade permite responder perguntas que você não antecipou: por que apenas algumas requests de /checkout levaram 8 segundos entre 14:02 e 14:07?

A matéria-prima são os dados que seu sistema emite — a telemetria — e os três sinais clássicos são métricas, logs e traces. Cada um responde uma pergunta diferente, e o valor real aparece quando você os conecta.

Regra mental do curso

A métrica diz QUE existe um problema. O trace diz ONDE ele está. O log explica POR QUÊ. O SLO e o burn rate dizem QUÃO URGENTE é.

Os quatro Golden Signals

Latência (quanto demoram as requests), tráfego (quanto trabalho entra), erros (quantas falham) e saturação (quão perto do limite você está). Com esses quatro números você responde em dez segundos se um serviço está saudável.

Dois métodos derivados organizam o trabalho: RED (Rate, Errors, Duration) olha o serviço pela ótica do usuário; USE (Utilization, Saturation, Errors) olha o recurso — CPU, memória, disco, conexões de banco. Um bom dashboard usa os dois, nessa ordem: primeiro impacto, depois causa.

Antipadrão frequente

Ter trinta painéis e nenhuma resposta. Um dashboard bonito não é observabilidade: se ninguém consegue ir de "algo está errado" até "esta request gastou o tempo nesta chamada externa" em menos de cinco minutos, você ainda não tem.

Percentis, não médias

Se 99 requests levam 100 ms e uma leva 10 s, a média mente e aquele usuário foi embora. Por isso medimos p50 (experiência típica), p95 (o usado em acordos) e p99 (a cauda longa, onde vivem timeouts e retries).

Exercício 1

Mapeie os Golden Signals de um serviço seu

Escolha um serviço real que já esteja rodando. Escreva, para cada sinal, qual número o representa hoje e de onde ele viria: latência (p95 de qual endpoint), tráfego (req/s), erros (o que conta como erro: só 5xx?, também 4xx de negócio?) e saturação (CPU, memória, pool de conexões). Se você não sabe de onde sai um número, marque como lacuna.

Exercício 2

Desenhe o fluxo de telemetria atual

Diagrame o caminho que cada sinal faz hoje da sua app até onde alguém a olha: app → qual coletor? → qual backend? → qual UI? Marque em vermelho os trechos que não existem. Ao terminar o módulo 8, esse mesmo diagrama precisa estar completo.

Entregável do módulo

Vai para o projeto final: Essa definição de request falha é literalmente o SLI que você vai medir no módulo 4 e o que vai disparar seus alertas no módulo 5. Escolha com cuidado.

Módulo 2 · Fase 2 · Métricas

Métricas com Prometheus

Instrumentar sua app e fazer as primeiras perguntas em PromQL.

Três tipos de métrica dão conta de quase tudo

Um counter só sobe e conta eventos: requests, erros, mensagens processadas. Um gauge sobe e desce e mede um estado instantâneo: conexões abertas, itens na fila, memória. Um histogram distribui observações em buckets e é o que permite calcular percentis de latência no lado do servidor.

A instrumentação mínima de qualquer serviço HTTP são duas métricas: um counter de requests com labels route, method e status, e um histogram de duração com as mesmas labels.

Cardinalidade: o erro que mata o Prometheus

Cada combinação de labels é uma série temporal. Colocar user_id, order_id ou uma URL com IDs como label gera milhões de séries e derruba a instância. Identificadores vão em logs e traces, nunca em labels de métrica. Use a rota (/orders/:id), não a URL.

Prometheus em Docker Compose

services:
  prometheus:
    image: prom/prometheus:latest
    command:
      - --config.file=/etc/prometheus/prometheus.yml
      - --storage.tsdb.retention.time=15d
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prom-data:/prometheus
    ports: ["9090:9090"]

O scrape config aponta para o seu serviço pelo nome da rede interna: no Compose (e num stack de Coolify ou Portainer) os contêineres se enxergam pelo nome do serviço, então targets: ['api:3000'] basta. Nunca exponha /metrics à internet sem proteção.

As três queries que você vai usar sempre

# tráfego (req/s)
sum(rate(http_requests_total[5m]))

# error rate (proporção de 5xx)
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m]))

# latência p95
histogram_quantile(0.95,
  sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
Detalhe que confunde todo mundo

rate() sobre um counter dá a inclinação por segundo, não o total. Sempre rate() antes de sum(), nunca o contrário: somar counters crus de réplicas que reiniciam gera números fantasma.

Exercício 1

Instrumente um serviço real

Adicione prom-client (Node) ou prometheus_client (Python) a um serviço seu. Exponha /metrics com um counter http_requests_total{route,method,status} e um histogram http_request_duration_seconds. Verifique que a rota esteja normalizada (sem IDs na label) e que os buckets cubram sua faixa real de latência.

Exercício 2

Suba o Prometheus e escreva as três queries

Monte o docker-compose.yml com o Prometheus fazendo scrape do seu serviço a cada 15 s. Gere carga (hey, k6 ou um loop com curl) e responda na UI: qual é o seu RPS?, qual o error rate?, qual o p95? Salve as três queries com um comentário do que cada uma responde.

Entregável do módulo

Vai para o projeto final: Esse histogram é o que vai alimentar os exemplars do módulo 8. Se você deixá-lo bem nomeado e com buckets sensatos agora, a correlação depois sai de graça.

Módulo 3 · Fase 2 · Métricas

Grafana: dashboards que respondem perguntas

Um painel não é uma coleção de gráficos: é uma ordem de leitura.

A ordem importa mais que os painéis

Um dashboard operacional se lê de cima para baixo como uma triagem. Camada 1: impacto — disponibilidade, error rate, latência, error budget. Camada 2: RED — tráfego, erros e duração por rota. Camada 3: saturação — CPU, memória, réplicas, restarts, pool de conexões. Camadas 4 e 5 (a partir do módulo 6): logs e traces do período que você está olhando.

O teste do dashboard

Se às 3h da manhã alguém abre o painel, a primeira tela — sem rolar — precisa responder: há usuários afetados e desde quando? Todo o resto fica abaixo.

Grafana também entra no compose

  grafana:
    image: grafana/grafana:latest
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
    ports: ["3001:3000"]

O volume de provisioning é o que separa um brinquedo de algo operável: datasources e dashboards definidos por arquivo, versionados no git, reproduzíveis em outro VPS. Um dashboard feito na mão na UI e não exportado se perde no dia em que o volume quebra.

Variáveis: um painel, muitos serviços

Em vez de clonar o dashboard por serviço, defina uma variável $service com a query label_values(http_requests_total, service) e use em todas as consultas. O mesmo painel serve para sua API, seu worker e o bot de WhatsApp.

Antipadrão

Painéis com eixos automáticos e sem unidade. Se o eixo não diz segundos ou porcentagem, e o limite do SLO não está desenhado como linha, ninguém sabe se 0,42 está bom ou ruim.

Exercício 1

Dashboard v1 em quatro painéis

Construa um painel com: disponibilidade (1 − error ratio) como stat grande, RPS por rota, error rate com limite pintado, e p95/p99 no mesmo gráfico. Coloque unidades corretas em cada eixo e uma linha de limite na latência. Exporte o JSON para grafana/dashboards/red-v1.json.

Exercício 2

Torne reproduzível

Passe o datasource do Prometheus e o dashboard para arquivos de provisioning. Apague o volume do Grafana, suba a stack de novo e confirme que tudo volta sozinho. Se não voltar, você ainda tem configuração que vive só na UI.

Entregável do módulo

Vai para o projeto final: Este painel é a base do dashboard final. No módulo 4 você adiciona a linha de SLO e error budget, e no 8 os painéis correlacionados.

Módulo 4 · Fase 3 · Confiabilidade medida

SLI, SLO, error budget e burn rate

Transformar 'está funcionando' em um número que dá para discutir com produto.

Três siglas que não são sinônimos

O SLI é o que você mede (disponibilidade = 99,92%). O SLO é a meta interna que você define (99,9% em 30 dias). O SLA é um compromisso contratual com dinheiro ou créditos no meio. Quase nenhum serviço precisa de SLA; quase todos deveriam ter SLO.

O error budget

Se o SLO é 99,9%, você pode falhar 0,1%. Em 30 dias isso dá 43 minutos e 12 segundos de indisponibilidade. Esse orçamento é uma ferramenta de decisão: enquanto sobra budget, dá para deployar rápido e correr riscos; quando queimou, a prioridade vira estabilizar.

SLO 99.9%   → 43m 12s / 30 dias
SLO 99.95%  → 21m 36s / 30 dias
SLO 99.99%  →  4m 19s / 30 dias
Não escolha 99,99% porque soa bem

Cada nove a mais multiplica o custo: redundância, plantão, deploys mais lentos. O SLO se escolhe olhando o que o usuário tolera e o que o negócio está disposto a pagar, não o que impressiona numa reunião.

Burn rate: a velocidade de queima

O burn rate é quanto mais rápido do que o permitido você está gastando o orçamento: error_ratio / (1 − SLO). Um burn rate de 1× consome o budget exatamente em 30 dias. Um 14,4× consome em pouco mais de dois dias — isso justifica acordar alguém. Um 3× sustentado não é urgente hoje à noite, mas destrói o mês.

Recording rules: calcule uma vez

groups:
  - name: slo
    interval: 30s
    rules:
      - record: job:http_error_ratio:rate5m
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m]))
          / sum(rate(http_requests_total[5m]))
      - record: job:http_availability:ratio30d
        expr: 1 - (
          sum(rate(http_requests_total{status=~"5.."}[30d]))
          / sum(rate(http_requests_total[30d])))
Por que vale a pena

Com as regras gravadas, dashboards e alertas consultam um nome curto em vez de recalcular uma query pesada a cada 10 segundos. Além disso unifica a definição: uma única verdade sobre o que é "disponibilidade" em toda a sua stack.

Exercício 1

Defina e justifique seu SLO

Escreva em docs/slo.md dois SLOs para seu serviço: um de disponibilidade e um de latência (por exemplo, 99% das requests abaixo de 500 ms). Para cada um, anote o error budget em minutos por mês e uma frase do porquê desse número e não de um mais alto. Se não conseguir justificar, ainda não é um SLO.

Exercício 2

Recording rules + painel de budget

Crie rules/slo.yml com error ratio em 5m, 30m, 1h, 6h e disponibilidade em 30d. Carregue no Prometheus, verifique em /rules que avaliam sem erro, e adicione ao dashboard um painel de error budget restante (1 − (error_ratio_30d / (1 − SLO))) em porcentagem.

Entregável do módulo

Vai para o projeto final: As regras deste módulo são exatamente as que vão disparar os alertas do módulo 5. Sem SLO não existe alerta bom: só limites inventados.

Módulo 5 · Fase 3 · Confiabilidade medida

Alertas que não acordam ninguém à toa

Alertar sobre sintomas do usuário, não sobre sintomas da máquina.

O teste das três da manhã

Antes de criar um alerta, responda: se isso tocar às 3h, alguém precisa levantar e fazer algo agora? Se a resposta for não, não é um page: é um ticket, ou puro ruído. Fadiga de alerta é a forma mais rápida de um time parar de olhar o celular.

Antipadrão clássico

CPU > 70%. CPU alta sozinha não significa impacto: pode ser o pico normal do meio-dia. Alerte sobre disponibilidade, latência e burn rate — ou seja, sobre o que o usuário sofre.

Alertas multi-janela de burn rate

A receita padrão do Google usa duas janelas por severidade, para detectar rápido sem disparar por um pico de trinta segundos: a janela longa confirma que o problema é real, a curta confirma que continua acontecendo.

groups:
  - name: slo-alerts
    rules:
      - alert: ErrorBudgetFastBurn
        expr: |
          job:http_error_ratio:rate1h > (14.4 * 0.001)
          and job:http_error_ratio:rate5m > (14.4 * 0.001)
        for: 2m
        labels: { severity: page }
        annotations:
          summary: "Queima rápida do error budget"
          runbook: "https://git.seu-dominio/runbooks/availability.md"

      - alert: ErrorBudgetSlowBurn
        expr: |
          job:http_error_ratio:rate6h > (6 * 0.001)
          and job:http_error_ratio:rate30m > (6 * 0.001)
        for: 15m
        labels: { severity: ticket }

O Alertmanager decide quem fica sabendo

O Prometheus detecta a condição; o Alertmanager faz o resto: agrupa alertas relacionados para não mandar vinte mensagens, deduplica, silencia durante manutenções e inibe alertas filhos quando o pai já disparou. A rota é definida por label: severity: page vai para o canal que acorda, severity: ticket para o que se lê em horário comercial.

Atalho útil com n8n

Em vez de configurar cada integração dentro do Alertmanager, mande um único webhook_configs para um fluxo n8n. Lá você formata a mensagem, enriquece com o link do dashboard e do runbook, e roteia para WhatsApp, Slack ou e-mail conforme severidade e horário. Uma só integração para manter.

Todo alerta precisa de um runbook

A anotação runbook não é decorativa: é o link que a pessoa de plantão abre meio dormindo. Se o alerta não tem runbook, ainda não está pronto.

Exercício 1

Escreva dois alertas de burn rate

Crie rules/alerts.yml com um alerta fast burn (14,4× em 1h e 5m, severidade page) e um slow burn (6× em 6h e 30m, severidade ticket), ambos com summary e link de runbook. Valide com promtool check rules antes de recarregar.

Exercício 2

Roteamento e teste real

Configure alertmanager.yml com duas rotas por severidade e um webhook para o n8n que formate e entregue a mensagem. Depois quebre algo de propósito (mate uma dependência, devolva 500 numa fração das requests) e cronometre: quanto levou do primeiro erro até a mensagem no seu telefone. Esse número é o seu MTTD.

Entregável do módulo

Vai para o projeto final: O MTTD que você mede aqui é a primeira métrica do seu processo de incidentes do módulo 9. Guarde: no fim do curso você vai comparar com o do GameDay.

Módulo 6 · Fase 4 · Logs e traces

Logs estruturados e Loki

Parar de ler texto solto e começar a consultar eventos.

Um log é um evento, não uma frase

Log em texto livre: Erro ao processar o pedido do usuário. Log estruturado:

{"ts":"2026-09-08T14:03:11Z","level":"error","service":"api",
 "route":"/orders/:id","status":500,"duration_ms":831,
 "trace_id":"b2fe643bc4d341a1f7076f265910e649","order_id":"o_8812",
 "msg":"payment gateway timeout"}

O segundo dá para filtrar, agrupar e contar. Os campos mínimos que não podem faltar: timestamp, level, service, route, status, duration_ms e trace_id. Esse último campo é o que vai conectar este log ao seu trace no módulo 8.

Loki não é Elasticsearch

O Loki não indexa o conteúdo do log: indexa só as labels do stream e guarda o resto comprimido. Por isso é barato de rodar num VPS, e por isso as labels precisam ser poucas e de baixa cardinalidade: service, env, level. Nada além disso.

O erro que explode o Loki

Colocar trace_id, user_id ou order_id como label de stream. Cada valor diferente cria um stream novo e o índice fica ingerenciável. Esses campos vão dentro do JSON do log e se filtram com | json | trace_id="...", que é igualmente rápido para o que você precisa.

Coleta no Docker

  loki:
    image: grafana/loki:latest
    command: -config.file=/etc/loki/local-config.yaml
    volumes: [ "loki-data:/loki" ]

  alloy:
    image: grafana/alloy:latest
    volumes:
      - ./alloy/config.alloy:/etc/alloy/config.alloy:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    command: run /etc/alloy/config.alloy

O Alloy descobre os contêineres pelo socket do Docker, aplica labels a partir do nome e das etiquetas do contêiner, e empurra para o Loki. Alternativa mais simples se seu VPS já estiver organizado: o driver de logs do Docker apontando direto para o Loki.

LogQL em três movimentos

# todos os erros do serviço
{service="api", level="error"}

# só 5xx, parseando o JSON
{service="api"} | json | status >= 500

# taxa de erros por rota, últimos 5 minutos
sum by (route) (
  rate({service="api"} | json | status >= 500 [5m]))
Retenção: decida agora

Num VPS o disco é finito. Defina retenção com antecedência (por exemplo 14 dias de logs e 30 de métricas) na config do Loki, e não quando acabar o espaço num domingo.

Exercício 1

Migre sua app para logging estruturado

Substitua os console.log / print por um logger JSON (pino, winston, structlog). Garanta que cada request logue uma linha com route, status, duration_ms e trace_id, e que os erros incluam o stack em um campo separado, não concatenado à mensagem.

Exercício 2

Loki + três consultas úteis

Adicione Loki e Alloy ao compose, adicione o datasource no Grafana e salve três consultas LogQL: todos os 5xx da última hora, os erros de uma rota específica, e a taxa de erros por rota. Adicione um painel de logs ao dashboard, abaixo dos painéis de métricas.

Entregável do módulo

Vai para o projeto final: Por enquanto o trace_id dos seus logs não aponta para lugar nenhum: no módulo 7 nasce o trace, e no 8 o campo vira um link clicável.

Módulo 7 · Fase 4 · Logs e traces

Traces distribuídos com OpenTelemetry e Tempo

Saber onde o tempo foi embora, span por span.

Trace, span e contexto

Um trace é o percurso completo de uma request pelo seu sistema. Cada etapa mensurável é um span: a request HTTP de entrada, a query no Postgres, o GET na API de pagamentos, o job enfileirado. Os spans formam uma árvore, e ali se vê na hora onde o tempo foi:

POST /orders                    2.41s
├── auth.verify                  38ms
├── db.query orders_insert      142ms
├── http POST payments.api      2.14s   ←
└── queue.publish order.created  21ms

A conclusão não é "a app está lenta": é "o gateway de pagamentos está consumindo 89% do tempo". São duas conversas completamente diferentes.

OpenTelemetry: instrumentar uma vez

OTel é o padrão aberto que evita ficar preso a um fornecedor: você instrumenta com o SDK dele e depois decide para onde manda os dados — Tempo hoje, Datadog ou Grafana Cloud amanhã, sem tocar no código da app.

A auto-instrumentação cobre o óbvio (servidor HTTP, cliente HTTP, driver de banco, Redis) com pouquíssimo código. Os spans manuais você adiciona onde está sua lógica cara: o cálculo pesado, a chamada ao LLM, o render do PDF.

O span que sempre falta

Ninguém instrumenta o que não suspeita. Coloque um span manual em volta de cada chamada a terceiros e de cada operação que possa passar de 100 ms. Os spans que você nunca criou são exatamente os que vai sentir falta durante o incidente.

O Collector como amortecedor

  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    command: ["--config=/etc/otel/config.yaml"]
    volumes: [ "./otel/config.yaml:/etc/otel/config.yaml:ro" ]
    ports: ["4318:4318"]   # OTLP/HTTP

  tempo:
    image: grafana/tempo:latest
    command: ["-config.file=/etc/tempo.yaml"]
    volumes:
      - ./tempo/tempo.yaml:/etc/tempo.yaml:ro
      - tempo-data:/var/tempo

A app fala OTLP com o Collector, e o Collector decide o que fazer: filtrar spans de health check, amostrar, adicionar atributos de ambiente e exportar para o Tempo. Trocar de backend depois é trocar um exporter, não redeployar a aplicação.

TraceQL para achar a agulha

{ resource.service.name = "api" && duration > 1s }
{ span.http.status_code >= 500 }
{ name = "http POST payments.api" && duration > 2s }
Sampling

Guardar 100% dos traces num VPS pequeno enche o disco rápido. Amostre (10% é um ponto de partida razoável) mas mantenha sempre 100% dos traces com erro: são esses que você vai olhar.

Exercício 1

Instrumente com OTel e envie ao Tempo

Adicione o SDK do OpenTelemetry com auto-instrumentação HTTP e de banco, exportando OTLP/HTTP para o Collector. Adicione Collector e Tempo ao compose e confirme no Grafana Explore que você vê traces completos com seus spans filhos.

Exercício 2

Ache sua operação mais lenta

Adicione um span manual à operação mais cara do seu serviço, com atributos úteis (tamanho do payload, provedor externo, se houve cache hit). Depois busque com TraceQL os traces acima de 1 s e anote em qual span o tempo se vai. Essa é sua primeira hipótese de otimização baseada em dados.

Entregável do módulo

Vai para o projeto final: Você já tem os três sinais, mas ainda separados: três abas e muito copiar e colar. O módulo 8 os une.

Módulo 8 · Fase 4 · Logs e traces

Correlação: métrica → trace → log

O salto de três cliques que derruba o MTTR de horas para minutos.

Uma chave comum: trace_id

Os três sinais que você montou até aqui vivem em bases diferentes. O que os conecta é um identificador que viaja com a request: o trace_id. Se seu histogram o anexa como exemplar, seu log o escreve como campo e seu trace o tem por definição, você percorre o caminho inteiro sem escrever uma query na mão.

É isso que derruba o MTTR

Adicionar mais painéis não encurta um incidente. O que encurta é conseguir ir do pico no gráfico até a request concreta, e dali até a linha de log do erro, em três cliques. Este módulo inteiro existe para isso.

Exemplars: da métrica ao trace

Um exemplar é um ponto concreto anexado a um bucket do histogram, com o trace_id de uma request real que caiu ali. No gráfico de latência aparece como um pontinho sobre a curva: você clica e abre o trace que produziu aquele valor.

# o Prometheus precisa do feature habilitado
command:
  - --enable-feature=exemplar-storage

# e o scrape em formato OpenMetrics
scrape_configs:
  - job_name: api
    static_configs: [{ targets: ["api:3000"] }]

Do lado da app, a biblioteca do Prometheus precisa registrar a observação com o exemplar do trace ativo. Em Node com prom-client passa-se como terceiro argumento do observe(); em Python, com exemplar=.

Derived fields: do log ao trace

Na configuração do datasource do Loki você define um campo derivado que detecta o trace_id no JSON e o transforma num botão que abre o Tempo:

derivedFields:
  - name: TraceID
    matcherRegex: '"trace_id":"(\w+)"'
    url: '${__value.raw}'
    datasourceUid: tempo

E no datasource do Tempo você ativa o caminho inverso (Trace to logs): de um span, pular para os logs daquele mesmo trace_id na janela de tempo do span. Ida e volta fechada.

Confira o relógio

Se os contêineres estiverem com relógios defasados, o salto trace → logs volta vazio mesmo com tudo configurado: o Grafana busca numa janela de tempo que não bate. Sincronize NTP no VPS antes de enlouquecer depurando a config.

O percurso completo

Alerta de burn rate
      ↓  (link do runbook)
Dashboard: p95 em 12s desde as 14:02
      ↓  (clique no exemplar)
Trace: POST /orders — 11.8s em payments.api
      ↓  (Trace to logs)
Log: "payment gateway timeout" order_id=o_8812
      ↓
Causa encontrada — 4 minutos desde o alerta
Exercício 1

Habilite exemplars e faça o salto

Ative exemplar-storage no Prometheus, anexe o trace_id ao observar a latência e ligue Show exemplars no painel de p95. Gere carga lenta e confirme que dá para clicar num ponto do gráfico e cair no trace certo.

Exercício 2

Feche o círculo entre log e trace

Configure o derived field no datasource do Loki e o Trace to logs no do Tempo. Depois faça o percurso completo cronometrado: do painel de latência até a linha de log da causa. Anote quantos cliques e quantos segundos levou — esse número é o que você vai defender no projeto final.

Entregável do módulo

Vai para o projeto final: Esse percurso é literalmente o corpo do seu runbook do módulo 9: em vez de escrever 'olhe os logs', você vai escrever os três cliques exatos.

Módulo 9 · Fase 5 · Operação

Resposta a incidentes, severidade e runbooks

O processo que transforma pânico em passos ordenados.

As quatro etapas e os três relógios

Um incidente é detectado, reconhecido, mitigado e resolvido — e cada trecho tem sua métrica. MTTD (detectar): do primeiro erro ao alerta. MTTA (reconhecer): do alerta até alguém assumir. MTTR (restaurar): do início ao serviço normal. Se você não mede os três separadamente, não sabe qual melhorar: um MTTD de 20 minutos não se resolve com mais gente de plantão, se resolve com alertas melhores.

Mitigar primeiro, entender depois

Durante o incidente, o objetivo é restaurar o serviço, não achar a causa raiz. Rollback, feature flag desligada, escalar réplicas, cortar o tráfego para o provedor caído. A investigação vai no post-mortem, com o serviço já saudável.

Severidade: defina antes de precisar

SEV1  Serviço fora ou perda de dados.
      Todos os usuários. Acorda-se gente. Comunicação imediata.
SEV2  Degradação séria: SLO em risco, funcionalidade
      importante quebrada. Atende-se já, no horário ou fora.
SEV3  Impacto limitado ou com workaround.
      Ticket, resolve-se em horário comercial.

O critério é sempre impacto no usuário, nunca quanto código precisa ser tocado. Um typo no CSS do checkout que impede comprar é SEV1; um worker de relatórios parado pode ser SEV3.

Anatomia de um runbook que serve

Um runbook é para quem abre o celular às 3h, não para quem escreveu o sistema. Cinco seções:

  1. Sintoma — qual alerta disparou e o que o usuário está vendo.
  2. Verificação — as queries exatas, copiáveis, com links do dashboard.
  3. Mitigação — comandos concretos, em ordem, do menos ao mais destrutivo.
  4. Escalação — para quem ligar e a partir de qual minuto.
  5. Pós-incidente — o que salvar antes que se perca: trace_ids, prints, janela de horário.
Antipadrão

Um runbook que diz "olhar os logs e verificar se o serviço está saudável" não é runbook, é desejo. Se um passo não dá para copiar e colar, ainda não está pronto.

Exercício 1

Escreva um runbook real

Escolha o modo de falha mais provável do seu serviço (dependência externa fora, banco saturado, disco cheio) e escreva o runbook completo com as cinco seções. As queries de verificação precisam ser as que você montou nos módulos 2, 6 e 8, copiáveis como estão.

Exercício 2

Simulado cronometrado

Quebre o serviço de propósito e conduza o incidente inteiro como se fosse real: espere o alerta, assuma, siga seu próprio runbook, mitigue. Registre os três tempos e anote honestamente qual passo do runbook não serviu. Corrija no mesmo dia.

Entregável do módulo

Vai para o projeto final: O simulado deste módulo é o ensaio. No módulo 10 você repete pra valer, com falhas que não foi você que escolheu, e escreve o post-mortem.

Módulo 10 · Fase 5 · Operação

Post-mortem e GameDay

Que o incidente deixe aprendizado e não só cansaço.

Post-mortem sem culpados, de verdade

"Blameless" não é gentileza: é a única coisa que faz as pessoas contarem o que realmente aconteceu. Se quem apertou o botão sabe que vai ser apontado, na próxima o timeline vai ter buracos justamente onde estava a informação útil. A pergunta certa nunca é quem errou, e sim o que fez aquela ação parecer razoável naquele momento.

As seis seções

1. Resumo         — 3 linhas: o que houve, quem foi afetado, quanto durou.
2. Impacto        — usuários, requests falhas, error budget consumido,
                    dinheiro se aplicável.
3. Timeline       — com horas exatas: primeiro erro, alerta, ack,
                    mitigação, resolução. Dali saem MTTD/MTTA/MTTR.
4. Causa          — a técnica e também a contribuinte (por que o
                    sistema permitiu que acontecesse).
5. O que funcionou— sim, esta seção entra. O alerta que disparou certo e
                    o runbook que serviu também são resultados.
6. Action items   — cada um com dono, data e ticket. Sem dono não é
                    action item, é comentário.
A única métrica que importa do post-mortem

O percentual de action items fechados. Um documento lindo com doze tarefas que ninguém tocou em três meses significa que o incidente vai se repetir exatamente igual.

GameDay: quebrar de propósito, em horário comercial

Um GameDay é um incidente planejado: você escolhe um dia, avisa o time, injeta falhas reais e verifica se sua observabilidade as detecta. É a única forma honesta de saber se a stack serve — porque no dia do incidente real você não quer descobrir que o alerta nunca esteve bem configurado.

Hipótese:   "Se o provedor de pagamentos começar a devolver 500,
             o alerta fast burn dispara em menos de 5 minutos e
             o runbook nos leva à causa em menos de 10."
Injeção:    proxy que devolve 500 em 30% das chamadas
Observação: disparou? em quanto tempo? o runbook bastou?
Aprendizado: o que faltou — um span, um painel, um passo do runbook
Regras do GameDay

Avise sempre (não é teste-surpresa com as pessoas), tenha o plano de rollback escrito antes de começar, defina hora de corte fixa, e silencie os canais de alerta para não confundir quem não está participando.

Três falhas para começar

Dependência externa lenta ou quebrada, saturação de recursos (CPU no limite ou pool de conexões esgotado), e perda de um contêiner (matar e ver se o sistema se recupera sozinho). Com essas três você já encontra pelo menos um buraco na sua instrumentação.

Exercício 1

Rode um GameDay de 60 minutos

Escreva o plano antes: três hipóteses, três injeções, critério de sucesso para cada e plano de rollback. Execute cronometrando tudo. Anote qual falha o seu stack NÃO detectou — essa é a mais valiosa do dia.

Exercício 2

Escreva o post-mortem do GameDay

Use as seis seções sobre o que realmente aconteceu, com os tempos medidos. Os action items precisam ser concretos: 'adicionar span à chamada de pagamentos', 'baixar o for: do alerta de 5m para 2m', com dono e data. Feche pelo menos um na mesma semana.

Entregável do módulo

Vai para o projeto final: Com isso você fecha o ciclo completo: instrumentar, medir, alertar, correlacionar, responder e aprender. O que resta é empacotar como uma stack que outra pessoa consiga subir.

Projeto final

Projeto final: a stack completa sobre um serviço real

O entregável final não é um laboratório de brinquedo: é a observabilidade de um serviço seu que realmente roda, empacotada de forma que outra pessoa consiga subir com um docker compose up -d e entender em quinze minutos.

O que precisa estar rodando

  • Serviço instrumentado: métricas RED, logs JSON com trace_id, traces OTel
  • Prometheus com recording rules de SLO e exemplars habilitados
  • Grafana provisionado por arquivos: datasources, dashboard de cinco camadas, variável de serviço
  • Loki + Alloy com retenção definida, Tempo com sampling
  • Alertmanager com duas severidades roteando para um fluxo n8n
  • Toda a stack atrás de autenticação, nada de portas abertas para o mundo

O que precisa estar escrito

  • docs/slo.md — dois SLOs justificados com seu error budget
  • runbooks/ — pelo menos dois modos de falha, com queries copiáveis
  • docs/postmortem-template.md e o post-mortem do GameDay
  • README.md — como subir tudo do zero num VPS limpo

Critérios de aprovação

Critério

Seu progresso fica salvo neste navegador.