DevOps

Artigo 34 — Alertas Inteligentes e Cultura de Resposta a Incidentes Já leu

15 min de leitura

Artigo 34 — Alertas Inteligentes e Cultura de Resposta a Incidentes
Artigo 34 — Alertas Inteligentes e Cultura de Resposta a Incidentes O Paradoxo dos Alertas Existe um paradoxo bem documentado em times de operações: quanto

Artigo 34 — Alertas Inteligentes e Cultura de Resposta a Incidentes

Módulo 6 · Monitoramento e Observabilidade Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano


O Paradoxo dos Alertas

Existe um paradoxo bem documentado em times de operações: quanto mais alertas existem, menos atenção cada um recebe. Um sistema que envia cinquenta notificações por dia treina a equipe a ignorá-las. Quando o alerta crítico chega, ele se perde no ruído.

Esse fenômeno tem nome — alert fatigue — e é uma das causas mais comuns de incidentes graves que poderiam ter sido detectados e resolvidos mais cedo. A equipe viu o alerta, mas o tratou como mais um falso positivo em uma fila interminável.

A solução não é ter menos alertas a qualquer custo — é ter alertas de alta qualidade. Um alerta de alta qualidade tem três características: é acionável (existe algo concreto a fazer quando ele chega), é preciso (dispara quando há realmente um problema, não antes), e é contextualizado (chega com informação suficiente para que o receptor saiba por onde começar).

Este artigo cobre como construir esse sistema — desde a filosofia de alertas até a cultura de resposta a incidentes que transforma cada problema em aprendizado.


Os Quatro Sinais de Ouro

O Google SRE Book define quatro sinais que, juntos, cobrem a saúde de qualquer serviço que atende requisições de usuários. Qualquer sistema de alertas eficaz começa por monitorar esses quatro sinais antes de qualquer outra métrica.

Latência — o tempo que leva para atender uma requisição. É importante distinguir latência de requisições bem-sucedidas da latência de requisições com erro — um erro rápido não é necessariamente bom. O percentil 99 é mais relevante que a média, porque a média esconde os usuários com pior experiência.

Tráfego — a demanda atual sobre o sistema. Para uma API HTTP, são requisições por segundo. Para um sistema de streaming, são bytes por segundo. Para um banco de dados, são transações por segundo. O tráfego contextualiza todos os outros sinais — uma taxa de erros de 1% com tráfego de 10 req/s é muito diferente de 1% com tráfego de 10.000 req/s.

Erros — a taxa de requisições que falham. Inclui tanto erros explícitos — HTTP 5xx — quanto erros implícitos — HTTP 200 com conteúdo incorreto ou incompleto. A definição de "erro" deve ser estabelecida em termos do que representa uma falha para o usuário, não apenas do que o servidor considera falha.

Saturação — quão "cheio" está o serviço. Mede a utilização dos recursos mais constrangidos — CPU, memória, disco, conexões de banco de dados, threads de worker. A saturação é um indicador preditivo: um serviço pode estar funcionando bem agora mas prestes a degradar porque seus recursos estão quase esgotados.


Estrutura de um Alerta Eficaz

Cada alerta deve ser construído como uma unidade completa de informação, não como um trigger que obriga o receptor a ir buscar contexto em outro lugar:

