Grafana: Dashboards e Alertas que Fazem Sentido

[300] Grafana: Dashboards e Alertas que Fazem Sentido

Dashboards que respondem perguntas em vez de acumular gráficos: provisionamento declarativo do Grafana, o painel de visão geral, a metodologia RED aplicada a serviços, a configuração do Alertmanager, alertas baseados em SLO, anotações de deploy sobre as séries e a organização por audiência.
DevOps

19 min de leitura

Um dashboard mal projetado é quase tão ruim quanto não ter dashboard. Quando um engenheiro de plantão recebe um alerta às três da manhã e abre o Grafana, ele precisa entender o estado do sistema em segundos — não em minutos. Se o dashboard exige interpretação, se os painéis não têm contexto, se os alertas não apontam para onde olhar, o tempo de resposta ao incidente aumenta e a confiança na ferramenta diminui.

Bons dashboards não são feitos de acúmulo de gráficos. São feitos de perguntas respondidas de forma visual. A pergunta vem primeiro — "o sistema está saudável?", "qual serviço está causando lentidão?", "o deploy de hoje afetou a performance?" — e o gráfico é a resposta.

Este artigo aborda como construir dashboards que comunicam, como configurar alertas que chegam ao lugar certo com o contexto certo, e como organizar o Grafana para que seja útil tanto no dia a dia quanto durante incidentes.

Provisionamento Declarativo do Grafana

Em vez de configurar datasources e dashboards manualmente pela interface, o Grafana suporta provisionamento declarativo — arquivos YAML e JSON que definem toda a configuração. Isso permite versionar a observabilidade junto com o código da aplicação.

Provisionando datasources:

# observabilidade/grafana/provisioning/datasources/datasources.yml
apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    editable: false
    jsonData:
      timeInterval: "15s"
      queryTimeout: "60s"
      httpMethod: POST

  - name: Loki
    type: loki
    access: proxy
    url: http://loki:3100
    editable: false
    jsonData:
      maxLines: 1000
      derivedFields:
        # Cria link automático entre logs e traces a partir do request_id
        - matcherRegex: '"requestId":"([^"]+)"'
          name: RequestID
          url: '/explore?orgId=1&left={"datasource":"Tempo","queries":[{"query":"${__value.raw}"}]}'
          urlDisplayLabel: Ver trace

Provisionando dashboards:

# observabilidade/grafana/provisioning/dashboards/dashboards.yml
apiVersion: 1

providers:
  - name: 'Dashboards do Projeto'
    orgId: 1
    type: file
    disableDeletion: false
    updateIntervalSeconds: 30
    allowUiUpdates: true
    options:
      path: /etc/grafana/provisioning/dashboards
      foldersFromFilesStructure: true

Construindo o Dashboard de Visão Geral

O primeiro dashboard que todo sistema precisa é a visão geral — o painel que responde em segundos se o sistema está saudável. Ele é o destino padrão quando alguém quer saber "está tudo bem?".

O JSON abaixo define um dashboard completo com os quatro painéis essenciais. Salvo em observabilidade/grafana/provisioning/dashboards/visao-geral.json:

