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 * 1000Alertas 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:
- RPS (requests per second):
sum(rate(http_requests_total[1m])) - Error Rate: query de error rate acima, com threshold vermelho em 5%
- Latência p50/p95/p99: 3 linhas no mesmo painel para comparar
- 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_bytesUm 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.