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
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.
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.
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).
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.
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.
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])))
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.
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.
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.
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.
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.
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.
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
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])))
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.
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.
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.
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.
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.
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.
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.
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]))
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.
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.
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.
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 }
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.
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.
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.
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.
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 alertaHabilite 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.
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.
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:
- Sintoma — qual alerta disparou e o que o usuário está vendo.
- Verificação — as queries exatas, copiáveis, com links do dashboard.
- Mitigação — comandos concretos, em ordem, do menos ao mais destrutivo.
- Escalação — para quem ligar e a partir de qual minuto.
- Pós-incidente — o que salvar antes que se perca: trace_ids, prints, janela de horário.
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.
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.
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.
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
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.
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.
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 budgetrunbooks/— pelo menos dois modos de falha, com queries copiáveisdocs/postmortem-template.mde o post-mortem do GameDayREADME.md— como subir tudo do zero num VPS limpo
Critérios de aprovação
| Critério |
|---|
Seu progresso fica salvo neste navegador.