# observabilidade/prometheus/rules/alertas-gold-signals.yml
groups:
  - name: sinais-de-ouro
    rules:

      # ── Latência ─────────────────────────────────────
      - alert: LatenciaAltaP99
        expr: |
          histogram_quantile(0.99,
            sum by (job, le) (
              rate(http_request_duration_seconds_bucket{job="minha-api"}[5m])
            )
          ) > 1.0
        for: 5m
        labels:
          severity: warning
          sinal: latencia
          team: backend
        annotations:
          summary: "Latência P99 acima de 1s em {{ $labels.job }}"
          description: |
            A latência P99 está em {{ $value | humanizeDuration }}.
            Threshold: 1 segundo por mais de 5 minutos consecutivos.

            Possíveis causas:
            - Queries lentas no banco de dados
            - Serviço externo com alta latência
            - GC pause no processo Node.js
            - Conexões de banco esgotadas
          grafana_url: "https://grafana.empresa.com/d/minha-api-red"
          runbook_url: "https://wiki.empresa.com/runbooks/latencia-alta"

      # ── Erros ─────────────────────────────────────────
      - alert: TaxaDeErrosCritica
        expr: |
          (
            sum(rate(http_requests_total{job="minha-api",status=~"5.."}[5m]))
            /
            sum(rate(http_requests_total{job="minha-api"}[5m]))
          ) > 0.01
        for: 2m
        labels:
          severity: critical
          sinal: erros
          team: backend
        annotations:
          summary: "Taxa de erros 5xx acima de 1% em {{ $labels.job }}"
          description: |
            Taxa de erros: {{ $value | humanizePercentage }}
            Threshold: 1% por mais de 2 minutos.

            Impacto estimado: {{ with query "sum(rate(http_requests_total{job='minha-api'}[5m]))" }}
            {{ . | first | value | humanize }} usuários/segundo afetados{{ end }}.

            Verificar:
            1. Logs de erro recentes: https://grafana.empresa.com/explore?...
            2. Último deploy: https://github.com/empresa/repo/deployments
            3. Status do banco de dados
          grafana_url: "https://grafana.empresa.com/d/minha-api-red"
          runbook_url: "https://wiki.empresa.com/runbooks/taxa-erros-critica"

      # ── Saturação — conexões de banco ─────────────────
      - alert: ConexoesBancoDeDadosEsgotando
        expr: |
          (
            pg_stat_activity_count
            /
            pg_settings_max_connections
          ) > 0.80
        for: 5m
        labels:
          severity: warning
          sinal: saturacao
          team: backend
        annotations:
          summary: "Pool de conexões do PostgreSQL acima de 80%"
          description: |
            Conexões em uso: {{ $value | humanizePercentage }} do máximo.
            Em {{ with query "pg_settings_max_connections" }}{{ . | first | value | humanize }}{{ end }} conexões máximas,
            {{ with query "pg_stat_activity_count" }}{{ . | first | value | humanize }}{{ end }} estão em uso.

            Ações imediatas:
            1. Verificar queries longas: SELECT * FROM pg_stat_activity WHERE state = 'active'
            2. Verificar se há connection leaks na aplicação
            3. Considerar aumentar max_connections ou usar PgBouncer
          runbook_url: "https://wiki.empresa.com/runbooks/conexoes-banco"

      # ── Tráfego — queda abrupta ────────────────────────
      # Uma queda de tráfego pode indicar que o serviço parou de receber
      # requisições — tão preocupante quanto um aumento
      - alert: TrafegoBaixoAnomalo
        expr: |
          (
            sum(rate(http_requests_total{job="minha-api"}[5m]))
            <
            sum(rate(http_requests_total{job="minha-api"}[5m] offset 1d)) * 0.3
          )
          and
          sum(rate(http_requests_total{job="minha-api"}[5m] offset 1d)) > 1
        for: 10m
        labels:
          severity: warning
          sinal: trafego
          team: backend
        annotations:
          summary: "Queda abrupta de tráfego em {{ $labels.job }}"
          description: |
            O tráfego atual está 70% abaixo do mesmo período de ontem.
            Tráfego atual: {{ $value | humanize }} req/s
            Possíveis causas: falha no load balancer, DNS, ou upstream.

Runbooks: O Manual de Resposta

Um runbook é um documento que descreve como responder a um tipo específico de incidente. Quando um alerta chega às 3h da manhã, o engenheiro de plantão não deve precisar de experiência prévia com aquele tipo de problema para responder adequadamente — o runbook deve guiá-lo passo a passo.

Estrutura de um runbook eficaz:

# Runbook: Taxa de Erros 5xx Alta

**Alerta:** TaxaDeErrosCritica
**Severidade:** Critical
**Última atualização:** 2025-03-10
**Proprietário:** Time Backend

---

## Impacto

Taxa de erros acima de 1% significa que pelo menos 1 em cada 100
requisições está falhando. Com o tráfego atual de ~500 req/s, isso
representa ~5 usuários por segundo recebendo erros.

---

## Diagnóstico Rápido (< 5 minutos)

### 1. Verificar se o serviço está de pé

