$ cat prometheus-y-grafana-la-dupla-perfecta-para-monitoreo-y-obse.md
Prometheus y Grafana: La Dupla Perfecta para Monitoreo y Observabilidad
¿Qué es Prometheus?
Características Clave
| Característica | Descripción | |----------------|-------------| | Modelo Pull | Prometheus "extrae" métricas de los targets cada cierto tiempo | | TSDB | Base de datos temporal optimizada para series de tiempo | | PromQL | Lenguaje potente para consultar y agregar métricas | | Multi-dimensional | Métricas con etiquetas (labels) para filtrado avanzado | | AlertManager | Sistema de alertas integrado (Slack, Email, PagerDuty) | | Exporters | Adaptadores para servicios que no exponen métricas nativamente |
¿Qué es Grafana?
Instalación Rápida con Docker Compose
docker-compose.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
restart: unless-stopped
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_INSTALL_PLUGINS=grafana-piechart-panel
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
depends_on:
- prometheus
restart: unless-stopped
# Ejemplo: Aplicación Node.js a monitorear
node-app:
build: ./node-app
ports:
- "3001:3000"
labels:
- "prometheus-job=node-app"
restart: unless-stopped
# Ejemplo: PostgreSQL con exporter
postgres:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
postgres-exporter:
image: prometheuscommunity/postgres-exporter:latest
environment:
DATA_SOURCE_NAME: "postgresql://postgres:secret@postgres:5432/postgres?sslmode=disable"
ports:
- "9187:9187"
depends_on:
- postgres
volumes:
prometheus_data:
grafana_data:
Configuración de Prometheus (prometheus/prometheus.yml)
global:
scrape_interval: 15s # Frecuencia de extracción
evaluation_interval: 15s # Frecuencia de evaluación de alertas
# Configuración de AlertManager
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
# Archivos con reglas de alerta
rule_files:
- "alerts/*.yml"
# Targets a monitorear
scrape_configs:
# Auto-descubrimiento: Prometheus mismo
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Aplicación Node.js personalizada
- job_name: 'node-app'
static_configs:
- targets: ['node-app:3000']
# PostgreSQL Exporter
- job_name: 'postgres'
static_configs:
- targets: ['postgres-exporter:9187']
# Node Exporter (métricas del sistema)
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
# cAdvisor (métricas de contenedores Docker)
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
Reglas de Alerta (prometheus/alerts/app_alerts.yml)
groups:
- name: app_alerts
rules:
- alert: HighCPUUsage
expr: rate(process_cpu_seconds_total[5m]) > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "Alto uso de CPU en {{ $labels.instance }}"
description: "CPU está al {{ $value }}% por más de 5 minutos"
- alert: ServiceDown
expr: up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Servicio {{ $labels.job }} está caído"
description: "{{ $labels.instance }} no responde desde hace 1 minuto"
- alert: HighMemoryUsage
expr: (node_memory_MemTotal_bytes - node_memory_MemFree_bytes - node_memory_Buffers_bytes - node_memory_Cached_bytes) / node_memory_MemTotal_bytes > 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "Memoria alta en {{ $labels.instance }}"
description: "Uso de memoria al {{ $value | humanizePercentage }}"
Instrumentando una Aplicación en Go
Ejemplo: Servidor HTTP con métricas Prometheus
// main.go
package main
import (
"fmt"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Definir métricas
var (
// Contador: número de peticiones
httpRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "endpoint", "status"},
)
// Histograma: duración de peticiones
httpRequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Duration of HTTP requests",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "endpoint"},
)
// Gauge: usuarios activos (puede subir y bajar)
activeUsers = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "active_users",
Help: "Current active users",
},
)
// Summary: tamaño de respuestas
responseSize = promauto.NewSummaryVec(
prometheus.SummaryOpts{
Name: "http_response_size_bytes",
Help: "Size of HTTP responses",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
},
[]string{"method", "endpoint"},
)
)
func main() {
// Middleware para registrar métricas
http.HandleFunc("/", metricsMiddleware(handleHome))
http.HandleFunc("/api/users", metricsMiddleware(handleUsers))
// Endpoint de métricas para Prometheus
http.Handle("/metrics", promhttp.Handler())
// Simular cambio de usuarios activos
go simulateActiveUsers()
fmt.Println("Server running on :8080")
http.ListenAndServe(":8080", nil)
}
func metricsMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Crear response writer personalizado para capturar status
rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
next(rw, r)
duration := time.Since(start).Seconds()
endpoint := r.URL.Path
method := r.Method
httpRequestsTotal.WithLabelValues(method, endpoint, fmt.Sprintf("%d", rw.statusCode)).Inc()
httpRequestDuration.WithLabelValues(method, endpoint).Observe(duration)
responseSize.WithLabelValues(method, endpoint).Observe(float64(rw.size))
}
}
func handleHome(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World"))
}
func handleUsers(w http.ResponseWriter, r *http.Request) {
activeUsers.Set(42) // Actualizar gauge
w.Write([]byte(`{"users": ["alice", "bob"]}`))
}
type responseWriter struct {
http.ResponseWriter
statusCode int
size int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
func (rw *responseWriter) Write(b []byte) (int, error) {
size, err := rw.ResponseWriter.Write(b)
rw.size += size
return size, err
}
func simulateActiveUsers() {
for {
time.Sleep(10 * time.Second)
// Simular fluctuación de usuarios
activeUsers.Set(float64(40 + time.Now().Second()%30))
}
}
Dockerfile para la app Go
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o main .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]
Dashboards en Grafana
Configuración Automática (provisioning)
# grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: true
Dashboard JSON básico
{
"title": "Mi App - Dashboard Principal",
"panels": [
{
"title": "Peticiones por Segundo",
"type": "graph",
"targets": [
{
"expr": "rate(http_requests_total[1m])",
"legendFormat": "{{method}} {{endpoint}}"
}
],
"gridPos": {"x": 0, "y": 0, "w": 12, "h": 8}
},
{
"title": "Latencia P99",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))",
"legendFormat": "{{method}} {{endpoint}}"
}
],
"gridPos": {"x": 12, "y": 0, "w": 12, "h": 8}
},
{
"title": "Usuarios Activos",
"type": "stat",
"targets": [
{
"expr": "active_users"
}
],
"gridPos": {"x": 0, "y": 8, "w": 6, "h": 4}
}
]
}
Consultas PromQL Esenciales
| Qué quieres medir | Consulta PromQL |
|------------------|-----------------|
| Tasa de peticiones | rate(http_requests_total[5m]) |
| Latencia promedio | rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m]) |
| Percentil 99 | histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) |
| Tasa de error | rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) |
| Uso de CPU | rate(process_cpu_seconds_total[1m]) |
| Memoria usada | process_resident_memory_bytes |
| Servicios caídos | up == 0 |
| Predicción de espacio | predict_linear(node_filesystem_free_bytes[1h], 4 * 3600) < 0 |
Exporters Populares
Configuración de Node Exporter
# docker-compose.yml (agregar)
services:
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--path.rootfs=/rootfs'
restart: unless-stopped
Alertas Avanzadas con AlertManager
Configuración (alertmanager/config.yml)
global:
smtp_smarthost: 'smtp.gmail.com:587'
smtp_from: 'alerts@tuapp.com'
smtp_auth_username: 'alerts@tuapp.com'
smtp_auth_password: 'tu-password'
route:
group_by: ['alertname', 'severity']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
routes:
- match:
severity: critical
receiver: pagerduty
continue: true
- match:
severity: warning
receiver: slack
receivers:
- name: 'slack'
slack_configs:
- api_url: 'https://hooks.slack.com/services/XXX'
channel: '#alertas'
title: '⚠️ Alerta: {{ .GroupLabels.alertname }}'
text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
- name: 'pagerduty'
pagerduty_configs:
- service_key: 'tu-service-key'
- name: 'email'
email_configs:
- to: 'equipo@tuapp.com'
Ejemplo: Alerta de SRE (Service Level Objective)
# prometheus/alerts/slo_alerts.yml
groups:
- name: slo_alerts
rules:
- alert: HighErrorRate
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
) > 0.01
for: 5m
labels:
severity: critical
slo: "99.9%"
annotations:
summary: "Tasa de error superior al 1%"
description: "Error rate actual: {{ $value | humanizePercentage }}"
runbook: "https://wiki.tuapp.com/runbooks/high-error-rate"
- alert: LatencyBreach
expr: |
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 0.5
for: 10m
labels:
severity: warning
annotations:
summary: "Latencia P99 superior a 500ms"
description: "Latencia actual: {{ $value }}s"
Casos de Uso Reales
1. Monitoreo de Kubernetes (kube-prometheus-stack)
# Instalación con Helm
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# Instalar stack completo (Prometheus + Grafana + AlertManager + Exporters)
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace
Métricas que obtienes automáticamente:
- Uso de CPU/Memoria por pod
- Estado de los deployments
- Latencia de API Server
- Eventos del scheduler
2. Monitoreo de APIs con Blackbox Exporter
# prometheus/prometheus.yml (agregar)
scrape_configs:
- job_name: 'blackbox-http'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- https://tuapi.com/health
- https://tuapi.com/v1/users
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:9115
3. Monitoreo de Batch Jobs (Pushgateway)
// Para jobs que no pueden esperar el pull de Prometheus
import "github.com/prometheus/client_golang/prometheus/push"
func main() {
// Ejecutar job
result := runBatchJob()
// Pushear métrica al Pushgateway
pusher := push.New("http://pushgateway:9091", "batch_job")
.Collector(jobDuration)
.Grouping("job_name", "daily_cleanup")
if err := pusher.Push(); err != nil {
log.Fatal("Error pushing metrics:", err)
}
}
Mejores Prácticas
1. Nombrado de Métricas
// ✅ Bueno - Estructura clara
http_requests_total
http_request_duration_seconds
db_query_duration_seconds
// ❌ Malo - Confuso
counter1
time_taken
val
2. Uso de Labels
// ✅ Bueno - Labels útiles
http_requests_total{method="GET", endpoint="/users", status="200"}
// ❌ Malo - Demasiados valores únicos
http_requests_total{user_id="12345"} // Cada usuario crea una serie nueva
3. Tipos de Métricas Correctos
| Tipo | Cuándo usar | |------|-------------| | Counter | Número de peticiones, errores, bytes transferidos (solo aumenta) | | Gauge | Temperatura, memoria usada, conexiones activas (sube y baja) | | Histogram | Latencia, tamaños (percentiles útiles) | | Summary | Similar a histogram pero con percentiles configurables |
4. Retención de Datos
# Configurar retención en Prometheus
command:
- '--storage.tsdb.retention.time=30d' # 30 días
- '--storage.tsdb.retention.size=50GB' # Máximo 50GB
¿Cuándo USAR Prometheus + Grafana?
✅ Casos Ideales
| Escenario | ¿Por qué? | |-----------|-----------| | Microservicios | Descubrimiento automático de servicios | | Kubernetes | Integración nativa (kube-state-metrics) | | Equipos de SRE/DevOps | Alertas potentes y SLI/SLO tracking | | Arquitecturas dinámicas | Auto-escalado, cambios frecuentes de instancias | | Necesidad de alertas complejas | PromQL permite lógica avanzada |
❌ Alternativas para otros casos
| Escenario | Alternativa | |-----------|-------------| | Monitoreo simple de 2 servidores | Netdata o Uptime Kuma | | Logs (no métricas) | ELK Stack (Elasticsearch, Logstash, Kibana) | | Traces distribuidos | Jaeger o Tempo | | Métricas empresariales (BI) | Tableau o PowerBI | | Infraestructura legacy | Zabbix o Nagios |
Conclusión
Prometheus y Grafana son la combinación estándar de la industria para observabilidad. Prometheus brilla en:
- Recolección eficiente de métricas (pull model)
- Consultas potentes con PromQL
- Alertas confiables sin SPOF
Grafana aporta:
- Visualización hermosa e interactiva
- Múltiples fuentes (Prometheus, Loki, Tempo, CloudWatch)
- Dashboards compartibles y alertas visuales
El consejo final: Empieza pequeño. Monitorea 3-4 métricas clave (peticiones, latencia, errores, CPU). Añade más conforme entiendas qué necesitas. No intentes monitorearlo todo desde el día 1.
Recursos Adicionales
- Documentación oficial de Prometheus
- Grafana Labs
- Awesome Prometheus
- Prometheus Roast (ejemplos prácticos)
- Grafana Dashboards compartidos
¿Ya usas Prometheus y Grafana? ¿Qué métricas monitoreas primero en una aplicación nueva? ¡Déjamelo en los comentarios! 👇