Imagina un Raspberry Pi que lleva meses encendido en el taller, junto a la CNC o la impresora 3D. Un día empieza a andar lento, y al siguiente se cuelga justo a mitad de un trabajo. Lo que nunca viste fue el deterioro: la temperatura que subía porque el disipador se llenó de polvo, la tarjeta que se iba llenando semana a semana, las caídas de voltaje de tres segundos cada vez que arrancaban los motores. Un Pi que funciona no te cuenta nada de sí mismo: responde o no responde.
Esta guía convierte todo eso en gráficos con historial y, lo más importante, en un aviso en tu celular antes de que el problema te detenga. Vas a montar Prometheus y Grafana en el propio Pi con tres contenedores de Docker, importar un dashboard listo y crear cuatro alertas que valen la pena. Al final vas a saber:
- Qué hace cada pieza del stack y por qué el modelo de "ir a buscar" los datos simplifica todo.
- El ajuste escondido de Raspberry Pi OS sin el cual la mitad de los gráficos queda vacía.
- Cómo medir el subvoltaje del Pi, el fallo más típico y el que ningún dashboard estándar muestra.
- Cómo leer bien la temperatura (el valor más alto casi nunca es la CPU).
- Cómo recibir las alertas en el celular con ntfy, o en Telegram.
Es un montaje pequeño, no una solución empresarial: funciona con un solo Pi y crece sin drama al puñado de equipos que la mayoría tiene en casa o en el taller.
Concepto: qué vas a medir y con qué
Lo que ves cuando todo está andando
Abres una página y tienes las últimas dos semanas de un vistazo: uso y temperatura de la CPU, memoria libre, espacio en disco con su tendencia, tráfico de red y una fila por cada contenedor de Docker. El valor no está en el número de este momento (eso ya te lo da htop), está en la curva. Un Pi que hoy anda a 45 °C y en marzo andaba a 58 °C tiene un problema de refrigeración, y eso solo lo muestra un historial.
Encima de los gráficos van las alertas. Cuatro cubren casi todo lo que de verdad falla en un servidor casero: el equipo se calienta, el disco se llena, la fuente se queda corta o el equipo deja de responder.
Las cuatro piezas, cada una con una sola tarea
En internet los nombres se mezclan y eso hace que el tema parezca más complicado de lo que es:
| Pieza | Qué hace | Dónde corre |
|---|---|---|
| Node Exporter | Lee los valores del equipo (CPU, RAM, discos, red, temperaturas) y los publica como una página de texto simple | En cada equipo que quieras vigilar |
| cAdvisor | Lo mismo, pero para los contenedores de Docker: CPU, memoria y tráfico por contenedor | En cada equipo con Docker |
| Prometheus | Visita esas páginas cada pocos segundos y guarda los valores con fecha y hora (una base de datos de series de tiempo) | Una vez, en un equipo |
| Grafana | Le hace preguntas a Prometheus, dibuja las respuestas y maneja las alertas | Una vez, en un equipo |
La clave que conviene entender antes de sumar un segundo equipo: Prometheus va a buscar los datos, nadie se los envía. Por eso agregar un equipo es solo levantar un exporter ahí y sumar una línea a la configuración. Sin agentes que registrar ni claves que repartir.
Este modelo "pull" tiene otra ventaja que se nota en el taller: si un equipo muere, Prometheus lo sabe de inmediato porque la visita falla. En un sistema donde cada equipo "empuja" sus datos, un equipo muerto simplemente se queda callado, y el silencio es difícil de distinguir de "todo bien".
Hardware: qué necesitas
- Raspberry Pi 4 o 5 con al menos 2 GB de RAM. Un Pi Zero 2 W con 512 MB no alcanza: el stack en reposo ocupa unos 360 MB, casi no le queda nada al sistema y un dashboard abierto lo pasa del límite. Con los dashboards abiertos el consumo sube a unos 670 MB, y la mayor parte se la lleva Grafana, no Prometheus.
- Raspberry Pi OS de 64 bits con Docker.
- Almacenamiento que aguante muchas escrituras pequeñas. Prometheus escribe todo el tiempo. Una SSD NVMe o una SSD por USB es el lugar correcto; con microSD también funciona, pero la tarjeta pasa a ser la pieza que se desgasta primero.
- Una fuente como corresponde. Parece un detalle, pero es exactamente lo que vas a vigilar en el paso de subvoltaje.
Tiempo total: unos 45 minutos, diez de ellos en un reinicio y en esperar los primeros datos.
Si todavía no tienes Docker en el Pi, esta es la versión corta de la instalación:
curl -sSL https://get.docker.com | sh
sudo usermod -aG docker $USER
Después cierra la sesión y vuelve a entrar para que el cambio de grupo tenga efecto.
Software, parte 1: el ajuste que casi todas las guías olvidan
La mayoría de las guías de monitoreo se escriben en un PC, por eso se saltan este paso. En Raspberry Pi OS el kernel trae desactivado el controlador de memoria de los cgroups. Docker funciona igual, pero nadie puede medir cuánta RAM usa cada contenedor.
Revísalo así:
cat /sys/fs/cgroup/cgroup.controllers
Si la lista dice cpuset cpu io pids y no aparece memory, entonces docker stats muestra 0B en todos los contenedores, cAdvisor no entrega métricas container_memory_* y cualquier dashboard de Docker que importes va a tener los gráficos de memoria vacíos. Lo típico es culpar a cAdvisor y perder una tarde buscando en el lugar equivocado, cuando solo falta un parámetro de arranque.
Respalda el archivo y ábrelo:
sudo cp /boot/firmware/cmdline.txt /boot/firmware/cmdline.txt.bak
sudo nano /boot/firmware/cmdline.txt
Agrega esto al final de la línea que ya existe, separado por un espacio:
cgroup_enable=memory cgroup_memory=1
No crees una segunda línea. cmdline.txt es una sola línea sin salto al final, y el bootloader solo lee la primera. Un salto de línea ahí significa un Pi que no vuelve a arrancar; por eso el respaldo va antes. Si igual te pasa, saca la tarjeta o la SSD, abre la partición de arranque en otro computador y restaura el .bak.
Reinicia y revisa de nuevo:
sudo reboot
# nach dem Neustart:
cat /sys/fs/cgroup/cgroup.controllers
Ahora debería aparecer memory. Desde aquí docker stats muestra valores reales, y Grafana también.
Software, parte 2: el stack con Docker Compose
Todo vive en una carpeta. Créala junto con la estructura de configuración:
mkdir -p ~/monitoring/prometheus
mkdir -p ~/monitoring/grafana/provisioning/datasources
mkdir -p ~/monitoring/grafana/provisioning/alerting
sudo mkdir -p /var/lib/node_exporter
sudo chown $USER:$USER /var/lib/node_exporter
cd ~/monitoring
El archivo compose.yaml completo:
services:
prometheus:
image: prom/prometheus:v3.14.0
container_name: prometheus
restart: unless-stopped
user: "65534:65534"
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=15d
- --storage.tsdb.retention.size=2GB
- --web.enable-lifecycle
volumes:
- ./prometheus:/etc/prometheus:ro
- prometheus-data:/prometheus
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "9090:9090"
node-exporter:
image: prom/node-exporter:v1.12.1
container_name: node-exporter
restart: unless-stopped
pid: host
network_mode: host
command:
- --path.procfs=/host/proc
- --path.sysfs=/host/sys
- --path.rootfs=/host/root
- --collector.textfile.directory=/var/lib/node_exporter
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/host/root:ro,rslave
- /var/lib/node_exporter:/var/lib/node_exporter:ro
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.55.1
container_name: cadvisor
restart: unless-stopped
privileged: true
devices:
- /dev/kmsg
command:
- --docker_only=true
- --housekeeping_interval=30s
- --store_container_labels=false
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker:/var/lib/docker:ro
- /dev/disk:/dev/disk:ro
ports:
- "8080:8080"
grafana:
image: grafana/grafana:13.2.2
container_name: grafana
restart: unless-stopped
user: "472:472"
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: bitte-aendern
GF_ANALYTICS_REPORTING_ENABLED: "false"
GF_ANALYTICS_CHECK_FOR_UPDATES: "false"
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
ports:
- "3000:3000"
depends_on:
- prometheus
volumes:
prometheus-data:
grafana-data:
Antes de levantarlo, cambia la contraseña de Grafana: el valor bitte-aendern del ejemplo significa, literalmente, "por favor cámbiame" en alemán.
Hay dos decisiones en este archivo que no son obvias y que el autor original justifica con mediciones:
Por qué Node Exporter va en la red del host. Casi todas las guías lo ponen en la red bridge normal de Docker, y entonces los gráficos de red miden otra cosa. /proc/net depende del espacio de nombres de red, y --path.procfs=/host/proc no lo cambia: el exporter sigue leyendo la interfaz de su propio contenedor. En el Pi de prueba eso significaba que Node Exporter reportaba 1,2 MB recibidos en eth0 mientras la interfaz real, end0, llevaba 556 MB. Cada gráfico de tráfico mostraba al contenedor de monitoreo hablando solo. network_mode: host lo resuelve, y es lo que recomienda el propio proyecto Node Exporter. Por eso no hay puerto publicado (el exporter ocupa el 9100 directo en el Pi) y Prometheus lo alcanza vía host.docker.internal.