```bash
curl -s https://api.empresa.com/health | jq .

Se retornar 200: o serviço está respondendo — problema provavelmente em uma rota específica.

Se não retornar: executar o passo 4 diretamente.

2. Identificar quais rotas estão errando

Abrir o Grafana → Dashboard RED → painel "Erros por Endpoint".

Ou via PromQL:

topk(10, sum by (route, status) (
  rate(http_requests_total{job="minha-api",status=~"5.."}[5m])
))

3. Verificar os logs de erro

# Últimos 100 erros da aplicação
kubectl logs -l app=minha-api --tail=100 | grep '"level":"error"' | jq .

# Ou via Loki (LogQL):
# {service="minha-api"} | json | level="error" | line_format "{{.msg}} {{.err}}"

4. Verificar o status do banco de dados

# Verificar se o banco está acessível
psql $DATABASE_URL -c "SELECT 1"

# Verificar queries lentas
psql $DATABASE_URL -c "
SELECT pid, now() - pg_stat_activity.query_start AS duracao,
       query, state
FROM pg_stat_activity
WHERE state != 'idle' AND query_start < now() - interval '30 seconds'
ORDER BY duracao DESC;"

Ações de Mitigação

Se o problema for uma rota específica com um bug recente:

  1. Identificar o commit que introduziu o problema: bash git log --oneline --since="2 hours ago"
  2. Executar rollback: bash gh workflow run rollback.yml \ -f versao=<SHA_ANTERIOR> \ -f ambiente=production

Se o problema for sobrecarga do banco de dados:

  1. Reiniciar o pool de conexões (sem downtime): bash kubectl rollout restart deployment/minha-api
  2. Se as queries lentas persistirem, escalar o banco: bash aws rds modify-db-instance \ --db-instance-identifier producao-db \ --db-instance-class db.r6g.2xlarge \ --apply-immediately

Se o serviço não estiver respondendo:

  1. Verificar status dos pods: bash kubectl get pods -l app=minha-api kubectl describe pod <pod-com-problema>
  2. Verificar eventos recentes do cluster: bash kubectl get events --sort-by='.lastTimestamp' | tail -20

Escalonamento

Se o problema não for resolvido em 15 minutos: - Primeiro escalonamento: Líder técnico do time backend - Segundo escalonamento: CTO

Contatos de emergência: ver página de plantão no PagerDuty.


Resolução e Fechamento

Ao resolver o incidente: 1. Confirmar que a taxa de erros voltou ao normal no Grafana 2. Documentar a causa raiz no canal #incidentes do Slack 3. Criar issue para o postmortem se o incidente durou > 15 minutos


---

## Conduzindo Postmortems Sem Culpa

Um **postmortem** é uma análise retrospectiva de um incidente com o objetivo de entender o que aconteceu, por que aconteceu e como evitar que aconteça novamente. A palavra "postmortem" pode soar pesada, mas o processo deve ser construtivo — focado no sistema, não nas pessoas.

O princípio fundamental é a **ausência de culpa**: em sistemas complexos, incidentes são o resultado de múltiplos fatores sistêmicos, não de uma única pessoa que "cometeu um erro". A pergunta não é "quem errou?" mas "o que no nosso sistema tornou esse erro possível ou provável?"

**Estrutura de um postmortem:**

```markdown
# Postmortem: Indisponibilidade da API de Checkout — 2025-03-10

**Duração:** 47 minutos (15h43 – 16h30)
**Severidade:** SEV1 — serviço crítico indisponível
**Status:** Resolvido
**Autores:** João Silva, Ana Costa

---

## Resumo Executivo

A API de checkout ficou indisponível por 47 minutos após um deploy
que introduziu uma query PostgreSQL sem índice em um endpoint de
alto tráfego. O aumento de latência nas queries consumiu todas as
conexões disponíveis do pool, tornando o serviço incapaz de atender
novas requisições.

---

## Linha do Tempo

| Horário | Evento |
|---------|--------|
| 15h30   | Deploy da versão 1.8.3 concluído em produção |
| 15h43   | Primeiro alerta de latência alta disparado |
| 15h45   | Engenheiro de plantão começa investigação |
| 15h52   | Causa raiz identificada: query sem índice |
| 16h10   | Índice criado no banco de dados em produção |
| 16h20   | Pool de conexões recuperado gradualmente |
| 16h30   | Serviço normalizado, alerta resolvido |

---

## Causa Raiz

O endpoint `GET /api/checkout/historico` introduzido na versão 1.8.3
executava a seguinte query:

