Problema
En infraestructuras virtualizadas es frecuente que los servidores dependan de un UPS para evitar apagados bruscos. Cuando el UPS se comunica por USB, la mayoría de las soluciones de gestión de energía en Proxmox requieren scripts personalizados o acceso directo al hardware, lo que complica la automatización y la portabilidad. El patrón típico es:
- Un UPS conectado vía USB a un nodo que no ejecuta directamente el software de monitorización.
- Necesidad de exponer los valores de batería a varios hosts sin instalar agentes complejos.
- Riesgo de que el driver del UPS falle y siga enviando datos “stale”, provocando decisiones de apagado erróneas.
El objetivo es disponer de un punto único que lea los datos del UPS, los sirva a través de una API estándar y permita que cualquier host (incluidos contenedores Docker) tome decisiones de apagado basadas en información fiable.
Causa
Los fallos más habituales provienen de tres áreas:
- Drivers NUT inestables – Cuando el proceso
upsdpierde la conexión con el hardware, sigue respondiendo con los últimos valores. Si el cliente interpreta esos valores como válidos, puede creer que la energía sigue disponible. - Falta de abstracción de lectura – Muchos scripts acceden directamente a
/dev/usb/hiddev*o a/procdel host, lo que impide que otros nodos o contenedores consuman la información sin montar el dispositivo. - Configuración rígida de umbrales – Los umbrales de batería y tiempo de ejecución se definen en archivos locales. Si el UPS no reporta una métrica (por ejemplo,
battery.runtime), el umbral nunca se dispara y el servidor permanece encendido aunque la energía se agote.
Solución
Una arquitectura basada en NUT como driver + cliente NUT read‑only en Docker resuelve los puntos anteriores:
- Servidor NUT dedicado – Puede ejecutarse en cualquier máquina con acceso físico al UPS (Raspberry Pi, NAS, OPNsense, o incluso el propio nodo Proxmox). El servidor expone los datos por TCP (puerto 3493) y gestiona reconexiones automáticas.
- Cliente Docker read‑only – Un contenedor ligero se conecta al servidor NUT, solicita
LIST VARy expone los valores a través de una API REST interna o los escribe en volúmenes compartidos. No envía comandos de apagado ni ejecutaupsmon, por lo que sigue siendo estrictamente de lectura. - Manejo de datos obsoletos – El cliente interpreta respuestas
ERR DATA-STALE,ERR DRIVER‑NOT‑CONNECTEDo la ausencia deups.statuscomo “UPS no disponible”. En esos casos se genera una alarma pero nunca se inicia un apagado. - Umbrales configurables – Los umbrales (porcentaje de carga, minutos restantes) se definen en variables de entorno del contenedor. Si el UPS no reporta una métrica, el cliente muestra un mensaje de “trigger no disponible” y evita crear un umbral imposible.
- Despliegue con Docker Compose – La solución se entrega como una imagen versionada (
ghcr.io/ffind-dev/pve-ups:<tag>). El contenedor se ejecuta con los volúmenesconfigylogspersistentes, lo que permite conservar la configuración entre actualizaciones sin tocar el host.
Paso a paso
-
Instalar NUT en el nodo con acceso al UPS
apt-get install nut-server nut-clientConfigura
ups.confcon el driverusbhid-upsy habilitaMODE=standalone. Reiniciaupsd. -
Crear archivo
docker-compose.ymlversion: "3.8" services: pve-ups: image: ghcr.io/ffind-dev/pve-ups:3.2.0 restart: unless-stopped network_mode: host environment: NUT_HOST: "192.168.1.10" NUT_PORT: "3493" UPS_NAME: "myups" UPS_USER: "monitor" UPS_PASSWORD: "secret" THRESHOLD_RUNTIME: "10" # minutos THRESHOLD_BATTERY: "20" # porcentaje volumes: - ./pve-ups-config:/app/config - ./pve-ups-logs:/app/logs -
Iniciar el contenedor
docker compose up -d -
Configurar la acción de apagado en Proxmox
Usa un API token con permisoSys.PowerMgmty crea una tarea programada que invoquepve-upsvía su API interna cuando la alarma de “low battery” sea emitida.
Cuándo aplicar esta solución
- Entornos con varios nodos Proxmox que comparten un único UPS físico.
- Infraestructuras donde el UPS está conectado a un dispositivo distinto al host principal (NAS, Raspberry Pi, firewall).
- Necesidad de evitar scripts de apagado distribuidos; centralizar la lógica de decisión en un contenedor simplifica el mantenimiento.
- Escenarios donde el driver NUT puede fallar y se requiere una política de “fail‑safe” que no apague el host por datos obsoletos.
No es la solución adecuada cuando:
- El UPS solo necesita ser leído por un único host y ya existe una integración nativa (por ejemplo,
apcupsden el mismo nodo). - Se requiere control activo del UPS (ejecutar
upsdrvctl shutdown), ya que el cliente Docker es estrictamente de solo lectura.
Código
# Instalar NUT server
apt-get update && apt-get install -y nut-server nut-client
# Editar /etc/nut/ups.conf (ejemplo)
cat > /etc/nut/ups.conf <<EOF
[myups]
driver = usbhid-ups
port = auto
desc = "USB UPS en Raspberry Pi"
EOF
# Habilitar modo standalone y reiniciar
sed -i 's/^MODE=.*/MODE=standalone/' /etc/nut/nut.conf
systemctl restart nut-server
# Docker compose (ya mostrado en la sección anterior)
docker compose up -d
Verificación
-
Comprobar conexión NUT
upsc [email protected]Debería listar variables como
battery.charge,battery.runtimeyups.status. -
Revisar logs del contenedor
docker logs pve-upsBusca líneas que indiquen “UPS reachable” o “Data stale – alarm”.
-
Simular pérdida de driver
Desconecta el UPS físicamente y ejecutaupsc. La respuesta debe serERR DRIVER-NOT-CONNECTED. El contenedor debe registrar una alarma sin iniciar el apagado. -
Provocar bajo nivel de batería
Reduce la carga del UPS (por ejemplo, desconectando la carga) hasta quebattery.chargecaiga bajo el umbral configurado. Verifica que el contenedor envíe la alerta y que la tarea API de Proxmox reciba la señal.
Notas adicionales
- SNMPv3 – Si tu UPS solo expone datos vía SNMP, asegúrate de que la librería
pysnmptenga la dependenciacryptographyinstalada; de lo contrario la autenticaciónauthPrivfallará silenciosamente. - Self‑test – Configura
SELF_TEST_INTERVAL(15 min – 24 h) en una variable de entorno. El cliente omitirá la prueba mientras el UPS está en batería, evitando falsos positivos. - Persistencia de configuración – Mantén los volúmenes
configylogsfuera del contenedor para que las actualizaciones de la imagen no sobrescriban la configuración personalizada. - Escalado en clúster – En un clúster Proxmox, despliega una única instancia del cliente Docker en el nodo de gestión. Usa la API de Proxmox para que todos los nodos reciban la misma señal de apagado, garantizando que el nodo que ejecuta el cliente sea el último en apagarse.