Por qué cAdvisor queda fijo en v0.55.1. El proyecto publica versiones más nuevas en GitHub, pero en el registro gcr.io/cadvisor/cadvisor la última imagen para arm64 es la v0.55.1. Si pides una etiqueta más nueva o :latest, obtienes no such manifest.
Ahora la configuración de Prometheus, prometheus/prometheus.yml:
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ["localhost:9090"]
- job_name: node
static_configs:
- targets: ["host.docker.internal:9100"]
- job_name: cadvisor
static_configs:
- targets: ["cadvisor:8080"]
Resiste la tentación de ponerle una etiqueta instance propia al objetivo para que los gráficos queden con un nombre bonito. El dashboard que vas a importar filtra por instance con la dirección completa host.docker.internal:9100; si la reemplazas, todos los gráficos quedan vacíos y parece una instalación rota. Más abajo verás la forma correcta de nombrar equipos.
Y la fuente de datos de Grafana, grafana/provisioning/datasources/datasource.yml:
apiVersion: 1
datasources:
- name: Prometheus
uid: prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false
Fija el uid desde ya. Muchos ejemplos lo omiten y Grafana inventa uno, pero las alertas de más adelante apuntan a la fuente de datos por su uid. Si agregas esa línea después, sobre una fuente ya creada, Grafana deja de arrancar con Datasource provisioning error: data source not found, interfaz web incluida, y la única salida es borrar el volumen de Grafana.
Levántalo:
docker compose up -d
docker compose ps
Dale un minuto y abre http://<ip-del-pi>:9090/targets. Los tres jobs deben estar en UP. Si node o cadvisor están caídos, el mensaje de error en esa misma página dice por qué; casi siempre es un error de tipeo en el nombre del objetivo.

