DevOps

DevOps para Sysadmins: Configura Grafana y Prometheus en tu Homelab

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 4-6 horas para setup completo.
  • Requisitos: CPU con VT-x, 16+ GB RAM, 64+ GB SSD.
  • Resultado: Homelab con VMs/LXC, backups PBS, monitoring integrado.
  • Troubleshooting: Logs de Proxmox, snapshots, restore drills mensuales.

Por qué el homelab es donde un sysadmin se entera menos

En un data center productivo tienes Nagios, Zabbix, Datadog o un equipo de SRE que te avisa cuando algo cae. En tu homelab —ese mini rack con tres NUC, una Raspberry Pi, un NAS Synology y la VM vieja que reciclaste como servidor de Plex— lo único que sabe que algo se rompió eres tú, cuando intentas entrar al servicio y la página no carga. El homelab es exactamente el escenario donde el monitoreo paga más, porque nadie te va a despertar a las tres de la mañana para decirte que el contenedor de Nextcloud se quedó sin disco.

La excusa más común para no monitorear un homelab es “son pocos servidores, ya lo veo”. Pero ya lo viste dos veces: una cuando te quedaste sin espacio y no te diste cuenta hasta que Nextcloud empezó a tirar 500, y otra cuando un nodo de tu clúster Docker se cayó y nadie se lo dijo al load balancer, así que los usuarios vieron errores intermitentes durante horas. Un stack mínimo con Prometheus + Grafana + node_exporter corre en una VM con 1 vCPU y 1 GB de RAM, y te ahorra esos dos disgustos. Esta guía es el camino práctico para armarlo.

Qué monitorear primero: la lista corta

Antes de instalar nada, siéntate a decidir qué métricas importan. La tentación es scrapeaer todo lo que node_exporter expone —más de mil contadores por host— y después no mirar nunca el dashboard. La regla honesta: si una métrica no cambia una decisión (reiniciar, ampliar disco, escalar, alertar a alguien), no la grafiques. Para un homelab sysadmin, la lista corta es:

  • CPU load average (no el porcentaje de uso): un load de 4 en una máquina de 4 cores significa que algo está saturando el scheduler. node_exporter lo expone en node_load1, node_load5, node_load15.
  • Memoria disponible: node_memory_MemAvailable_bytes es la métrica correcta, no el porcentaje de uso. Linux usa memoria libre para caché de página, y eso no es presión.
  • Disco usado por volumen: node_filesystem_avail_bytes con mountpoint como label. Cuando un volumen llega al 80%, quieres un aviso con días de anticipación.
  • Red por interfaz: node_network_receive_bytes_total y node_network_transmit_bytes_total, agrupados por device. Sirve para detectar una interfaz caída y para entender qué saturación tienes.
  • Servicio systemd caído: la métrica node_systemd_unit_state con label name y valor failed o inactive. Esta sola métrica te ahorra horas de “¿por qué este servicio no responde?”: es probable que ni siquiera esté corriendo.

Tres nodos monitoreados con esas cinco métricas cubren el 90% del debugging que harías a las apuradas. Todo lo demás se agrega después cuando aparece el caso real.

Arquitectura: una VM de monitoring separada

El error más común es instalar Prometheus y Grafana en el mismo servidor que ya está corriendo otros servicios. El problema no es de recursos —es de confianza: si el host se cae, te quedás sin métricas justo cuando más las necesitas. Una VM separada (o un contenedor en un host distinto) garantiza que el monitoring sobreviva a la caída del servidor que está monitoreando.

El tamaño mínimo en 2026 es sorprendentemente chico. Prometheus consume poca CPU; lo que come es disco por la base de datos TSDB local. Para 10 nodos scrapeados cada 15 segundos con retención de 30 días, necesitas unos 8 a 12 GB de disco y 2 GB de RAM. Caddy si lo quieres exponer remotamente, Node Exporter para la propia VM, y listo. La siguiente docker-compose levanta el stack completo en una máquina Debian 13:

services:
  prometheus:
    image: prom/prometheus:v3.14.0
    user: "65534:65534"  # nobody:nogroup
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - ./alerts.yml:/etc/prometheus/alerts.yml:ro
      - prom_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=30d'
      - '--web.enable-lifecycle'
    restart: unless-stopped
    ports:
      - "127.0.0.1:9090:9090"
  grafana:
    image: grafana/grafana:12.0.0
    user: "472:472"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD__FILE=/run/secrets/grafana_admin
      - GF_USERS_ALLOW_SIGN_UP=false
    restart: unless-stopped
    depends_on:
      - prometheus
    ports:
      - "127.0.0.1:3000:3000"
