Pular para o conteúdo
CloudIntermediário

Monitoramento com Prometheus e Grafana: do zero aos alertas

Instrumente uma API com Prometheus, escreva PromQL para latência, erros e tráfego, monte alertas sem ruído e evite explodir a cardinalidade das métricas.

Por Equipe IAUAI Estudos · 3 de agosto de 2026 · 10 min de leitura

Nesta página

Ao terminar este artigo você vai ter uma aplicação Flask expondo métricas, um Prometheus coletando e um Grafana pronto para desenhar painéis. Também vai saber escrever as consultas PromQL dos quatro sinais dourados, criar alertas que acordam alguém só quando vale a pena e evitar o erro de cardinalidade que derruba o Prometheus.

O problema real

Dez serviços rodando e a queixa chega pelo chat: "está lento". Sem métricas, ninguém sabe qual serviço, desde quando, nem se é erro ou lentidão. A investigação vira adivinhação. Com métricas, um gráfico mostra que as chamadas a uma API externa estão falhando em 5% e o problema se resolve rápido.

O Prometheus coleta números em intervalos regulares (modelo pull: ele busca as métricas nos seus serviços). O Grafana desenha os gráficos e o Alertmanager envia os alertas.

Os quatro tipos de métrica

Counter: só cresce

Conta eventos: requisições, erros, mensagens processadas. Zera quando o processo reinicia, e o rate() sabe lidar com isso.

Gauge: sobe e desce

Valor instantâneo: memória, conexões abertas, tamanho de fila.

Histogram: distribuição em faixas

Guarda quantas observações caíram em cada faixa (bucket), mais a soma e a contagem. É o tipo certo para latência, porque permite calcular percentis no servidor Prometheus e agregar entre várias instâncias.

Summary: quantis calculados no cliente

No cliente Python, o Summary só expõe contagem e soma, sem quantis. Em outros clientes ele pode expor quantis, mas esses valores não podem ser agregados entre instâncias. Por isso, para latência, use Histogram na maioria dos casos.

Instrumentando uma aplicação

Estrutura do projeto: app.py, prometheus.yml, docker-compose.yml, Dockerfile e requirements.txt (com flask e prometheus-client).

# app.py
import time
from flask import Flask, Response, request
from prometheus_client import (
    CONTENT_TYPE_LATEST, Counter, Gauge, Histogram, generate_latest
)

app = Flask(__name__)

REQUISICOES = Counter(
    "http_requests_total", "Total de requisições HTTP",
    ["method", "route", "status"],
)
DURACAO = Histogram(
    "http_request_duration_seconds", "Latência das requisições",
    ["route"],
    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5),
)
EM_ANDAMENTO = Gauge("http_requests_in_progress", "Requisições em andamento")

@app.before_request
def antes():
    request.inicio = time.perf_counter()
    EM_ANDAMENTO.inc()

@app.after_request
def depois(resposta):
    # url_rule é o padrão da rota (/users/<id>), não o caminho real.
    rota = request.url_rule.rule if request.url_rule else "desconhecida"
    DURACAO.labels(rota).observe(time.perf_counter() - request.inicio)
    REQUISICOES.labels(request.method, rota, resposta.status_code).inc()
    EM_ANDAMENTO.dec()
    return resposta

@app.route("/metrics")
def metrics():
    return Response(generate_latest(), mimetype=CONTENT_TYPE_LATEST)

@app.route("/users")
def users():
    time.sleep(0.05)
    return {"users": ["alice", "bob"]}

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

Note que o rótulo usa o padrão da rota e não request.path. Se usasse o caminho real, cada /users/123 viraria uma série nova, que é o problema de cardinalidade tratado adiante.

Com docker-compose.yml:

services:
  app:
    build: .
    ports: ["5000:5000"]

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

  grafana:
    image: grafana/grafana
    ports: ["3000:3000"]
    volumes:
      - grafana_data:/var/lib/grafana

volumes:
  prometheus_data:
  grafana_data:

E prometheus.yml. Dentro do Compose, o alvo é o nome do serviço, não localhost:

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: app
    static_configs:
      - targets: ["app:5000"]

Suba com docker compose up -d --build, abra http://localhost:9090/targets e confira se o alvo está UP. O Grafana fica em http://localhost:3000, com login inicial admin e senha admin (ele pede para trocar no primeiro acesso). Adicione o Prometheus como fonte de dados usando a URL http://prometheus:9090. Imagens sem tag usam a versão mais recente, o que é prático para estudar, mas em produção fixe uma versão.

PromQL na prática

Os exemplos usam as métricas definidas acima.

Requisições por segundo, na média dos últimos 5 minutos:

sum(rate(http_requests_total[5m]))

Proporção de erros 5xx:

sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))

Latência p95 por rota. Agregue os buckets com sum ... by (le) antes do histogram_quantile:

histogram_quantile(
  0.95,
  sum by (le, route) (rate(http_request_duration_seconds_bucket[5m]))
)

A precisão do percentil depende dos buckets que você definiu. Se quase toda a latência cai num bucket só, o resultado é uma estimativa grosseira, então ajuste as faixas em volta do seu objetivo de latência.

Por que não confiar na média: com latências de 10 ms, 10 ms, 10 ms, 10 ms e 10 segundos, a média é cerca de 2 segundos e não descreve ninguém. Percentis mostram a cauda, que é onde o usuário sofre.