Software, parte 3: el subvoltaje, lo que el Pi no le cuenta a nadie
Node Exporter es genérico: no sabe qué es vcgencmd ni conoce el subvoltaje. Y el throttling (bajar la frecuencia para protegerse) es el fallo más típico del Pi. Basta una fuente débil o un cable USB largo y delgado para que el firmware baje el reloj; el Pi se pone lento y ningún dashboard común te dice por qué.
Cómo funciona get_throttled por dentro
vcgencmd get_throttled devuelve un número en hexadecimal, por ejemplo throttled=0x50005. Cada bit es una bandera:
| Bit | Significado | Tipo |
|---|---|---|
| 0 | Subvoltaje detectado ahora | Estado actual |
| 1 | Frecuencia del ARM limitada ahora | Estado actual |
| 2 | Throttling activo ahora | Estado actual |
| 3 | Límite suave de temperatura activo ahora | Estado actual |
| 16 | Hubo subvoltaje desde el arranque | Memoria |
| 17 | Hubo limitación de frecuencia desde el arranque | Memoria |
| 18 | Hubo throttling desde el arranque | Memoria |
| 19 | Se alcanzó el límite suave de temperatura desde el arranque | Memoria |
En el ejemplo 0x50005, los bits 0 y 2 están activos (hay subvoltaje y throttling en este momento) y los bits 16 y 18 dicen que ya había pasado antes. El Pi marca subvoltaje cuando la alimentación baja de alrededor de 4,63 V; con una fuente de 5 V justa y un cable con resistencia, un pico de consumo (la CPU a fondo, una SSD arrancando, un periférico USB) basta para cruzar esa línea.
El script
Node Exporter puede leer métricas extra desde una carpeta, y para eso está la opción --collector.textfile.directory del compose.yaml. Crea ~/monitoring/rpi_throttled.sh:
#!/bin/bash
set -eu
OUT=/var/lib/node_exporter/rpi_throttled.prom
RAW=$(vcgencmd get_throttled) # z. B. throttled=0x50005
VALUE=$((${RAW#throttled=}))
bit() { echo $(( (VALUE >> $1) & 1 )); }
{
echo "# HELP rpi_throttled_flags Raw value of vcgencmd get_throttled."
echo "# TYPE rpi_throttled_flags gauge"
echo "rpi_throttled_flags $VALUE"
echo "# HELP rpi_throttled_state Raspberry Pi throttling state, 1 = active."
echo "# TYPE rpi_throttled_state gauge"
echo "rpi_throttled_state{state=\"under_voltage\"} $(bit 0)"
echo "rpi_throttled_state{state=\"frequency_capped\"} $(bit 1)"
echo "rpi_throttled_state{state=\"throttled\"} $(bit 2)"
echo "rpi_throttled_state{state=\"soft_temp_limit\"} $(bit 3)"
echo "# HELP rpi_throttled_since_boot Raspberry Pi throttling events since boot, 1 = occurred."
echo "# TYPE rpi_throttled_since_boot gauge"
echo "rpi_throttled_since_boot{state=\"under_voltage\"} $(bit 16)"
echo "rpi_throttled_since_boot{state=\"frequency_capped\"} $(bit 17)"
echo "rpi_throttled_since_boot{state=\"throttled\"} $(bit 18)"
echo "rpi_throttled_since_boot{state=\"soft_temp_limit\"} $(bit 19)"
} > "$OUT.tmp"
mv "$OUT.tmp" "$OUT"
Dale permisos, córrelo una vez para probarlo y agrégalo al cron:
chmod +x ~/monitoring/rpi_throttled.sh
~/monitoring/rpi_throttled.sh
cat /var/lib/node_exporter/rpi_throttled.prom
crontab -e
Al final del crontab agrega esta línea (si tu usuario no es pi, corrige la ruta):
* * * * * /home/pi/monitoring/rpi_throttled.sh >/dev/null 2>&1
El script genera dos grupos de métricas y la diferencia importa. rpi_throttled_state es el estado en este instante. rpi_throttled_since_boot recuerda si pasó alguna vez desde el último reinicio, y es la que atrapa la caída de voltaje de tres segundos a las cuatro de la mañana, mucho después de que el valor instantáneo volvió a cero. Como el cron corre una vez por minuto, sin esa memoria te perderías casi todos los eventos cortos.
El mv del final tampoco es decorativo. Node Exporter puede leer la carpeta en cualquier momento, y si el script escribiera directo en el archivo final, a veces leería un archivo a medio escribir. Escribir primero con otro nombre y después renombrar hace el cambio atómico.
Tests: el dashboard y cómo leerlo sin engañarte
Importar el dashboard 1860
No tienes que diseñar nada. Node Exporter Full es el dashboard que usa prácticamente todo el mundo; en grafana.com tiene el ID 1860 y más de cien millones de descargas. Abre Grafana en http://<ip-del-pi>:3000, entra y ve a Dashboards → New → Import. Escribe 1860, cárgalo y elige Prometheus como fuente de datos.
Casi todo se llena de inmediato. Una parte no se va a llenar nunca, y es mejor saberlo antes para no buscar un error que no existe: de los 124 gráficos con consulta, 16 quedan vacíos en un Raspberry Pi, por dos razones distintas.
Primero, collectors apagados. Node Exporter trae de fábrica una selección moderada. Estos gráficos funcionan en cuanto activas el collector correspondiente en la sección command de node-exporter en el compose.yaml:
- --collector.systemd
- --collector.processes
- --collector.interrupts
- --collector.tcpstat
Con eso aparecen los gráficos de systemd, los contadores de procesos e hilos, las interrupciones y los tres gráficos de estados de conexiones TCP. Ojo: cada collector cuesta algo de CPU en cada lectura y suma series a la base de datos, sobre todo systemd en un equipo con muchas unidades. Activa lo que de verdad vas a mirar.
Segundo, datos que el kernel del Pi no entrega. Aquí no hay collector que ayude:
| Gráfico | Por qué queda vacío |
|---|---|
| Pressure / Pressure Stall Information | Raspberry Pi OS no compila PSI; no existe /proc/pressure |
| Memory DirectMap, Memory HugePages | Esas líneas no aparecen en /proc/meminfo en arm64 |
| Power Supply | El Pi no tiene batería; /sys/class/power_supply/ está vacío |
Nada de eso está roto: es la diferencia entre un dashboard pensado para servidores y el equipo que tienes sobre el escritorio.
La trampa de la temperatura de CPU
Es el valor que todos buscan primero y el más fácil de leer mal, porque el Pi no reporta una temperatura sino seis. Si en Explore consultas node_hwmon_temp_celsius, recibes tres chips distintos:
| Chip | Nombre | Qué es en realidad |
|---|---|---|
thermal_thermal_zone0 |
cpu_thermal | El SoC. Esta es la temperatura de CPU que buscas |
1000120000_pcie_1f000c8000_adc |
rp1_adc | Un sensor del chip de entradas y salidas RP1. En reposo anda más caliente que la CPU |
nvme_nvme0 |
nvme | Tres sensores de la SSD NVMe, si tienes una |
El gráfico Hardware Temperature Monitor del dashboard 1860 dibuja las seis curvas juntas, y la de más arriba normalmente no es la CPU: en el equipo de prueba, el sensor del RP1 marcaba 39 °C con la CPU a 31 °C. Si miras solo el valor más alto, vas a creer que tu Pi está siempre más caliente de lo que está.

Para un gráfico propio que muestre solo la CPU, usa esta consulta:
node_hwmon_temp_celsius{chip="thermal_thermal_zone0",sensor="temp0"}
Compárala una vez con vcgencmd measure_temp en el propio Pi: deberían diferir en pocas décimas.
Un detalle más: si tu ventilador aparece en 0 RPM aunque esté girando, revisa dónde está conectado. La métrica node_hwmon_fan_rpm{chip="platform_cooling_fan"} solo funciona con un ventilador enchufado al conector de cuatro pines del Pi 5. Las cajas con control propio de ventiladores (el equipo de prueba usa una Pironman 5, que los maneja por un GPIO) dejan esa métrica siempre en cero. Antes de comprar un ventilador de repuesto, mira el cable.
¿Cuánto se calienta un Pi de verdad?
Con las curvas funcionando, el autor original hizo una prueba de carga con stress-ng --cpu 4 --timeout 720s (doce minutos con los cuatro núcleos al máximo) y leyó el resultado desde las series de tiempo:
| Momento | Temperatura del SoC |
|---|---|
| Reposo antes de la prueba | 27,1 °C |
| A los dos minutos | 36,4 °C |
| A los seis minutos | 43,6 °C |
| Meseta, minutos 9 a 12 | 45,8 °C |
| Máximo | 46,9 °C |
| Dos minutos después de terminar | 33,7 °C |
vcgencmd get_throttled quedó en 0x0 todo el tiempo: sin throttling, sin subvoltaje, sin tocar siquiera el límite suave de temperatura.

Ese número necesita contexto: el Pi 5 de la prueba estaba en una caja Pironman 5 con heatpipe pasivo y dos ventiladores siempre encendidos, mucha más refrigeración que una placa desnuda o el cooler activo oficial. Lo que demuestra es que un Pi 5 bien refrigerado tiene unos 33 grados de margen antes de que el firmware baje la frecuencia. Lo que no demuestra es cómo se porta un Pi sin refrigeración con la misma carga. Para las alertas eso significa que un umbral de 70 °C está bien elegido aunque en un montaje así nunca se dispare: está ahí para el día en que falle un ventilador o el disipador se llene de polvo, que en un taller con viruta y aserrín llega antes de lo que crees.
Varios equipos: sumar un segundo Pi, un NAS o un mini PC
Como Prometheus va a buscar los datos, sumar un equipo es dejarle algo que buscar. En el segundo equipo (otro Pi, un mini PC, cualquier cosa con Linux) solo levantas el exporter:
docker run -d --name node-exporter --restart unless-stopped \
--pid host --net host \
-v /proc:/host/proc:ro -v /sys:/host/sys:ro -v /:/host/root:ro,rslave \
prom/node-exporter:v1.12.1 \
--path.procfs=/host/proc --path.sysfs=/host/sys --path.rootfs=/host/root
Después, en el Pi de monitoreo amplías el job node. Aquí va la forma correcta de nombrar equipos que quedó pendiente: no sobrescribes instance, agregas una etiqueta propia al lado:
- job_name: node
static_configs:
- targets: ["host.docker.internal:9100"]
labels:
device: monitoring-pi
- targets: ["192.168.1.50:9100"]
labels:
device: nas
- targets: ["192.168.1.51:9100"]
labels:
device: medien-pi
instance sigue teniendo la dirección, así que el dashboard 1860 funciona normal y muestra los tres equipos en su selector. La etiqueta device es tuya, para gráficos propios y para textos de alerta donde nas se lee mucho mejor que 192.168.1.50:9100.
Una consecuencia que conviene conocer: si después cambias la dirección de un objetivo, Prometheus lo trata como otra serie. El historial no se muda, y un dashboard filtrado por el instance viejo se corta justo en el momento del cambio, lo que parece que la recolección murió. No se perdió nada: elige el valor nuevo en el selector. Buena razón para dejar bien las direcciones (idealmente con IP fija reservada en el router) antes de acumular meses de datos.
Prometheus toma el cambio sin reiniciarse, gracias a --web.enable-lifecycle:
curl -X POST http://localhost:9090/-/reload
Luego revisa /targets. Recuerda que un exporter escuchando en 0.0.0.0:9100 es legible para cualquiera en la red; en una red en la que no confíes, amárralo a la interfaz por la que llega Prometheus o ponle una regla de firewall.
Alertas: cuatro que valen la pena
Grafana trae su propio sistema de alertas. Para un montaje de este tamaño no necesitas Alertmanager, y te ahorras un quinto contenedor con su consumo de memoria. Las reglas se pueden dejar en un archivo, que es más cómodo que hacer clic y además queda en tu respaldo.
Crea ~/monitoring/grafana/provisioning/alerting/alerts.yml. Esta es la primera regla completa:
apiVersion: 1
groups:
- orgId: 1
name: raspberry-pi
folder: Raspberry Pi
interval: 1m
rules:
- uid: rpi-cpu-temperature
title: CPU-Temperatur zu hoch
condition: C
for: 5m
noDataState: NoData
execErrState: Error
annotations:
summary: Der SoC liegt seit fünf Minuten über 70 °C. Kühler und Luftstrom prüfen.
labels:
severity: warning
isPaused: false
data:
- refId: A
relativeTimeRange:
from: 600
to: 0
datasourceUid: prometheus
model:
refId: A
editorMode: code
instant: true
expr: node_hwmon_temp_celsius{chip="thermal_thermal_zone0",sensor="temp0"}
- refId: B
datasourceUid: __expr__
model:
refId: B
type: reduce
reducer: last
expression: A
- refId: C
datasourceUid: __expr__
model:
refId: C
type: threshold
expression: B
conditions:
- evaluator:
type: gt
params: [70]
La regla tiene tres consultas encadenadas: A trae la temperatura, B se queda con el último valor y C compara contra 70. Las otras tres reglas se arman igual; solo cambian la consulta y el umbral:
| Regla | Qué vigila | Se dispara tras |
|---|---|---|
| Temperatura de CPU alta | node_hwmon_temp_celsius{chip="thermal_thermal_zone0",sensor="temp0"} sobre 70 |
5 min |
| Disco raíz llenándose | Porcentaje libre de / bajo 15 % |
10 min |
| Equipo vigilado caído | up bajo 1 |
2 min |
| Subvoltaje detectado | rpi_throttled_state{state="under_voltage"} sobre 0 |
1 min |
Para el disco, una consulta que funciona es node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} * 100, con el umbral en 15.
Los tiempos de espera son lo que casi todos configuran mal. Una CPU que toca 71 °C durante ocho segundos en un respaldo no es un problema; una que se queda ahí cinco minutos, sí. Sin la espera te despierta el ruido, dejas de leer los avisos y terminas peor que sin alertas. Al disco se le dan diez minutos porque el espacio se mueve lento, pero ojo: si Prometheus guarda su base en el mismo disco que vigila, una retención mal puesta puede llenarlo más rápido de lo que esperas (más abajo lo dimensionamos).
Después de editar el archivo recarga la configuración sin reiniciar Grafana:
curl -X POST -u admin:euer-passwort \
http://localhost:3000/api/admin/provisioning/alerting/reload
Las reglas aparecen en Alerting → Alert rules, cada una en estado Normal. Una regla en Pending ya cruzó el umbral y está esperando su tiempo; Firing significa que avisó.

El aviso en el celular
Una alerta que solo enciende un dashboard que nadie está mirando es decoración. ntfy es la salida más simple: instalas la app, te suscribes a un topic que inventas tú y todo lo que se publique en ese topic llega como notificación. Sin cuenta y sin token de bot.
Al crear la suscripción en la app, deja apagado el interruptor de usar otro servidor; si lo activas sin tener un servidor propio, la app queda preguntando al vacío.

En Grafana, crea grafana/provisioning/alerting/contactpoints.yml con un contact point de tipo webhook que publica en ntfy.sh. La sección payload es la parte decisiva: arma el JSON que ntfy espera, con título, mensaje, etiquetas de emoji y prioridad distintos para una alerta disparada y para una resuelta:
apiVersion: 1
contactPoints:
- orgId: 1
name: ntfy
receivers:
- uid: ntfy-raspberry-pi
type: webhook
settings:
url: https://ntfy.sh
httpMethod: POST
payload:
template: |
{"topic":"euer-topic-name-hier","title":"{{ if eq .Status "firing" }}{{ .CommonLabels.alertname }}{{ else }}Resolved: {{ .CommonLabels.alertname }}{{ end }}","message":"{{ range .Alerts }}{{ .Annotations.summary }} [{{ .Labels.instance }}] {{ end }}","tags":["{{ if eq .Status "firing" }}rotating_light{{ else }}white_check_mark{{ end }}"],"priority":{{ if eq .Status "firing" }}4{{ else }}3{{ end }}}
disableResolveMessage: false
Cambia euer-topic-name-hier ("el nombre de tu topic aquí") por el topic que creaste en la app. Como cualquiera que adivine el nombre puede leerlo, usa algo largo y difícil de adivinar, no mi-raspberry. Este archivo solo define el destino; para que las alertas lleguen a él también necesitas una política de notificación que las enrute a ese contact point. El archivo completo, con esa política incluida, está en el repositorio del proyecto (enlace al final). También puedes asignar el contact point por defecto desde Alerting → Notification policies.

Retención: que la base de datos no se coma el disco
En el compose.yaml, Prometheus tiene dos límites: --storage.tsdb.retention.time=15d y --storage.tsdb.retention.size=2GB. Se aplica el que se cumpla primero: borra lo más antiguo al pasar los 15 días o al llegar a 2 GB.
Para tener una idea del orden de magnitud: Prometheus comprime cada muestra a más o menos 1 o 2 bytes. Un Pi con Node Exporter y cAdvisor genera del orden de unas mil series; a una muestra cada 15 segundos son 5.760 muestras por serie al día, es decir, unos 10 MB diarios, y con 15 días quedas bastante bajo los 2 GB. Si sumas equipos o activas collectors extra, la cuenta crece en proporción, y el límite de tamaño es la red de seguridad para que el disco nunca se llene por culpa del monitoreo. Si quieres más historia, sube el tiempo solo después de mirar cuánto ocupa de verdad la carpeta de datos después de una semana.
Y si todavía estás en microSD, este es el mejor argumento para pasar a una SSD: una base de datos de series de tiempo escribiendo cada 15 segundos es justo el patrón que desgasta una tarjeta.
Cuándo no necesitas todo esto
Si tienes un solo Pi que usas de vez en cuando y no te importa el historial, htop y un vcgencmd get_throttled de vez en cuando bastan. El stack se justifica cuando el Pi hace algo que no puede fallar sin aviso: controla una máquina, guarda respaldos, sirve la casa domótica o corre solo por semanas.
Variantes y mejoras
- Panel de subvoltaje para el Pi del taller. Si tu Pi controla una CNC, una impresora 3D o una estación de soldadura, crea un panel tipo State timeline con
rpi_throttled_state{state="under_voltage"}y otro tipo Stat conmax_over_time(rpi_throttled_state{state="under_voltage"}[24h]). El primero te dice cuándo cayó el voltaje; cruzándolo con la hora de arranque de los motores sabrás si comparten fuente o cableado con el Pi, la causa más común de cortes en talleres. Sumarpi_throttled_since_bootpara no perder los eventos que duran menos que el minuto del cron. - Alertas a Telegram en vez de ntfy. Grafana tiene un contact point nativo de Telegram: crea un bot con @BotFather, copia el token, obtén el ID de tu chat y agrégalo en Alerting → Contact points → Add contact point → Telegram. Útil si en el taller ya coordinan todo por un grupo de Telegram: el aviso de subvoltaje o de disco lleno llega a todos.
- Vigilar el ambiente, no solo el Pi. Conecta un sensor BME280 por I2C al mismo Pi, expón temperatura y humedad del taller con el mismo truco del script en la carpeta de textfile, y crea una alerta de humedad alta para las bobinas de filamento o de sobretemperatura en el gabinete eléctrico.
- Un UPS para el Pi. Si la alerta de subvoltaje salta seguido por cortes de luz, un HAT UPS con baterías 18650 mantiene el Pi vivo y te permite medir cuánto dura la autonomía con el mismo stack.
Personalización para Chile
En Chile consigues lo necesario para este montaje en MechatronicStore:
- Raspberry Pi 5 de 4 GB (o un Raspberry Pi 4 con 2 GB o más): el servidor de monitoreo. Con 4 GB sobra espacio para Grafana y otros servicios.
- Fuente USB-C oficial de 27 W para Raspberry Pi 5: la mejor prevención contra la alerta de subvoltaje. Los cargadores de celular genéricos son la causa número uno del throttling.
- SSD NVMe con HAT M.2 para Raspberry Pi 5: el lugar correcto para la base de datos de Prometheus.
- Cooler activo para Raspberry Pi 5: mantiene la CPU lejos de los 70 °C de la alerta y se conecta al conector de cuatro pines, así que la métrica de RPM funciona.
- Tarjeta microSD de 32 GB: para el primer arranque o si todavía no migras a SSD.
Si en el tutorial original usan una caja Pironman 5 con ventiladores por GPIO, el cooler activo oficial cumple la función de refrigerar, con la ventaja de que su velocidad sí aparece en el dashboard.
Recursos
- Tutorial original (alemán), en el que se basa esta guía: Raspberry Pi Monitoring mit Prometheus und Grafana, de Philipp Schweizer en raspberry.tips
- Repositorio GitHub: raspberry tips/raspberry pi monitoring, con todos los archivos de configuración listos para clonar
- Dashboard descargable: Node Exporter Full (ID 1860)
- Documentación adicional: Node Exporter y su guía para Docker
Versión chilena con componentes en stock local en MechatronicStore.