```sql
SELECT * FROM pedidos WHERE usuario_id = $1 ORDER BY criado_em DESC;

A coluna usuario_id não tinha índice na tabela pedidos (4,2 milhões de registros). Cada chamada ao endpoint resultava em um full table scan de ~800ms. Com o aumento de tráfego pós-deploy, o pool de 100 conexões foi consumido em menos de 2 minutos.


Fatores Contribuintes

  1. Ausência de testes de performance no pipeline: O pipeline de CI executa testes funcionais mas não detecta regressões de performance.

  2. Falta de análise de queries no processo de PR: Não há checklist ou ferramenta que verifique queries SQL em PRs.

  3. Ambiente de staging com dados reduzidos: O banco de staging tem apenas 500 registros — a query levava 2ms lá, sem indicação do problema em produção.

  4. Alerta de latência com janela longa: O alerta disparou apenas 5 minutos após o deploy. Uma janela de 1 minuto teria alertado mais cedo.


O Que Foi Bem

  • O runbook foi suficientemente detalhado para guiar o diagnóstico
  • A correlação de logs e traces no Grafana reduziu o tempo de identificação da causa raiz
  • O rollback estava disponível mas não foi necessário — a criação do índice resolveu sem interrupção adicional

Ações Corretivas

Ação Responsável Prazo Issue
Adicionar análise de EXPLAIN para queries em PRs João 2025-03-17 #1234
Criar dados realistas no banco de staging Ana 2025-03-24 #1235
Reduzir janela do alerta de latência de 5m para 1m Pedro 2025-03-12 #1236
Adicionar teste de carga no pipeline (k6) João 2025-03-31 #1237
Documentar checklist de queries SQL para revisores Ana 2025-03-14 #1238

---

## Automatizando a Criação de Incidentes

Para times que usam ferramentas como PagerDuty ou OpsGenie, o processo de criação de incidentes pode ser automatizado diretamente a partir dos alertas do Alertmanager:

```yaml
# Receptor PagerDuty no alertmanager.yml
receivers:
  - name: 'pagerduty-critico'
    pagerduty_configs:
      - routing_key: '${{ secrets.PAGERDUTY_INTEGRATION_KEY }}'
        severity: '{{ (index .Alerts 0).Labels.severity }}'
        description: '{{ (index .Alerts 0).Annotations.summary }}'
        details:
          firing: '{{ .Alerts.Firing | len }}'
          environment: '{{ (index .Alerts 0).Labels.environment }}'
          runbook: '{{ (index .Alerts 0).Annotations.runbook_url }}'
          grafana: '{{ (index .Alerts 0).Annotations.grafana_url }}'
        links:
          - href: '{{ (index .Alerts 0).Annotations.grafana_url }}'
            text: 'Dashboard Grafana'
          - href: '{{ (index .Alerts 0).Annotations.runbook_url }}'
            text: 'Runbook'

Um bot de Slack que centraliza a comunicação de incidentes:

// scripts/incident-bot.js
// Integra Alertmanager → Slack com criação automática de canal de incidente

const { WebClient } = require('@slack/web-api');
const express = require('express');

const slack = new WebClient(process.env.SLACK_BOT_TOKEN);
const app = express();
app.use(express.json());

// Webhook recebido do Alertmanager
app.post('/webhook/alertmanager', async (req, res) => {
  const { alerts, status } = req.body;

  for (const alert of alerts) {
    if (status === 'firing' && alert.labels.severity === 'critical') {
      await criarIncidente(alert);
    } else if (status === 'resolved') {
      await resolverIncidente(alert);
    }
  }

  res.sendStatus(200);
});