{
  "title": "Visão Geral — Minha API",
  "uid": "minha-api-overview",
  "tags": ["minha-api", "overview"],
  "time": { "from": "now-1h", "to": "now" },
  "refresh": "30s",
  "panels": [
    {
      "id": 1,
      "title": "Status do Sistema",
      "type": "stat",
      "gridPos": { "h": 4, "w": 6, "x": 0, "y": 0 },
      "options": {
        "reduceOptions": { "calcs": ["lastNotNull"] },
        "colorMode": "background",
        "graphMode": "none",
        "textMode": "auto"
      },
      "targets": [
        {
          "datasource": "Prometheus",
          "expr": "up{job=\"minha-api\"}",
          "legendFormat": "{{instance}}"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "mappings": [
            { "type": "value", "options": { "0": { "text": "DOWN", "color": "red" } } },
            { "type": "value", "options": { "1": { "text": "UP", "color": "green" } } }
          ]
        }
      }
    },
    {
      "id": 2,
      "title": "Requisições por Segundo",
      "type": "timeseries",
      "gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 },
      "targets": [
        {
          "datasource": "Prometheus",
          "expr": "sum(rate(http_requests_total{job=\"minha-api\"}[5m])) by (status)",
          "legendFormat": "HTTP {{status}}"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "unit": "reqps",
          "custom": { "lineWidth": 2, "fillOpacity": 10 }
        },
        "overrides": [
          {
            "matcher": { "id": "byRegexp", "options": "HTTP 5.*" },
            "properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }]
          },
          {
            "matcher": { "id": "byRegexp", "options": "HTTP 4.*" },
            "properties": [{ "id": "color", "value": { "fixedColor": "orange", "mode": "fixed" } }]
          },
          {
            "matcher": { "id": "byRegexp", "options": "HTTP 2.*" },
            "properties": [{ "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } }]
          }
        ]
      }
    },
    {
      "id": 3,
      "title": "Latência (P50 / P95 / P99)",
      "type": "timeseries",
      "gridPos": { "h": 8, "w": 12, "x": 12, "y": 4 },
      "targets": [
        {
          "datasource": "Prometheus",
          "expr": "histogram_quantile(0.50, sum(rate(http_request_duration_seconds_bucket{job=\"minha-api\"}[5m])) by (le))",
          "legendFormat": "P50"
        },
        {
          "datasource": "Prometheus",
          "expr": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{job=\"minha-api\"}[5m])) by (le))",
          "legendFormat": "P95"
        },
        {
          "datasource": "Prometheus",
          "expr": "histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{job=\"minha-api\"}[5m])) by (le))",
          "legendFormat": "P99"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "unit": "s",
          "custom": { "lineWidth": 2 },
          "thresholds": {
            "mode": "absolute",
            "steps": [
              { "color": "green", "value": null },
              { "color": "yellow", "value": 0.25 },
              { "color": "red", "value": 0.5 }
            ]
          }
        }
      }
    },
    {
      "id": 4,
      "title": "Taxa de Erros (%)",
      "type": "timeseries",
      "gridPos": { "h": 8, "w": 12, "x": 0, "y": 12 },
      "targets": [
        {
          "datasource": "Prometheus",
          "expr": "sum(rate(http_requests_total{job=\"minha-api\",status=~\"5..\"}[5m])) / sum(rate(http_requests_total{job=\"minha-api\"}[5m])) * 100",
          "legendFormat": "Erros 5xx (%)"
        },
        {
          "datasource": "Prometheus",
          "expr": "sum(rate(http_requests_total{job=\"minha-api\",status=~\"4..\"}[5m])) / sum(rate(http_requests_total{job=\"minha-api\"}[5m])) * 100",
          "legendFormat": "Erros 4xx (%)"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "unit": "percent",
          "min": 0,
          "custom": { "lineWidth": 2, "fillOpacity": 15 }
        }
      }
    },
    {
      "id": 5,
      "title": "Uso de Memória do Processo",
      "type": "timeseries",
      "gridPos": { "h": 8, "w": 12, "x": 12, "y": 12 },
      "targets": [
        {
          "datasource": "Prometheus",
          "expr": "process_resident_memory_bytes{job=\"minha-api\"} / 1024 / 1024",
          "legendFormat": "{{instance}} — RSS"
        },
        {
          "datasource": "Prometheus",
          "expr": "nodejs_heap_size_used_bytes{job=\"minha-api\"} / 1024 / 1024",
          "legendFormat": "{{instance}} — Heap Used"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "unit": "decmbytes",
          "custom": { "lineWidth": 2 }
        }
      }
    }
  ]
}

O Dashboard RED: A Metodologia de Monitoramento de Serviços

A metodologia RED — Rate, Errors, Duration — define as três métricas fundamentais para qualquer serviço que processa requisições. Foi popularizada por Tom Wilkie e é complementar aos quatro sinais de ouro do Google SRE.

Rate — quantas requisições por segundo o serviço está processando. É o indicador de tráfego e demanda.

Errors — qual a taxa de requisições que estão falhando. É o indicador mais direto de problemas que afetam usuários.

Duration — quanto tempo as requisições estão levando. É o indicador de performance e experiência do usuário.

Um dashboard RED para a API:

{
  "title": "RED — Minha API",
  "uid": "minha-api-red",
  "panels": [
    {
      "id": 1,
      "title": "R — Requisições por Segundo por Endpoint",
      "type": "timeseries",
      "gridPos": { "h": 8, "w": 24, "x": 0, "y": 0 },
      "targets": [
        {
          "datasource": "Prometheus",
          "expr": "topk(10, sum(rate(http_requests_total{job=\"minha-api\",status=~\"2..\"}[5m])) by (route))",
          "legendFormat": "{{route}}"
        }
      ],
      "fieldConfig": {
        "defaults": { "unit": "reqps" }
      }
    },
    {
      "id": 2,
      "title": "E — Erros por Endpoint (últimos 30 min)",
      "type": "table",
      "gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 },
      "targets": [
        {
          "datasource": "Prometheus",
          "expr": "sort_desc(sum by (route, status) (increase(http_requests_total{job=\"minha-api\",status=~\"[45]..\"}[30m])))",
          "legendFormat": "",
          "instant": true,
          "format": "table"
        }
      ],
      "options": {
        "sortBy": [{ "displayName": "Value", "desc": true }]
      }
    },
    {
      "id": 3,
      "title": "D — Latência P95 por Endpoint (heatmap)",
      "type": "heatmap",
      "gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 },
      "targets": [
        {
          "datasource": "Prometheus",
          "expr": "sum(rate(http_request_duration_seconds_bucket{job=\"minha-api\"}[5m])) by (le)",
          "legendFormat": "{{le}}"
        }
      ],
      "options": {
        "calculate": false,
        "yAxis": { "unit": "s" }
      }
    }
  ]
}

Configurando o Alertmanager

O Prometheus detecta quando uma condição de alerta é satisfeita, mas é o Alertmanager que gerencia o roteamento, agrupamento e envio das notificações. Essa separação é importante: o Alertmanager evita que a mesma notificação seja enviada dezenas de vezes durante um incidente prolongado, e garante que alertas relacionados sejam agrupados em uma única notificação.

# observabilidade/alertmanager/alertmanager.yml
global:
  # Intervalo de resolução — quanto tempo após o alerta ser resolvido
  # antes de enviar a notificação de resolução
  resolve_timeout: 5m

  # Configurações padrão do Slack
  slack_api_url: 'https://hooks.slack.com/services/TOKEN/TOKEN/TOKEN'

# Roteamento de alertas
route:
  # Agrupamento — alertas com as mesmas labels são agrupados
  group_by: ['alertname', 'cluster', 'service']

  # Aguarda este tempo antes de enviar o primeiro alerta do grupo
  group_wait: 30s

  # Aguarda este tempo antes de enviar novos alertas do mesmo grupo
  group_interval: 5m

  # Reenvia o alerta após este intervalo se ainda estiver ativo
  repeat_interval: 4h

  # Receptor padrão
  receiver: 'slack-ops'

  # Rotas específicas por severidade e equipe
  routes:
    # Alertas críticos de produção — alerta imediato + PagerDuty
    - matchers:
        - severity = critical
        - environment = production
      receiver: 'pagerduty-critico'
      group_wait: 0s
      repeat_interval: 1h
      routes:
        # Alertas de banco de dados — rota para equipe de dados
        - matchers:
            - team = dados
          receiver: 'slack-dados'

    # Alertas de warning — apenas Slack
    - matchers:
        - severity = warning
      receiver: 'slack-ops'
      group_wait: 1m
      repeat_interval: 8h

    # Alertas de CI/CD — canal separado
    - matchers:
        - alertname =~ "Pipeline.*"
      receiver: 'slack-cicd'

# Inibição — suprime alertas menos graves quando um grave está ativo
inhibit_rules:
  # Se um alerta critical está ativo, suprime o warning correspondente
  - source_matchers:
      - severity = critical
    target_matchers:
      - severity = warning
    equal: ['alertname', 'instance']

# Receptores
receivers:
  - name: 'slack-ops'
    slack_configs:
      - channel: '#ops-alertas'
        send_resolved: true
        title: '{{ template "slack.title" . }}'
        text: '{{ template "slack.text" . }}'
        color: '{{ if eq .Status "firing" }}{{ if eq (index .Alerts 0).Labels.severity "critical" }}danger{{ else }}warning{{ end }}{{ else }}good{{ end }}'
        actions:
          - type: button
            text: 'Ver no Grafana'
            url: '{{ (index .Alerts 0).Annotations.grafana_url }}'
          - type: button
            text: 'Runbook'
            url: '{{ (index .Alerts 0).Annotations.runbook_url }}'

  - name: 'slack-dados'
    slack_configs:
      - channel: '#time-dados-alertas'
        send_resolved: true
        title: '{{ template "slack.title" . }}'
        text: '{{ template "slack.text" . }}'

  - name: 'slack-cicd'
    slack_configs:
      - channel: '#ci-cd'
        send_resolved: true

  - name: 'pagerduty-critico'
    pagerduty_configs:
      - routing_key: '${{ secrets.PAGERDUTY_KEY }}'
        description: '{{ template "pagerduty.description" . }}'
        severity: '{{ (index .Alerts 0).Labels.severity }}'
        details:
          firing: '{{ .Alerts.Firing | len }}'
          resolved: '{{ .Alerts.Resolved | len }}'
          num_alerting: '{{ .Alerts.Firing | len }}'

# Templates de notificação
templates:
  - '/etc/alertmanager/templates/*.tmpl'

Templates de notificação customizados:

{{/* observabilidade/alertmanager/templates/slack.tmpl */}}

{{ define "slack.title" }}
{{ if eq .Status "firing" }}
  {{ if eq (index .Alerts 0).Labels.severity "critical" }}🚨{{ else }}⚠️{{ end }}
{{ else }}✅{{ end }}
[{{ .Status | toUpper }}] {{ (index .Alerts 0).Labels.alertname }}
{{ end }}

{{ define "slack.text" }}
{{ range .Alerts }}
*Alerta:* {{ .Labels.alertname }}
*Severidade:* {{ .Labels.severity }}
*Ambiente:* {{ .Labels.environment }}
*Instância:* {{ .Labels.instance }}

*Resumo:* {{ .Annotations.summary }}
*Detalhes:* {{ .Annotations.description }}

{{ if .Annotations.runbook_url }}
*Runbook:* {{ .Annotations.runbook_url }}
{{ end }}

*Início:* {{ .StartsAt | since }}
{{ end }}
{{ end }}

Alertas Baseados em SLOs

A abordagem mais madura de alertas não é baseada em thresholds arbitrários — "alerta quando CPU > 80%" — mas em SLOs: Service Level Objectives. Um SLO define um nível de serviço aceitável para o usuário final, e os alertas são disparados quando o sistema está consumindo o orçamento de erros mais rápido do que o aceitável.

Definindo um SLO para a API com 99,9% de disponibilidade:

# observabilidade/prometheus/rules/slos.yml
groups:
  - name: slo-minha-api
    rules:
      # Taxa de sucesso — base do SLO
      - record: job:http_requests_success:rate5m
        expr: |
          sum(rate(http_requests_total{job="minha-api",status!~"5.."}[5m]))
          /
          sum(rate(http_requests_total{job="minha-api"}[5m]))

      # Burn rate — velocidade de consumo do orçamento de erros
      # SLO de 99.9% = 0.1% de orçamento de erros por período
      - record: job:http_requests_error_budget_burn:rate1h
        expr: |
          (1 - job:http_requests_success:rate5m) / (1 - 0.999)

      # Alerta de burn rate alto — 1h e 5min ambos acima do threshold
      # Burn rate > 14.4x significa que o budget mensal será consumido em 2 dias
      - alert: SLOBurnRateCritico
        expr: |
          job:http_requests_error_budget_burn:rate1h > 14.4
          and
          (
            1 - (
              sum(rate(http_requests_total{job="minha-api",status!~"5.."}[5m]))
              /
              sum(rate(http_requests_total{job="minha-api"}[5m]))
            )
          ) / (1 - 0.999) > 14.4
        for: 2m
        labels:
          severity: critical
          slo: disponibilidade-api
        annotations:
          summary: "SLO em risco — burn rate crítico"
          description: |
            O burn rate do orçamento de erros está em {{ $value | humanize }}x
            a taxa aceitável. O orçamento mensal será consumido em menos de 2 dias
            se o ritmo atual continuar.
          runbook_url: "https://wiki.empresa.com/runbooks/slo-burn-rate"

Anotações de Deploy nos Dashboards

Um recurso fundamental para correlacionar deploys com mudanças de comportamento é adicionar anotações ao Grafana no momento de cada deploy. Uma linha vertical aparece no gráfico marcando exatamente quando o deploy aconteceu:

# Script chamado ao final de cada deploy bem-sucedido
# scripts/anotar-deploy.sh

GRAFANA_URL="${GRAFANA_URL:-http://localhost:3001}"
GRAFANA_TOKEN="${GRAFANA_TOKEN}"
VERSAO="${1}"
AMBIENTE="${2:-staging}"

curl -s -X POST "$GRAFANA_URL/api/annotations" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $GRAFANA_TOKEN" \
  -d '{
    "text": "Deploy: '"$VERSAO"'",
    "tags": ["deploy", "'"$AMBIENTE"'"],
    "time": '"$(date +%s%3N)"',
    "timeEnd": '"$(date +%s%3N)"'
  }'

No GitHub Actions, este script é chamado ao final do job de deploy:

- name: Registra anotação de deploy no Grafana
  if: success()
  run: |
    bash scripts/anotar-deploy.sh \
      "${{ github.sha }}" \
      "${{ inputs.ambiente }}"
  env:
    GRAFANA_URL: ${{ secrets.GRAFANA_URL }}
    GRAFANA_TOKEN: ${{ secrets.GRAFANA_API_TOKEN }}

Organizando Dashboards por Audiência

Dashboards diferentes servem a audiências diferentes. Uma estrutura eficiente organiza os dashboards em três níveis:

Nível executivo — um único painel mostrando os SLOs dos sistemas críticos e o status geral. Sem gráficos técnicos. Sem PromQL visível. Apenas "verde" ou "vermelho" para cada sistema.

Nível operacional — os dashboards RED e USE para cada serviço. Destinados ao time de operações e engenheiros de plantão. Devem responder a "onde está o problema?" em menos de 30 segundos.

Nível de diagnóstico — dashboards detalhados por componente. Métricas de banco de dados, uso de conexões, cache hit rate, filas internas. Destinados a quem está investigando a causa raiz de um problema já identificado.

No Grafana, isso é organizado com pastas:

Grafana/
├── 📁 Executivo/
│   └── Status dos SLOs
├── 📁 Operacional/
│   ├── Visão Geral — Minha API
│   ├── RED — Minha API
│   └── Infraestrutura — Servidores
└── 📁 Diagnóstico/
    ├── PostgreSQL — Detalhado
    ├── Redis — Detalhado
    ├── Node.js — Event Loop e GC
    └── Nginx — Conexões e Cache

O Que Vem a Seguir

O próximo artigo apresenta o rastreamento distribuído com OpenTelemetry — o terceiro pilar da observabilidade. Em sistemas com múltiplos serviços, entender o caminho completo de uma requisição — quais serviços foram chamados, quanto tempo cada um levou, onde os erros ocorreram — é impossível apenas com métricas e logs.

Referências para Aprofundamento

Documentação oficial

Metodologias de monitoramento

Dashboards prontos

  • Grafana Dashboards — grafana.com — Biblioteca de dashboards públicos prontos para importar, incluindo dashboards para Node.js, PostgreSQL, Nginx, Redis e dezenas de outras tecnologias.

Exercícios

Exercício 1

O que significa a sigla RED e o que cada letra mede? Por que o dashboard de latência exibe P50, P95 e P99 em vez de simplesmente a média das requisições?

Ver resposta

✓ Resposta: Rate (requisições por segundo — tráfego e demanda), Errors (taxa de requisições falhando — o indicador mais direto de impacto no usuário) e Duration (tempo das requisições — performance percebida). A metodologia foi popularizada por Tom Wilkie e é complementar aos quatro sinais de ouro do Google SRE.

Os percentis substituem a média porque a média esconde exatamente quem está sofrendo. Se 95% das requisições respondem em 20 ms e 5% levam 8 segundos, a média fica em torno de 420 ms — um número que não descreve a experiência de ninguém: nem a maioria rápida, nem a minoria que está de fato quebrada. Basta um punhado de requisições muito lentas para distorcer a média, e basta um volume grande de requisições rápidas para diluí-las.

Com os três percentis a leitura fica completa: o P50 mostra a experiência típica, o P95 e o P99 mostram a cauda — e é na cauda que estão os timeouts, os usuários que desistem e os alertas que importam. Quando P50 está estável e o P99 dispara, o problema atinge um subconjunto específico (uma rota, um cliente grande, um lock de banco), diagnóstico que a média jamais revelaria.

Exercício 2

Por que a detecção do alerta fica no Prometheus e o envio da notificação no Alertmanager? Explique o papel de group_wait, group_interval e repeat_interval, e por que a rota de alertas críticos usa group_wait: 0s.

Ver resposta

✓ Resposta: São responsabilidades distintas. O Prometheus avalia as regras e sabe apenas se a condição é verdadeira agora. O Alertmanager cuida do que fazer com isso: agrupar, rotear, silenciar e não repetir. Sem essa separação, um incidente prolongado geraria uma notificação a cada ciclo de avaliação — dezenas de mensagens idênticas sobre o mesmo problema.

  • group_wait: 30s — ao surgir o primeiro alerta de um grupo, espera um pouco antes de notificar, para que alertas relacionados que aparecerem em seguida entrem na mesma mensagem. Quando um serviço cai, várias instâncias alertam quase juntas.
  • group_interval: 5m — intervalo mínimo antes de notificar de novo o mesmo grupo por causa de alertas novos que se somaram a ele.
  • repeat_interval: 4h — de quanto em quanto tempo relembrar um alerta que continua ativo e sem mudanças.

A rota crítica usa group_wait: 0s porque, com produção fora do ar, meio minuto de espera para agrupar mensagens é tempo perdido — vale notificar imediatamente e agrupar o resto depois. Pela mesma lógica, seu repeat_interval cai para 1h: um problema crítico ainda ativo merece ser lembrado com mais frequência que um warning, cujo intervalo é de 8h.

Exercício 3

Explique o que a inhibit_rule abaixo faz e dê um exemplo concreto de par de alertas que ela evita. Por que a chave equal é indispensável?

inhibit_rules:
  - source_matchers:
      - severity = critical
    target_matchers:
      - severity = warning
    equal: ['alertname', 'instance']
Ver resposta

✓ Resposta: Ela suprime a notificação de alertas warning enquanto houver um critical equivalente ativo. A ideia é que, quando o problema já escalou para crítico, o aviso mais brando sobre a mesma coisa vira ruído — quem está atendendo o incidente não precisa de duas mensagens sobre o mesmo sintoma.

Exemplo concreto: existem LatenciaAlta em warning (P95 acima de 250 ms) e o mesmo LatenciaAlta em critical (acima de 500 ms). Quando a latência de web01 passa de 500 ms, as duas condições ficam verdadeiras ao mesmo tempo. A regra deixa passar apenas a crítica.

O equal: ['alertname', 'instance'] é indispensável porque delimita o que conta como "o mesmo problema": só é suprimido o warning cujo alertname e instance coincidem com os do crítico. Sem essa restrição, qualquer alerta crítico em qualquer lugar silenciaria todos os warnings do sistema — um banco de dados crítico em db01 esconderia um aviso legítimo e sem relação em web02, criando um ponto cego justamente durante um incidente.

Exercício 4

O que é error budget num SLO de 99,9%, e o que significa um burn rate de 14,4×? Por que o alerta exige que a condição seja verdadeira em duas janelas (1h e 5min) e ainda tenha for: 2m?

Ver resposta

✓ Resposta: Um SLO de 99,9% admite 0,1% de falhas — esse é o error budget. Ele não é um valor a ser evitado a todo custo: é um orçamento que pode ser gasto, e é o que autoriza a equipe a fazer deploys e assumir riscos enquanto houver saldo.

O burn rate mede a que velocidade o orçamento está sendo consumido em relação ao ritmo sustentável. Burn rate 1× significa gastar exatamente o previsto para o período. Um burn rate de 14,4× consome o orçamento mensal inteiro em cerca de dois dias — daí o alerta ser critical: não é o sistema estar fora do ar, é o ritmo atual de erros ser insustentável.

A dupla janela existe para equilibrar sensibilidade e ruído. A de 1 hora confirma que o problema é consistente e não um pico isolado; a de 5 minutos garante que ele ainda está acontecendo agora. Sem a janela curta, o alerta continuaria disparando por causa de um incidente já resolvido, cuja marca permanece na média de uma hora. Sem a janela longa, qualquer soluço momentâneo acordaria o plantão.

O for: 2m acrescenta uma terceira camada: a condição precisa se manter verdadeira por dois minutos seguidos antes de o alerta disparar de fato, filtrando oscilações instantâneas. A vantagem de alertar por SLO, no fim, é que a régua deixa de ser um limiar arbitrário — como "CPU acima de 80%", que pode ser perfeitamente normal — e passa a ser o impacto real sobre o usuário.

Exercício 5

Qual problema o provisionamento declarativo do Grafana resolve? Explique o efeito de editable: false nos datasources e por que anotações de deploy nos dashboards são tão úteis durante um incidente.

Ver resposta

✓ Resposta: Ele resolve o mesmo problema dos snowflake servers, aplicado à observabilidade: dashboards e datasources configurados à mão pela interface existem só naquela instância, não são revisáveis e desaparecem se o volume for perdido. Com arquivos YAML e JSON, a observabilidade passa a ser versionada junto com o código — sobe idêntica em qualquer ambiente e cada mudança de dashboard vira um diff em Pull Request.

O editable: false impede que alguém altere o datasource pela interface. Isso evita a divergência silenciosa em que a configuração real deixa de corresponder ao arquivo versionado — e o próximo provisionamento sobrescreveria a alteração de qualquer forma, gerando confusão. A intenção é deixar claro que a fonte de verdade é o repositório.

As anotações de deploy desenham uma linha vertical no gráfico no exato instante de cada deploy. Durante um incidente, a primeira pergunta é quase sempre "o que mudou?", e a anotação responde visualmente: se a latência sobe logo depois da linha, há forte indício de causa, e o rollback vira uma decisão de segundos em vez de uma investigação. Sem elas, seria preciso cruzar manualmente o horário do gráfico com o histórico do pipeline — justamente sob pressão, quando erros de correlação são mais prováveis.

Comentários

Mais em DevOps

RDS, ElastiCache e Estratégias de Dados na AWS
RDS, ElastiCache e Estratégias de Dados na AWS

A camada mais crítica e mais difícil de recuperar quando algo dá errado: RDS…

Ansible: Configurando Servidores de Forma Declarativa
Ansible: Configurando Servidores de Forma Declarativa

Configuração declarativa de servidores por SSH, sem agente instalado: o…

O Que é um Container e Por Que Isso Mudou Tudo
O Que é um Container e Por Que Isso Mudou Tudo

O que é um container e por que ele mudou a entrega de software: o problema…