Saltar al contenido principal

Monitoreo con Prometheus, Loki y Grafana

El stack de monitoreo es opcional y se despliega por separado del stack principal mediante docker-compose.monitoring.yml. Una vez activo, expone métricas del backend en tiempo real a través de Grafana.

Arquitectura del stack de monitoreo

Servidor Linux (host)
│ /proc, /sys, /rootfs ← node-exporter lee métricas del host

├── Docker daemon
│ │ socket + /var/lib/docker ← cAdvisor lee métricas por contenedor
│ │
│ └── sgi-full-backend:8080
│ │ /actuator/prometheus ← métricas JVM / HTTP / HikariCP
│ ▼
│ Prometheus ← raspa los 3 targets cada 15 s; retención 15 días
│ │
│ ▼
│ Grafana ← dashboards de infraestructura y aplicación
│ │
│ ├── datasource: Prometheus
│ └── datasource: Loki ← logs de contenedores
│ ▲
│ Promtail ← recolecta logs de Docker

Componentes

ContenedorImagenPuerto internoRol
sgi-full-prometheusprom/prometheus:latest9090Scraping y almacenamiento de métricas
sgi-full-node-exporterprom/node-exporter:latest9100Métricas del host: CPU, RAM, disco, red
sgi-full-cadvisorgcr.io/cadvisor/cadvisor:latest8080Métricas por contenedor Docker
sgi-full-lokigrafana/loki:2.9.103100Almacenamiento de logs
sgi-full-promtailgrafana/promtail:3.0.0Recolección de logs desde Docker
sgi-full-grafanagrafana/grafana:11.5.23000Dashboard de visualización

Qué mide cada componente

FuenteEjemplos de métricas
node-exporter (host)node_cpu_seconds_total, node_memory_MemAvailable_bytes, node_filesystem_avail_bytes, node_network_receive_bytes_total
cAdvisor (contenedores)container_cpu_usage_seconds_total, container_memory_usage_bytes, container_fs_reads_bytes_total
backend (JVM / app)jvm_memory_used_bytes, http_server_requests_seconds, hikaricp_connections_active, cache_gets_total
Versiones fijadas
  • Loki 2.9.10: Loki 3.x rompe la compatibilidad con boltdb-shipper + schema v11.
  • Promtail 3.0.0: Promtail 2.9.x usa Docker client API 1.42 (hardcodeado en el SDK de Go); los servidores modernos requieren mínimo API 1.44. Solo Promtail 3.x es compatible.
  • Grafana 11.5.2: Grafana 12 tiene un bug en el provisioning de datasources con UIDs fijos que causa crash loop al iniciar.

Redes

El stack de monitoreo usa dos redes externas que deben existir antes de desplegarlo:

RedPropósito
sgi_internalPermite a Prometheus alcanzar el backend para raspar /actuator/prometheus
proxy_networkExpone Grafana a través de Nginx Proxy Manager con HTTPS

Ambas redes se crean automáticamente al levantar el stack principal con docker compose up.

Despliegue

Prerrequisitos

  1. El stack principal debe estar corriendo (docker compose up -d).
  2. Las redes sgi_internal y proxy_network deben existir.
  3. La imagen del backend debe incluir la dependencia micrometer-registry-prometheus y tener /actuator/prometheus en rutas permitidas.

Levantar el stack de monitoreo

# Desde la raíz del repositorio
docker compose -f docker-compose.monitoring.yml up -d

Esto crea y arranca Prometheus, Loki, Promtail y Grafana.

Verificar que todos los servicios están corriendo

docker compose -f docker-compose.monitoring.yml ps

Todos deben aparecer como running o healthy.

Verificar que Prometheus puede raspar el backend

# Comprueba el endpoint de métricas del backend directamente
docker exec sgi-full-prometheus wget -qO- http://sgi-full-backend:8080/actuator/prometheus | head -20