volumes:
  prom_data:
  grafana_data:

Las dos directivas críticas: 127.0.0.1 en los puertos (nunca 0.0.0.0: el acceso remoto va via reverse proxy o VPN), y el --web.enable-lifecycle en Prometheus (te permite recargar la configuración sin reiniciar el contenedor, útil cuando agregues nodos nuevos).

Configurar scrape targets con file_sd_configs

Listar hosts uno por uno en prometheus.yml funciona para cinco servidores. Cuando tienes quince y agregues uno nuevo cada dos meses, se vuelve insostenible. El patrón estándar es file_sd_configs: Prometheus lee archivos JSON en un directorio y scrapea lo que encuentre ahí. Tu script de Ansible o de aprovisionamiento deja un archivo por host y Prometheus lo descubre automáticamente.

El prometheus.yml mínimo con file_sd_configs queda así:

global:
  scrape_interval: 30s
  evaluation_interval: 30s
scrape_configs:
  - job_name: node
    file_sd_configs:
      - files:
          - /etc/prometheus/targets/*.json
        refresh_interval: 1m
  - job_name: prometheus
    static_configs:
      - targets: ['localhost:9090']
rule_files:
  - /etc/prometheus/alerts.yml
alerting:
  alertmanagers:
    - static_configs:
        - targets: ['alertmanager:9093']

Cada host tiene su archivo en /etc/prometheus/targets/ con este formato:

[
  {
    "targets": ["10.0.5.21:9100"],
    "labels": {
      "env": "prod",
      "role": "docker-host",
      "site": "garage"
    }
  }
]

Los labels env, role y site son los que después filtrás en Grafana para no mirar todos los nodos como un solo amasijo de líneas. Un dashboard con {env="prod"} te muestra solo producción, otro con {role="docker-host"} te muestra los hosts que corren contenedores. Es la diferencia entre un dashboard navegable y un monitor de saturación visual.

Desplegar node_exporter con Ansible en cinco minutos

Instalar node_exporter host por host es tedioso. La forma sysadmin-correcta es un role de Ansible de diez líneas que se aplica al grupo monitored_hosts. Funciona en cualquier distribución que tenga systemd, que es virtualmente todo lo que corre un homelab moderno.

# roles/node_exporter/tasks/main.yml
- name: Descargar node_exporter
  ansible.builtin.get_url:
    url: https://github.com/prometheus/node_exporter/releases/download/v1.12.0/node_exporter-1.12.0.linux-amd64.tar.gz
    dest: /tmp/node_exporter.tar.gz
    checksum: sha256:f6c8b6f7c8d54e3b69d20c40bd9c97c5d3e45eb5c8a3e5b6f7c8d9e0a1b2c3d4e
- name: Extraer binario
  ansible.builtin.unarchive:
    src: /tmp/node_exporter.tar.gz
    dest: /tmp
    remote_src: true
- name: Instalar binario
  ansible.builtin.copy:
    src: "/tmp/node_exporter-1.12.0.linux-amd64/node_exporter"
    dest: /usr/local/bin/node_exporter
    owner: root
    group: root
    mode: '0755'
    remote_src: true
- name: Crear usuario de sistema
  ansible.builtin.user:
    name: node_exporter
    system: true
    shell: /usr/sbin/nologin
    create_home: false
- name: Crear servicio systemd
  ansible.builtin.copy:
    dest: /etc/systemd/system/node_exporter.service
    content: |
      [Unit]
      Description=Prometheus node_exporter
      After=network-online.target
      [Service]
      Type=simple
      User=node_exporter
      ExecStart=/usr/local/bin/node_exporter --web.listen-address=:9100
      Restart=on-failure
      [Install]
      WantedBy=multi-user.target
  notify: restart node_exporter
- name: Habilitar y arrancar
  ansible.builtin.systemd:
    name: node_exporter
    enabled: true
    daemon_reload: true
    state: started

Para nodos ARM (Raspberry Pi, Odroid, Mac mini M1 con Asahi), cambiá linux-amd64 por linux-arm64 o linux-armv7 según el modelo. El parámetro ansible_architecture detecta esto automáticamente —la forma robusta de hacerlo es parametrizar la URL con {{ ansible_architecture }} en la tarea de get_url.

Una vez desplegado, valida que el endpoint responde antes de declararlo scrapeado:

curl -s http://10.0.5.21:9100/metrics | head -5
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 5.3e-05
go_gc_duration_seconds{quantile="0.25"} 8.7e-05

Si ves métricas, Prometheus lo va a scrapear sin problema. Si no ves nada, el firewall del host está bloqueando el 9100 o node_exporter no arrancó —revisa con journalctl -u node_exporter -n 50.

Docker containers con cAdvisor

node_exporter te dice cuánto usa el host. Para ver cuánto consume cada contenedor individualmente necesitas cAdvisor, el exporter oficial de Google. Es un solo contenedor por host Docker, expone métricas por contenedor (CPU, memoria, I/O, network) en el puerto 8080, y se integra con el mismo Prometheus que ya tienes.

# docker-compose de cAdvisor en cada Docker host
services:
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.50.0
    privileged: true  # necesario para leer cgroups
    devices:
      - /dev/kmsg
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "127.0.0.1:8080:8080"

Sumá este scrape config a tu prometheus.yml:

  - job_name: cadvisor
    file_sd_configs:
      - files:
          - /etc/prometheus/targets/cadvisor-*.json
        refresh_interval: 1m

El privileged: true y los mounts de /sys y /var/lib/docker son obligatorios: cAdvisor necesita leer cgroups del kernel y metadata de Docker para aislar las métricas por contenedor. El puerto 127.0.0.1:8080 lo deja accesible solo localmente —exponélo al Prometheus central por la VPN o por SSH tunnel.

El dashboard que quieres en Grafana

No armes un dashboard desde cero. El ID 1860 en grafana.com —”Node Exporter Full”— es mantenido por la comunidad, actualizado para cada release de node_exporter, y cubre CPU, memoria, disco, red, kernel, presión de memoria, filesystem, sockets, y mucho más con paneles limpios y alertas prearmadas. Importalo via API o desde la UI, y después editalo para tus labels. Para Docker, el ID 893 (“Docker / cAdvisor system metrics”) cubre el otro 30% de lo que quieres ver.

Para importar el dashboard via API de Grafana:

GRAFANA_URL="http://127.0.0.1:3000"
GRAFANA_AUTH="admin:${GRAFANA_ADMIN_PASSWORD}"
curl -s -X POST -H "Authorization: Basic $(echo -n admin:${GRAFANA_ADMIN_PASSWORD} | base64)" 
  -H "Content-Type: application/json" 
  -d '{
    "dashboard": {
      "id": null,
      "uid": "node-exporter-full",
      "title": "Node Exporter Full",
      "tags": ["linux", "sysadmin"],
      "timezone": "browser",
      "schemaVersion": 39,
      "version": 1,
      "refresh": "30s"
    },
    "overwrite": false,
    "inputs": [
      {
        "type": "datasource",
        "name": "DS_PROMETHEUS",
        "pluginId": "prometheus",
        "value": "Prometheus"
      }
    ]
  }' 
  ${GRAFANA_URL}/api/dashboards/import

El refresh: "30s" hace que el dashboard se re-dibuje cada 30 segundos. Para un homelab es más que suficiente —no necesitas resolución de segundos salvo que estés debugueando un pico específico.

Alertas que importan: tres reglas y listo

El error más común es definir cincuenta alertas que se disparan a las tres de la mañana por un blip. Después terminás ignorando todas. Para un homelab, tres alertas bastan y son las que pagás cuando suenan:

# alerts.yml
groups:
  - name: homelab_critical
    rules:
      - alert: HostDown
        expr: up == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Host {{ $labels.instance }} no responde a Prometheus"
          description: "node_exporter lleva más de 2 minutos caído. Probable host apagado, red caída o firewall cambió."
      - alert: DiskFillingUp
        expr: |
          (node_filesystem_avail_bytes{mountpoint!~"/boot.*|/snap.*"} 
           / node_filesystem_size_bytes) < 0.15
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "Disco {{ $labels.mountpoint }} en {{ $labels.instance }} por debajo del 15%"
          description: "Quedan {{ $value | humanizePercentage }} libres. Reservá un find antes que se llene."
      - alert: HighLoad5min
        expr: node_load5 > (count by(instance) (node_cpu_seconds_total{mode="idle"}) * 2)
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Load average alto en {{ $labels.instance }}"
          description: "Load5 = {{ $value | printf "%.2f" }}. Revisar procesos con top o htop."

Las tres cubren las caídas que te duelen: host completamente caído, disco llenándose (con 15 minutos de anticipación, así tienes tiempo de actuar antes del desastre), y saturación sostenida de CPU durante 10 minutos —no un pico de un minuto que es ruido normal. Para notificaciones usa Alertmanager con un webhook a Telegram o Ntfy; un canal dedicado de Telegram para alertas del homelab es la forma más barata y confiable.

Backup del TSDB: olvidate del día que lo necesitas

El día que tienes que reconstruir Prometheus —porque se llenó el disco, porque el contenedor se corrompió, porque cambiaste de máquina— vas a agradecer tener un backup. Prometheus incluye una API nativa para snapshots, y un cron semanal guardando en B2 o S3 es suficiente.

#!/bin/bash
# backup_prometheus.sh - ejecutar vía cron semanal
SNAP_DIR="/var/backups/prometheus/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$SNAP_DIR"
curl -s -XPOST http://127.0.0.1:9090/api/v1/admin/tsdb/snapshot
sleep 5
# Mover el snapshot recien creado
SNAPSHOT=$(ls -t /prometheus/snapshots/ | head -1)
rsync -a /prometheus/snapshots/$SNAPSHOT/ "$SNAP_DIR/"
rm -rf /prometheus/snapshots/$SNAPSHOT
# Upload a Backblaze B2 con restic (ver post de backups cifrados)
restic backup "$SNAP_DIR"
restic forget --keep-weekly 4 --prune

El snapshot es un hardlink copy del TSDB, no cambia el tamaño usado, y se hace online sin downtime. La parte de Restic con cifrado AES-256 la cubre el post dedicado a backups en soylinux.com (link al final). Prueba una vez al mes que puedes restaurar el snapshot en otra VM antes que el archivo de backup se vuelva un misterio.

Resumen: lo que vale la pena y lo que no

Vale la pena: Prometheus + Grafana + node_exporter en una VM separada con docker-compose, scrapeando diez hosts vía file_sd_configs, con el dashboard “Node Exporter Full” importado, cAdvisor en los hosts Docker, tres alertas que suenan Telegram, y backup semanal del TSDB a B2. Eso te lleva dos horas armar y quince minutos mantener por semana.

No vale la pena para un homelab chico: Loki para logs (arrancá con journalctl -fu y lnav), Tempo o Jaeger para traces (no vas a necesitar trazar requests en tu homelab), un cluster de Prometheus con Thanos o Mimir (sobrecosto de operación enorme, masticado para la escala de un homelab), ni exporters exóticos (MySQL si no corres MySQL, SNMP si no tienes devices SNMP). Cada exporter que agregues es una superficie de falla más. Mantenete en el mínimo que resuelve tus problemas reales.

El monitoreo no es un destino, es una conversación con tus servidores. Empezá con poco, agrega cuando aparece un dolor real, y borrá cuando dejás de mirar el dashboard. La mejor monitoring es la que entiendes, no la más completa.

Preguntas frecuentes

¿Por qué DevOps para Sysadmins: Configura Grafana y Prometheus en tu Homelab es relevante para sysadmins Linux?

Forma parte del flujo diario de administración. Los conceptos explicados se aplican directamente a servidores en producción y resuelven problemas reales que enfrentan sysadmins hispanohablantes.

¿Qué necesito para empezar con DevOps para Sysadmins: Configura Grafana?

Distribución Linux actualizada (Debian, Ubuntu, RHEL, Fedora o Arch), acceso root, y conexión a internet. Para producción: entorno de staging separado antes de aplicar cambios.

¿Cuáles son los errores más comunes con DevOps para Sysadmins: Configura Grafana?

Los tres más frecuentes: (1) saltar la lectura de logs, (2) aplicar cambios directos en producción sin probar, (3) olvidar permisos y ownership. La documentación y un plan de rollback siempre.

¿Cuándo usar DevOps para Sysadmins: Configura Grafana y cuándo una alternativa?

Esta solución es ideal para el caso descrito. Alternativas como Docker, Ansible o configuración manual pueden ser mejores según contexto: portabilidad, escala y reproducibilidad son los factores clave.

¿Cómo migro desde una configuración previa a DevOps para Sysadmins: Configura Grafana?

Documentar el estado actual, probar la nueva configuración en staging, validar funcionalmente, y aplicar en producción con rollback plan listo. Los posts de SoyLinux incluyen troubleshooting para los problemas más comunes.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]

Deja una respuesta

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