Monitorización completa con Prometheus, Grafana y Loki: métricas, logs y contenedores Docker

Etiquetas:

Un servidor sin monitorización es un servidor ciego. No sabes cuándo se llena el disco, qué contenedor está consumiendo demasiada RAM, o cuántas peticiones 404 está generando tu web. Este artículo documenta cómo configuré el stack completo: Prometheus + Node Exporter + Grafana + Loki + Promtail.

La arquitectura

[Servidor doméstico]
  ├── node-exporter        → métricas del sistema (CPU, RAM, disco, red)
  ├── docker-stats-        → métricas de contenedores (textfile collector)
  │   collector
  ├── prometheus           → recolecta y almacena métricas
  ├── loki                 → agrega y almacena logs
  ├── promtail             → envía logs de Nginx y syslog a Loki
  └── grafana              → dashboards de todo lo anterior

Todos los servicios corren en Docker, coordinados por el mismo docker-compose.yml.

Métricas del sistema: Node Exporter

Node Exporter expone métricas del hardware y del SO. El truco: tiene que correr con network_mode: host para ver las interfaces de red reales del servidor. Si corre en red de Docker, solo ve la interfaz eth0 del contenedor.

  node-exporter:
    image: prom/node-exporter:v1.8.2
    network_mode: host
    pid: host
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
      - ./textfile-collector:/textfile:ro
    command:
      - --path.procfs=/host/proc
      - --path.sysfs=/host/sys
      - --path.rootfs=/rootfs
      - --web.listen-address=127.0.0.1:9100
      - --collector.textfile.directory=/textfile

Escucha en 127.0.0.1:9100. Prometheus lo alcanza por 172.17.0.1:9100 (la IP del host desde la red Docker).

Métricas de contenedores: docker stats + textfile collector

El problema con cAdvisor es que no funciona con Docker 29 y el driver de almacenamiento overlayfs en cgroupv2 — falla con «failed to identify read-write layer ID».

La solución: un contenedor ligero que ejecuta docker stats cada 30 segundos y escribe el resultado en formato Prometheus en un archivo que Node Exporter lee.

#!/bin/bash
# docker_stats.sh
OUTFILE="/textfile/docker_stats.prom"
TMPFILE="${OUTFILE}.tmp"

{
echo "# HELP docker_container_cpu_percent CPU usage percentage per container"
echo "# TYPE docker_container_cpu_percent gauge"
# ... más definiciones ...

docker stats --no-stream --format \
  '{{.Name}}|{{.CPUPerc}}|{{.MemUsage}}|{{.NetIO}}' 2>/dev/null | \
while IFS='|' read -r name cpu mem net; do
    cpu_val=$(echo "$cpu" | tr -d '%' | tr ',' '.')
    # ... conversión de unidades ...
    echo "docker_container_cpu_percent{name=\"${name}\"} ${cpu_val}"
    echo "docker_container_memory_bytes{name=\"${name}\"} ${mem_used_bytes}"
    echo "docker_container_running{name=\"${name}\"} 1"
done

# Contenedores parados
docker ps -a --filter "status=exited" --format '{{.Names}}' 2>/dev/null | \
while read -r name; do
    echo "docker_container_running{name=\"${name}\"} 0"
done

} > "$TMPFILE" && mv "$TMPFILE" "$OUTFILE"

La escritura atómica (tmp → final) evita que Prometheus lea un archivo a medias.

  docker-stats-collector:
    image: docker:27-cli
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./textfile-collector:/textfile
      - ./docker_stats.sh:/docker_stats.sh:ro
    entrypoint: sh -c "apk add --no-cache bc > /dev/null 2>&1; while true; do sh /docker_stats.sh; sleep 30; done"

Prometheus: recolectar y retener

  prometheus:
    image: prom/prometheus:v2.51.2
    networks:
      - monitoring
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    command:
      - --config.file=/etc/prometheus/prometheus.yml
      - --storage.tsdb.path=/prometheus
      - --storage.tsdb.retention.time=30d
      - --web.enable-lifecycle

Configuración de scraping:

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: [localhost:9090]

  - job_name: node
    static_configs:
      - targets: [172.17.0.1:9100]
    relabel_configs:
      - target_label: host
        replacement: servidor-casa

172.17.0.1 es la IP del host accesible desde la red Docker bridge. Los datos se retienen 30 días.

Logs: Loki + Promtail

Loki almacena logs sin indexar el contenido completo — solo las etiquetas (labels). Promtail los recoge y los envía con etiquetas como job, host, filename.

  promtail:
    image: grafana/promtail:3.3.2
    user: root
    networks:
      - monitoring
    volumes:
      - ./promtail-config.yml:/etc/promtail/config.yml:ro
      - ./promtail-data:/tmp/promtail
      - ~/infra/web/logs:/logs/nginx:ro
      - /var/log:/logs/host:ro

Necesita correr como root para leer /var/log.

Grafana: dashboards

Grafana se conecta a Prometheus y Loki como data sources. Los dashboards más útiles:

Sistema (Node Exporter):
– CPU total y por núcleo
– RAM usada / libre / cache
– Disco: uso por partición, IOPS, throughput
– Red: tráfico de entrada/salida por interfaz

Contenedores (docker stats):
– CPU % por contenedor
– RAM por contenedor vs límite
– Estado (running/stopped)
– Tráfico de red por contenedor

Logs (Loki):
– Logs de Nginx en tiempo real
– Peticiones por código de estado (200, 301, 404, 500)
– Top de IPs con más peticiones
– Top de rutas más accedidas

Problema: [$__range] en consultas instantáneas de Loki

Al usar paneles de tipo «stat» o «piechart» con Loki, la variable [$__range] no se resuelve — Grafana devuelve «empty duration string». La solución es usar una duración fija:

# MAL (en paneles stat/piechart):
sum by(status) (count_over_time({job="nginx"} | pattern ... [$__range]))

# BIEN:
sum by(status) (count_over_time({job="nginx"} | pattern ... [24h]))

Los paneles de tipo «time series» sí admiten [$__interval] correctamente.

Seguridad del stack

  • Prometheus y Loki no tienen acceso externo — solo en la red interna monitoring
  • Grafana es el único punto de acceso, protegido con Traefik y Let’s Encrypt
  • GF_AUTH_ANONYMOUS_ENABLED=false y GF_USERS_ALLOW_SIGN_UP=false en Grafana
  • Node Exporter escucha solo en 127.0.0.1, no expuesto en todas las interfaces

Resultado

Con este stack tienes visibilidad completa del servidor: qué procesos consumen recursos, qué contenedores fallan, qué peticiones recibe tu web y qué errores genera. Todo en dashboards accesibles desde monitor.serviciosrogeliowar.com.


Equipamiento recomendado

Enlaces de afiliado. Sin coste extra para ti.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *