Problema

En entornos con varios nodos Proxmox, la pérdida de energía suele desencadenar apagados descoordinados. Cada host intenta cerrar sus máquinas virtuales de forma independiente, lo que genera situaciones donde el clúster queda parcialmente activo: algunos nodos siguen con HA habilitado, otros ya están apagados y los discos compartidos (Ceph, ZFS) pierden quorum. Además, la falta de una capa de notificación hace que el equipo de operaciones no reciba alertas tempranas de la caída de la UPS. El reto es conectar la UPS al ecosistema Proxmox de modo que:

  1. La UPS sea monitorizada sin instalar agentes en cada nodo.
  2. Las notificaciones se envíen a los canales habituales (Slack, Discord, ntfy, Teams).
  3. En clústeres, el apagado sea ordenado: desactivar HA, detener VMs, marcar mantenimiento y, finalmente, apagar los hosts.
  4. Se pueda usar un Proxmox Backup Server (PBS) como objetivo de apagado cuando la energía se agota.

Causa

Los fallos típicos provienen de tres áreas:

  • Monitoreo distribuido – Configurar upsmon o NUT en cada nodo genera archivos de configuración duplicados y puntos de falla. Cuando la UPS cambia de puerto USB a SNMP, todos los nodos deben actualizarse manualmente.
  • Descoordinación de HA – En clústers sin una fase previa de desarme, el gestor de alta disponibilidad (HA) vuelve a intentar re‑schedulear VMs en nodos que ya están en proceso de apagado, provocando bucles de reinicio o I/O bloqueado.
  • Notificaciones limitadas – Los scripts de shutdown tradicionales solo escriben en syslog. Sin un webhook flexible, los operadores no pueden filtrar la gravedad ni enviar mensajes a la herramienta de chat que usan.

Solución

Una solución reutilizable combina tres componentes:

  1. Contenedor o LXC de monitorización UPS (pve‑ups) – Un único proceso que consulta la UPS vía SNMP, NUT o API propietaria y expone un endpoint HTTP para que Proxmox lo invoque. No requiere upsmon en cada nodo.
  2. Webhook manager integrado – Permite definir varios destinos, cada uno con su propio formato (JSON, Slack, Discord, ntfy, Teams) y filtro de severidad. Los encabezados de autorización se almacenan como secretos.
  3. Módulo de apagado consciente de clúster – Antes de iniciar el apagado, el contenedor ejecuta una serie de pasos:
    • Desarma HA a nivel de clúster (pvecm status --disable-ha o ajuste de flags en Ceph).
    • Detiene localmente todas las VMs del nodo que está a punto de apagarse.
    • Marca el nodo en modo mantenimiento (pvecm setmaintenance <node>).
    • Si el objetivo es PBS, genera un token válido y llama a la API de PBS para registrar el apagado.
    • Apaga el host mediante la API de Proxmox (/nodes/<node>/status/shutdown).

Implementación paso a paso

  1. Despliegue del contenedor

    docker pull ffind/pve-ups:latest
    docker run -d \
      --name pve-ups \
      -p 8080:8080 \
      -v /etc/pve-ups:/config \
      --restart unless-stopped \
      ffind/pve-ups:latest
    

    El volumen /etc/pve-ups contiene config.yaml con la información de la UPS (IP, comunidad SNMP, credenciales NUT) y la lista de nodos objetivo.

  2. Configurar webhooks
    En config.yaml añada una sección webhooks: con entradas como:

    webhooks:
      - name: slack-alert
        url: https://hooks.slack.com/services/T000/B000/XXXX
        format: slack
        severity: warning
        auth_header: "Bearer ${SLACK_TOKEN}"
      - name: ntfy
        url: https://ntfy.sh/pve-ups
        format: json
        severity: info
    

    Cada webhook tiene su propio filtro; los eventos críticos (batería < 20 %) se envían a todos los destinos.

  3. Activar soporte PBS
    Añada a la configuración del nodo objetivo:

    shutdown_targets:
      - type: pve
        node: node01
      - type: pbs
        url: https://pbs01:8007/api2/json
        token: ${PBS_TOKEN}
    

    El contenedor valida el token contra la API de PBS antes de iniciar el apagado.

  4. Habilitar modo clúster (beta)
    En la UI de PVE‑UPS active la opción “Cluster‑aware shutdown”. El contenedor verificará la versión de Proxmox (>=9.2) y, si está en un clúster, ejecutará los pasos de desarme HA y mantenimiento antes de cualquier apagado individual.

  5. Pruebas en seco
    La UI incluye un “dry‑run” que simula todo el flujo sin apagar los hosts. Use el botón “Run self‑test now” para validar la lógica después de cualquier cambio de configuración.

Cuándo aplicar esta solución

Aplica cuando:

  • Tiene al menos dos nodos Proxmox bajo HA y necesita que el apagado sea coordinado.
  • La UPS está expuesta vía SNMP o NUT y no quiere instalar agentes en cada host.
  • Requiere notificaciones a varios canales de chat y necesita filtrar por severidad.
  • Usa Proxmox Backup Server como repositorio de copias y quiere que reciba la señal de apagado.

No aplica si:

  • El entorno es un único nodo sin HA; el proceso de desarme de clúster es innecesario.
  • La UPS solo se monitorea con upsmon y no hay requerimientos de webhook.
  • No se dispone de acceso a Docker/LXC en el host de gestión.

Código

# Verificar que el contenedor está corriendo y escuchando
curl -s http://localhost:8080/health | grep alive

# Simular una caída de batería (valor bajo para pruebas)
curl -X POST http://localhost:8080/simulate \
  -H "Content-Type: application/json" \
  -d '{"battery_percent":15}'

Verificación

  1. Estado del contenedordocker logs pve-ups debe mostrar “initialized” y la lista de nodos detectados.
  2. Webhook recibido – Revise el canal Slack o la cola ntfy; debe aparecer un mensaje con la etiqueta “warning” y la carga de la batería.
  3. Desarme HA – En la UI de Proxmox, el clúster debe mostrar “HA disabled” antes de que cualquier nodo se apague.
  4. Apagado ordenado – Después del test de simulación, los nodos deben pasar a “maintenance” y luego a “offline”. Verifique que PBS registra el evento en su historial de tareas.
  5. Restauración – Use el botón “Restore cluster” o pvecm setmaintenance <node> off para volver a habilitar HA. Confirme que los nodos vuelven a “online”.

Notas adicionales

  • Persistencia de tokens – Guarde los tokens de PBS y los encabezados de autorización en un gestor de secretos (Vault, sops) y referéncialos con variables de entorno en config.yaml. Evita hardcodearlos.
  • Límites de SNMP – Algunas UPS limitan la frecuencia de polling. Ajuste poll_interval a 30 s o más para evitar bloqueos de la comunidad SNMP.
  • Ceph vs ZFS – Cuando el clúster usa Ceph, el desarme HA también debe establecer los flags noout y nodown para evitar rebalanceos inesperados durante el apagado.
  • Actualizaciones – La actualización del contenedor es tan simple como docker pull + docker restart. No hay cambios de esquema en la base de datos; la compatibilidad con versiones 3.x está garantizada.
  • Modo de emergencia – Si la UPS deja de responder, el contenedor pasa automáticamente a “force‑shutdown” después de un timeout configurable (default 5 min). Revise los logs para asegurarse de que no haya falsos positivos.