async function criarIncidente(alert) {
  const nomeCanal = `incidente-${Date.now()}`;

  // Cria canal dedicado para o incidente
  const { channel } = await slack.conversations.create({
    name: nomeCanal,
    is_private: false,
  });

  // Convida o time de plantão
  await slack.conversations.invite({
    channel: channel.id,
    users: process.env.ONCALL_USER_IDS,
  });

  // Posta a mensagem inicial estruturada
  await slack.chat.postMessage({
    channel: channel.id,
    blocks: [
      {
        type: 'header',
        text: {
          type: 'plain_text',
          text: `🚨 INCIDENTE — ${alert.annotations.summary}`,
        },
      },
      {
        type: 'section',
        fields: [
          { type: 'mrkdwn', text: `*Severidade:*\n${alert.labels.severity}` },
          { type: 'mrkdwn', text: `*Ambiente:*\n${alert.labels.environment}` },
          { type: 'mrkdwn', text: `*Início:*\n${alert.startsAt}` },
          { type: 'mrkdwn', text: `*Time:*\n${alert.labels.team}` },
        ],
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*Descrição:*\n${alert.annotations.description}`,
        },
      },
      {
        type: 'actions',
        elements: [
          {
            type: 'button',
            text: { type: 'plain_text', text: '📊 Grafana' },
            url: alert.annotations.grafana_url,
          },
          {
            type: 'button',
            text: { type: 'plain_text', text: '📖 Runbook' },
            url: alert.annotations.runbook_url,
            style: 'primary',
          },
        ],
      },
    ],
  });

  // Posta no canal geral de incidentes
  await slack.chat.postMessage({
    channel: '#incidentes',
    text: `🚨 Novo incidente crítico em andamento. Acompanhe em <#${channel.id}>.`,
  });
}

Métricas de Maturidade em Observabilidade

Para avaliar a maturidade do sistema de observabilidade de um time, as métricas DORA — já introduzidas no contexto de CI/CD — são complementadas por métricas específicas de operações:

MTTD — Mean Time to Detect. Quanto tempo leva, em média, desde o início de um problema até o alerta ser disparado. Sistemas com boa instrumentação têm MTTD abaixo de 5 minutos.

MTTR — Mean Time to Recover. Quanto tempo leva, em média, desde a detecção do problema até a recuperação do serviço. O MTTR é uma função direta da qualidade dos runbooks, da clareza dos alertas e da experiência da equipe com o sistema.

Alert-to-Action Ratio — que percentual dos alertas disparados resulta em uma ação concreta. Um ratio abaixo de 50% indica excesso de ruído — muitos alertas são ignorados ou descartados como falso positivo.

Postmortem Completion Rate — que percentual de incidentes de alta severidade resulta em um postmortem publicado. Times maduros têm 100% de completion rate para incidentes SEV1 e SEV2.


Encerrando o Módulo 6

Com este artigo encerra-se o Módulo 6 — Monitoramento e Observabilidade. Foram cobertos os três pilares da observabilidade, a construção de dashboards eficazes no Grafana, o rastreamento distribuído com OpenTelemetry e, neste artigo, a cultura de alertas e resposta a incidentes que transforma dados em ação.

O Módulo 7 entra no território da AWS em profundidade — os serviços gerenciados que permitem construir infraestrutura escalável, resiliente e econômica sem gerenciar servidores diretamente.


Referências para Aprofundamento

Cultura e processos - Google SRE Book — Postmortem Culture — Capítulo do SRE Book do Google sobre a cultura de postmortem sem culpa, disponível gratuitamente online. Define os princípios e o processo adotado pelo Google. - PagerDuty Incident Response Guide — response.pagerduty.com — Guia completo de resposta a incidentes mantido pelo PagerDuty, cobrindo papéis, comunicação e processos de escalonamento. Disponível gratuitamente online.

Alertas e SLOs - Google SRE Workbook — Alerting on SLOs — Capítulo técnico sobre como construir alertas baseados em orçamento de erros e burn rate, disponível gratuitamente. - Alertmanager Configuration — prometheus.io — Referência completa da configuração do Alertmanager, incluindo todos os tipos de receptores e opções de roteamento.

Ferramentas - Rootly — rootly.com — Blog da Rootly com artigos práticos sobre gestão de incidentes, postmortems e cultura SRE. - Firehydrant — firehydrant.com — Blog da FireHydrant com recursos sobre resposta a incidentes e automação de runbooks.


Artigo 34 de 52 · Módulo 6 — Monitoramento e Observabilidade Prof. Ricardo Matos · Série Dominando DevOps & Cloud em 1 Ano


you asked

Continue


claude response

Comentários

Mais em DevOps

Navegando pelo Sistema de Arquivos com Confiança
Navegando pelo Sistema de Arquivos com Confiança

No artigo anterior foram apresentados os primeiros movimentos no terminal. Ag...

Cultura DevOps: Maturidade, Postmortems e Melhoria Contínua
Cultura DevOps: Maturidade, Postmortems e Melhoria Contínua

É possível instalar todas as ferramentas descritas nos artigos anteriores — K...

Resiliência e Chaos Engineering
Resiliência e Chaos Engineering

Todo engenheiro que opera sistemas em produção por tempo suficiente aprende u...