Si devuelve líneas con métricas (# HELP, # TYPE), el scraping está funcionando.

También puedes abrir la UI de Prometheus en el servidor (si tienes acceso por túnel o red interna):

http://<servidor>:9090/targets

El target sgi-backend debe aparecer como UP.

Actualizar el stack de monitoreo

git pull
docker compose -f docker-compose.monitoring.yml up -d

Detener el stack de monitoreo

# Detiene sin eliminar datos
docker compose -f docker-compose.monitoring.yml down

# Detiene Y elimina todos los datos históricos (métricas, logs)
docker compose -f docker-compose.monitoring.yml down -v

Acceder a Grafana

Grafana está disponible en:

https://sgi-grafana.escuelamilitar.edu.pe

Las credenciales por defecto son:

CampoValor por defecto
Usuarioadmin
Contraseñaadmin
Cambia la contraseña por defecto

En el primer acceso, Grafana pedirá cambiar la contraseña. Usa una contraseña segura y guárdala en un gestor de contraseñas. Si necesitas restablecerla, usa las variables de entorno GRAFANA_USER y GRAFANA_PASSWORD en el archivo .env.

Dashboards precargados

Al arrancar, Grafana carga automáticamente tres dashboards desde monitoring/grafana/provisioning/dashboards/:

ArchivoUIDTítuloFuente de datos
sgi-backend.jsonsgi-emch-backend-v1SGI-EMCH BackendPrometheus + Loki
node-exporter.jsonsgi-node-exporterSGI-EMCH — Servidor (node-exporter)Prometheus
cadvisor.jsonsgi-cadvisorSGI-EMCH — Contenedores (cAdvisor)Prometheus

Dashboard: SGI-EMCH — Servidor (node-exporter)

Métricas del host Linux. Fila superior con 6 stats (CPU %, RAM usada, RAM disponible, disco /, uptime, load avg 1m); luego gráficas de series temporales para CPU, RAM, red I/O, disco I/O, load average y un bargauge con uso por partición.

Dashboard: SGI-EMCH — Contenedores (cAdvisor)

Métricas por contenedor Docker. Variables de template $host y $container para filtrar. Paneles de CPU usage, memory RSS, memory cache, tráfico de red entrada/salida, y tabla de info de contenedores con uptime en días.

Dashboard: SGI-EMCH Backend

Al arrancar, Grafana carga automáticamente el dashboard "SGI-EMCH Backend" (UID sgi-emch-backend-v1) con los siguientes paneles:

Sección: JVM & Sistema

PanelMétrica
Heap usado (%)jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} * 100
Threads activosjvm_threads_live_threads
GC pausas/minrate(jvm_gc_pause_seconds_count[1m]) * 60
GC tiempo total/minrate(jvm_gc_pause_seconds_sum[1m]) * 60

Sección: HTTP — Tráfico y Errores

PanelMétrica
Requests por minutosum(rate(http_server_requests_seconds_count[1m])) * 60
Tasa de error (%)(errores) / (total) * 100

Sección: Latencia HTTP

PanelMétrica
P50 latenciahistogram_quantile(0.50, ...)
P95 latenciahistogram_quantile(0.95, ...)
P99 latenciahistogram_quantile(0.99, ...)

Sección: Base de Datos (HikariCP)

PanelMétrica
Conexiones activashikaricp_connections_active
Conexiones pendienteshikaricp_connections_pending
Tiempo de adquisición P99histogram_quantile(0.99, hikaricp_connection_acquired_nanos_bucket) / 1e6

Sección: Caché Caffeine

PanelMétrica
Hit rate por caché (%)rate(cache_gets_total{result="hit"}[5m]) / (hits + misses) * 100
Misses por minutorate(cache_gets_total{result="miss"}[1m]) * 60

Sección: Logs en vivo

Panel de logs de Loki con filtro {container="sgi-full-backend"} — muestra los últimos registros del backend en tiempo real.

Configuración requerida en el backend

Los paneles de Latencia HTTP y Caffeine Hit Rate requieren que el backend tenga habilitadas estas propiedades en application.properties:

# Habilita _bucket series para histogram_quantile (P50/P95/P99)
management.metrics.distribution.percentiles-histogram.http.server.requests=true

# Habilita estadísticas en Caffeine para que Micrometer pueda leerlas
spring.cache.caffeine.spec=maximumSize=1000,expireAfterWrite=3600s,recordStats

# Pre-registra los caches al arrancar (sin esto, Micrometer no los ve hasta el primer uso)
spring.cache.cache-names=catalogos-areas,catalogos-areas-todas,catalogos-tipos-equipo,catalogos-marcas,catalogos-modelos,catalogos-so,catalogos-tipos-incidente

Sin estas propiedades, los paneles de latencia y caché muestran "No data" aunque el backend esté funcionando.

Provisionamiento automático

Los datasources y el dashboard se configuran automáticamente al arrancar Grafana mediante archivos YAML en monitoring/grafana/provisioning/:

monitoring/
├── prometheus/
│ └── prometheus.yml # Configuración de scraping (job sgi-backend)
├── loki/
│ └── loki-config.yaml # Configuración de almacenamiento de logs
├── promtail/
│ └── promtail-config.yaml # Recolección de logs de contenedores sgi-full-*
└── grafana/
└── provisioning/
├── datasources/
│ └── datasources.yaml # Prometheus (UID: prometheus-sgi) y Loki (UID: loki-sgi)
└── dashboards/
├── dashboards.yaml # Proveedor de tipo "file" apuntando a esta carpeta
├── sgi-backend.json # Dashboard: métricas JVM / HTTP / HikariCP / Caché
├── node-exporter.json # Dashboard: métricas del host (CPU, RAM, disco, red)
└── cadvisor.json # Dashboard: métricas por contenedor Docker

Los UIDs de datasource fijos (prometheus-sgi, loki-sgi) son referenciados directamente en el JSON del dashboard, eliminando la necesidad de variables de template.