Os quatro sinais dourados

O livro de SRE do Google propõe acompanhar quatro sinais por serviço.

  1. Latência: o p95 ou p99 da consulta acima, separando requisições com sucesso das com erro.
  2. Tráfego: sum(rate(http_requests_total[5m])).
  3. Erros: a proporção de 5xx acima.
  4. Saturação: o quanto o recurso mais apertado está cheio. Para máquinas, o node_exporter fornece node_cpu_seconds_total e node_memory_MemAvailable_bytes. Para contêineres, o cAdvisor ou o kubelet.

Não existem limiares universais. Defina cada um a partir do que o seu negócio tolera.

Alertas que não viram ruído

Alerte sobre sintomas que o usuário sente, como erros e lentidão, e não sobre causas possíveis, como memória alta. Um alerta de causa dispara com frequência sem que nada esteja errado e as pessoas aprendem a ignorá-lo.

groups:
  - name: api
    rules:
      - alert: TaxaDeErrosAlta
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m]))
          / sum(rate(http_requests_total[5m])) > 0.01
        for: 5m
        labels:
          severity: page
        annotations:
          summary: "Mais de 1% de erros 5xx nos últimos 5 minutos"
          runbook_url: "https://wiki.exemplo.com/runbooks/taxa-de-erros"

      - alert: LatenciaP95Alta
        expr: |
          histogram_quantile(0.95,
            sum by (le) (rate(http_request_duration_seconds_bucket[5m]))) > 1
        for: 10m
        labels:
          severity: ticket
        annotations:
          summary: "p95 acima de 1s por 10 minutos"

A cláusula for evita alertas por picos curtos. O runbook_url diz a quem está de plantão o que fazer, e sem isso o alerta só gera ansiedade. Carregue esse arquivo em rule_files no prometheus.yml e configure o Alertmanager para entregar por e-mail, Slack ou outro canal.

SLI, SLO e SLA

O SLI é a medida, o SLO é a meta interna e o SLA é o contrato com consequência financeira.

Um SLI de disponibilidade simples:

sum(rate(http_requests_total{status!~"5.."}[30d]))
/
sum(rate(http_requests_total[30d]))

Um SLO plausível seria "99,5% das requisições sem erro 5xx em 30 dias". A diferença para 100% é o orçamento de erro: nesse exemplo, 0,5% das requisições podem falhar sem violar a meta. Quando o orçamento acaba, a equipe prioriza confiabilidade em vez de funcionalidades novas. Valores de SLA e multas dependem do contrato de cada empresa.

Consultas de 30 dias são pesadas. O costume é criar uma recording rule que calcule a taxa em janelas curtas e agregue a partir dela.

Cardinalidade

Cada combinação única de valores de rótulos cria uma série temporal, e cada série ocupa memória. Rótulos com muitos valores possíveis, como user_id, e-mail ou ID de requisição, geram milhões de séries e derrubam o servidor.

# Evite: um valor novo por usuário
Counter("requests_by_user_total", "…", ["user_id"])

Se precisa saber o que um usuário fez, isso é trabalho para logs ou traces, não para métricas. Em métricas, mantenha rótulos com poucos valores conhecidos, como método, rota (o padrão, não o caminho) e classe de status. Para saber quantas séries você tem, consulte:

count({__name__=~".+"})

Em instâncias grandes essa consulta é cara, e a página /tsdb-status do Prometheus mostra as métricas com mais séries de forma mais barata. Você também pode limitar com sample_limit na configuração de scrape.

Armadilhas comuns

  • Scrape a cada 1 segundo sem necessidade. Os 15s costumam bastar, e intervalos curtos aumentam muito o armazenamento.
  • Esquecer a retenção. O padrão é 15 dias, ajuste com --storage.tsdb.retention.time e dimensione o disco.
  • Painéis sem contexto. Um gráfico de erros sem a escala de tráfego ao lado engana.
  • Alerta sem dono nem runbook.

Quando o Prometheus não é a ferramenta

Para logs use Loki ou Elasticsearch. Para traces distribuídos use Tempo ou Jaeger, de preferência com OpenTelemetry. Para eventos de alta resolução e armazenamento de longo prazo, olhe soluções como Mimir, Thanos ou VictoriaMetrics por cima do Prometheus. E para métricas de produtos com LLM, veja o artigo de observabilidade de LLM.

Próximos passos

Suba o Compose acima, gere carga com hey -z 30s http://localhost:5000/users (ou ab -n 1000 -c 10 ...) e crie no Grafana quatro painéis, um para cada sinal dourado. Para expor métricas do banco, combine com o postgres_exporter e veja o artigo de Postgres para desenvolvedores.

Quer aplicar isso na sua empresa? Marque uma conversa de 45 minutos em https://iauaicloud.com.br/consultoria

Aprenda jogando

Datacenter

Percorra o corredor de racks e resolva falhas antes que o uptime despenque.

Jogar Datacenter

Teste seu conhecimento

Teste o que você aprendeu sobre Prometheus e Grafana

Pergunta 1 de 6

Qual tipo de métrica é o indicado para latência na maioria dos casos?

Um conteúdo prático por quinzena

Deixe o seu e-mail para receber um conteúdo prático de IA e cloud a cada quinzena. Sem spam, e você pede a exclusão quando quiser.

Continue lendo