Saltar para o conteúdo
Monitoramento com Prometheus e Grafana: Do Zero ao Dashboard
DevOps

Monitoramento com Prometheus e Grafana: Do Zero ao Dashboard

15 de agosto de 2024·Paulo de Paula

Observabilidade não é sobre ter dados — é sobre responder perguntas. “Minha API está lenta?” exige latência por percentil. “Está caindo?” exige error rate. “Vai estourar memória?” exige tendência de uso. Prometheus coleta esses dados; Grafana os transforma em respostas visuais.

Tipos de métricas e quando usar cada uma

Counter    — só sobe (requisições totais, erros totais, bytes enviados)
Gauge      — sobe e desce (conexões ativas, uso de memória, fila atual)
Histogram  — distribui valores em buckets (latência, tamanho de payload)
Summary    — percentis pré-calculados (mais leve no query, menos flexível)

Para latência, sempre prefira Histogram — permite calcular percentis arbitrários no Grafana com histogram_quantile().

Instrumentando Node.js com prom-client

// metrics.js
import { Registry, Counter, Histogram, Gauge, collectDefaultMetrics } from 'prom-client';

export const register = new Registry();

// Coleta métricas padrão: heap, GC, event loop lag, etc.
collectDefaultMetrics({ register });

// Contador de requisições HTTP
export const httpRequestsTotal = new Counter({
  name: 'http_requests_total',
  help: 'Total de requisições HTTP recebidas',
  labelNames: ['method', 'route', 'status_code'],
  registers: [register],
});

// Histogram de latência
export const httpRequestDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duração das requisições HTTP em segundos',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
  registers: [register],
});

// Gauge de conexões ativas
export const activeConnections = new Gauge({
  name: 'active_connections',
  help: 'Número de conexões ativas',
  registers: [register],
});

Middleware Express para coletar automaticamente:

// middleware/metrics.js
import { httpRequestsTotal, httpRequestDuration, activeConnections } from '../metrics.js';

export function metricsMiddleware(req, res, next) {
  activeConnections.inc();
  const end = httpRequestDuration.startTimer();

  res.on('finish', () => {
    const labels = {
      method: req.method,
      route: req.route?.path ?? 'unknown',
      status_code: res.statusCode,
    };
    httpRequestsTotal.inc(labels);
    end(labels);
    activeConnections.dec();
  });

  next();
}

// Rota de scraping
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType);
  res.end(await register.metrics());
});

Configurando o Prometheus

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
  - static_configs:
    - targets: ['alertmanager:9093']

rule_files:
  - 'alerts/*.yml'

scrape_configs:
  - job_name: 'nodejs-api'
    static_configs:
    - targets: ['api:3000']
    metrics_path: '/metrics'

  - job_name: 'postgresql'
    static_configs:
    - targets: ['postgres-exporter:9187']

  - job_name: 'redis'
    static_configs:
    - targets: ['redis-exporter:9121']

PromQL: as queries que você mais vai usar

# Taxa de requisições por segundo (últimos 5 min)
rate(http_requests_total[5m])

# Error rate (%)
rate(http_requests_total{status_code=~"5.."}[5m])
/
rate(http_requests_total[5m])
* 100

# Latência p99 por rota
histogram_quantile(0.99,
  sum by (le, route) (
    rate(http_request_duration_seconds_bucket[5m])
  )
)

# Latência p50, p95, p99 em uma só query
histogram_quantile(0.50, rate(http_request_duration_seconds_bucket[5m]))
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

# Uso de memória heap Node.js (MB)
nodejs_heap_size_used_bytes / 1024 / 1024

# Event loop lag (ms) — indicador de CPU-bound
nodejs_eventloop_lag_p99_seconds * 1000

Alertas com Alertmanager

# alerts/api.yml
groups:
- name: api-alerts
  rules:
  - alert: HighErrorRate
    expr: |
      rate(http_requests_total{status_code=~"5.."}[5m])
      /
      rate(http_requests_total[5m])
      > 0.05
    for: 2m
    labels:
      severity: critical
    annotations:
      summary: "Taxa de erros alta: {{ $value | humanizePercentage }}"
      description: "A rota {{ $labels.route }} está com {{ $value | humanizePercentage }} de erros."

  - alert: HighLatency
    expr: |
      histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 1
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "P99 acima de 1 segundo"
# alertmanager.yml
route:
  receiver: slack-critical
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 12h
  routes:
  - match:
      severity: critical
    receiver: slack-critical
  - match:
      severity: warning
    receiver: slack-warning

receivers:
- name: slack-critical
  slack_configs:
  - api_url: 'https://hooks.slack.com/services/...'
    channel: '#alertas-criticos'
    text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'

Dashboard Grafana: o essencial

Configure 4 painéis para ter visibilidade completa:

  1. RPS (requests per second): sum(rate(http_requests_total[1m]))
  2. Error Rate: query de error rate acima, com threshold vermelho em 5%
  3. Latência p50/p95/p99: 3 linhas no mesmo painel para comparar
  4. Recursos do processo: heap Node.js + event loop lag + CPU

O método USE para infraestrutura

Para cada recurso (CPU, memória, disco, rede) monitore:

  • Utilization: quanto está sendo usado (%)
  • Saturation: está enfileirando requisições? (queue depth)
  • Errors: contagem de erros do recurso
# CPU Utilization
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# Memory Saturation (swap usage indica pressure)
node_memory_SwapTotal_bytes - node_memory_SwapFree_bytes

Um dashboard de produção eficiente não tem 50 gráficos — tem 8-10 que respondem as perguntas mais frequentes em menos de 30 segundos.