Diagnóstico de problemas

Grafana muestra "No data" en los paneles

  1. Verifica que el target en Prometheus esté UP:
    docker exec sgi-full-prometheus wget -qO- http://localhost:9090/api/v1/targets | grep health
  2. Asegúrate de que haya transcurrido al menos 1 minuto desde el inicio del backend para que Prometheus tenga datos suficientes.
  3. Ajusta el rango de tiempo en Grafana al momento en que el backend inició.

Promtail no recolecta logs

Verifica los logs de Promtail:

docker logs sgi-full-promtail --tail 50

Debe aparecer msg="added Docker target" para cada contenedor sgi-full-*.

Backend devuelve 401 en /actuator/prometheus

El endpoint debe estar en la lista de rutas públicas de SecurityConfig.java. Verifica:

docker exec sgi-full-prometheus wget -qS http://sgi-full-backend:8080/actuator/prometheus 2>&1 | grep HTTP

Debe mostrar HTTP/1.1 200. Si muestra 401, reconstruye el backend:

docker compose build backend && docker compose up -d backend

node-exporter no aparece como UP en Prometheus

# Verifica que el contenedor esté corriendo
docker compose -f docker-compose.monitoring.yml ps node-exporter

# Comprueba que expone métricas
docker exec sgi-full-prometheus wget -qO- http://sgi-full-node-exporter:9100/metrics | head -5

Si el contenedor no arranca, verifica que el servidor Linux expone /proc y /sys (siempre es el caso en Linux nativo; puede fallar en WSL2 sin configuración adicional).

cAdvisor no reporta métricas de contenedores

# Verifica que el contenedor esté corriendo
docker compose -f docker-compose.monitoring.yml ps cadvisor

# Comprueba el endpoint de métricas
docker exec sgi-full-prometheus wget -qO- http://sgi-full-cadvisor:8080/metrics | grep container_memory | head -5

Si ves errores de permisos en los logs de cAdvisor:

docker logs sgi-full-cadvisor --tail 20

En algunos kernels de Linux es necesario que /dev/kmsg exista en el host. Verifica con ls /dev/kmsg. Si no existe, elimina la línea devices: [/dev/kmsg] del compose y redesplega.

Prometheus no carga nuevos jobs tras actualizar prometheus.yml

Si se agregan nuevos scrape_configs al archivo pero los targets no aparecen en /targets después de un kill -HUP 1, el contenedor de Prometheus está montando un volumen nombrado inicializado con la config antigua. El HUP recarga desde ese volumen, no desde el archivo en disco.

# Verificar qué config tiene el contenedor actualmente
docker exec sgi-full-prometheus cat /etc/prometheus/prometheus.yml

# Si el archivo en disco tiene los jobs nuevos pero el contenedor no los ve:
docker compose -f docker-compose.monitoring.yml up -d --force-recreate prometheus

--force-recreate recrea el contenedor montando el archivo actual del host sin tocar el volumen de datos TSDB (métricas históricas no se pierden).

Paneles de Latencia HTTP sin datos ("No data")

Spring Boot 3 no expone series _bucket por defecto. Sin buckets, histogram_quantile no puede calcular percentiles.

# Verificar si existen buckets
docker exec sgi-full-prometheus wget -qO- http://sgi-full-backend:8080/actuator/prometheus | grep "http_server_requests_seconds_bucket" | head -3

Si no devuelve nada, agregar a application.properties del backend y redesplegar:

management.metrics.distribution.percentiles-histogram.http.server.requests=true

Panel Caffeine Hit Rate sin datos ("No data")

Dos causas posibles (ambas deben resolverse):

  1. recordStats ausente en el spec de Caffeine — sin él Micrometer no tiene contadores que leer.
  2. Caches creados de forma lazyCaffeineCacheManager crea cada cache en el primer acceso; Micrometer solo se enlaza a los caches que existen al arrancar, por lo que los caches lazy nunca quedan registrados.
# Verificar si existen métricas de caché
docker exec sgi-full-prometheus wget -qO- http://sgi-full-backend:8080/actuator/prometheus | grep "cache_gets_total" | head -5

Si no devuelve nada, agregar a application.properties y redesplegar:

spring.cache.caffeine.spec=maximumSize=1000,expireAfterWrite=3600s,recordStats
spring.cache.cache-names=catalogos-areas,catalogos-areas-todas,catalogos-tipos-equipo,catalogos-marcas,catalogos-modelos,catalogos-so,catalogos-tipos-incidente

Grafana no arranca (crash loop)

Si los logs muestran Datasource provisioning error: data source not found, el volumen grafana_data tiene estado inconsistente. Reinicializa:

docker compose -f docker-compose.monitoring.yml stop grafana
docker rm sgi-full-grafana
docker volume rm sgi-emch_grafana_data
docker compose -f docker-compose.monitoring.yml up -d grafana