Operación en producción: monitoreo, notificaciones, actualizaciones y runbook
Operación en producción: monitoreo, notificaciones, actualizaciones y runbook
Un cluster que funciona el día que lo montas no demuestra nada. Lo que decide si Proxmox VE aguanta en producción es lo que pasa a los seis meses: el disco de backups que se llenó un martes por la noche, el kernel nuevo que rompió el passthrough de una GPU, el nodo que se reinició solo a las tres de la mañana y nadie se enteró hasta el lunes. En el capítulo 14 automatizaste la creación de recursos; este capítulo va del otro lado del ciclo de vida, el que nadie automatiza y todo el mundo sufre.
Hay tres piezas que resuelven casi todo. La primera es el sistema de notificaciones de PVE, que convierte seis tipos de evento internos en correos, mensajes de Gotify o llamadas a un webhook, con filtrado por severidad y por calendario. La segunda es la telemetría: pvestatd empujando métricas a Graphite o InfluxDB, o un exporter tirando de la API para Prometheus. La tercera es un procedimiento de actualización que no dependa de la suerte, con pve8to9 como red de seguridad y proxmox-boot-tool como marcha atrás cuando el kernel nuevo te deja sin red.
Al final tendrás alertas que llegan a alguien que puede actuar, las métricas que importan en un dashboard, una ventana de mantenimiento repetible y un runbook con las emergencias que de verdad ocurren: quorum perdido, recurso HA en error, storage lleno, backup fallido y nodo caído. La versión de referencia es Proxmox VE 9.2 sobre Debian 13 Trixie, con kernel 7.0 y corosync 3.1.10-pve2.
El sistema de notificaciones: eventos, matchers y targets
Proxmox VE no tiene “alertas” en el sentido de un sistema de monitorización. Tiene un bus de notificaciones con tres etapas: un evento se emite con unos metadatos, uno o varios matchers deciden si les interesa, y cada matcher que coincide lo entrega a sus targets.
flowchart LR
E1["vzdump correcto o fallido"] --> M
E2["fencing"] --> M
E3["replication fallida"] --> M
E4["package-updates"] --> M
E5["system-mail"] --> M
M["Matchers<br/>match-field / match-severity / match-calendar"] --> T1["sendmail<br/>MTA del sistema con cola"]
M --> T2["smtp<br/>relay directo sin cola"]
M --> T3["gotify"]
M --> T4["webhook<br/>plantillas Handlebars"]
La configuración vive en dos ficheros dentro de pmxcfs, así que es cluster-wide sin copiar nada: /etc/pve/notifications.cfg con targets y matchers, y /etc/pve/priv/notifications.cfg con los secretos (contraseñas SMTP, tokens de Gotify y de webhook), este último legible solo por root. Esa separación permite dar auditoría sobre la configuración sin exponer los tokens. El nodo ACL es /mapping/notifications: Mapping.Modify y Mapping.Audit para configurar targets, y Mapping.Use, Mapping.Audit o Mapping.Modify para probarlos. Las rutas ACL están explicadas en el capítulo 10.
De fábrica viene un target mail-to-root de tipo sendmail, que escribe al correo del usuario root@pam, y un matcher por defecto que le manda absolutamente todo. Un cluster recién instalado ya notifica, pero a una dirección que probablemente nadie lee.
Los seis eventos que emite Proxmox VE y sus metadatos
La lista es corta y conviene memorizarla, porque es todo lo que puedes filtrar:
| Evento | type | Severidad | Metadatos |
|---|---|---|---|
| Hay actualizaciones del sistema | package-updates | info | hostname |
| Un nodo del cluster fue fenceado | fencing | error | hostname |
| Un job de replicación de storage falló | replication | error | hostname, job-id |
| Backup correcto | vzdump | info | hostname, job-id |
| Backup fallido | vzdump | error | hostname, job-id |
| Correo para root | system-mail | unknown | hostname |
Las severidades posibles son info, notice, warning, error y unknown. Los campos que puedes usar en un matcher son type, hostname (sin dominio) y job-id.
Gotcha: system-mail tiene severidad unknown, no warning ni error. Si escribes un matcher con match-severity error,warning pensando que cubre “todo lo malo”, te dejas fuera precisamente el correo de smartd avisando de que un disco se muere. Para el correo del sistema hay que filtrar por type, no por severidad.
Tres de estos eventos tienen además un interruptor global en /etc/pve/datacenter.cfg:
notify: package-updates=auto,fencing=always,replication=always
package-updates acepta auto (por defecto), always o never; fencing y replication aceptan always o never. El valor auto es el que evita que recibas un correo diario idéntico: PVE decide cuándo tiene sentido volver a avisar. Ponerlo en always en un cluster de ocho nodos es la forma más rápida de entrenar a tu equipo para ignorar los correos de Proxmox.
Targets sendmail, smtp, gotify y webhook
sendmail entrega al MTA local (normalmente Postfix) y se olvida. La ventaja es enorme: tiene cola y reintentos. Si el relay de correo está caído, el mensaje espera y sale cuando vuelva.
sendmail: example
mailto-user root@pam
mailto-user admin@pve
mailto [email protected]
from-address [email protected]
comment Send to multiple users/addresses
Opciones: mailto (direcciones sueltas), mailto-user (usuarios de PVE, toma su email), from-address (por defecto root@<hostname>), author (por defecto Proxmox VE) y comment.
smtp habla directamente con el relay. La parte pública va en notifications.cfg y la contraseña en el fichero de secretos:
smtp: example
mailto-user root@pam
mailto [email protected]
from-address [email protected]
username pve1
server mail.example.com
mode starttls
# y en /etc/pve/priv/notifications.cfg
smtp: example
password somepassword
mode acepta insecure, starttls o tls; el puerto es configurable. La diferencia que importa está documentada sin ambigüedad: «Unlike sendmail targets, SMTP targets do not have any queuing/retry mechanism in case of a failed mail delivery.»
Advertencia: para alertas críticas usa sendmail. Un target smtp que falla pierde el aviso de fencing para siempre, y el fencing es exactamente el evento que no te puedes permitir perder. smtp es cómodo porque no exige configurar Postfix, y esa comodidad se paga en fiabilidad.
gotify sigue el mismo patrón de dos ficheros:
gotify: example
server http://gotify.example.com:8888
comment Send to multiple users/addresses
# y en /etc/pve/priv/notifications.cfg
gotify: example
token somesecrettoken
webhook construye una petición HTTP arbitraria con plantillas Handlebars. Las variables son {{ title }}, {{ message }}, {{ severity }}, {{ timestamp }} (epoch), {{ fields.<name> }} y {{ secrets.<name> }}, con tres helpers: url-encode, escape y json.
| Servicio | Método | URL | Cabecera | Cuerpo | Secreto |
|---|---|---|---|---|---|
| ntfy.sh | POST | https://ntfy.sh/{{ secrets.channel }} | Markdown: Yes | {{ message }} | channel |
| Discord | POST | https://discord.com/api/webhooks/{{ secrets.token }} | Content-Type: application/json | { "content": "{{ escape message }}" } | token |
| Slack | POST | https://hooks.slack.com/services/{{ secrets.token }} | Content-Type: application/json | { "text": "{{escape message}}", "type": "mrkdwn" } | token |
El escape no es decorativo: el cuerpo de un vzdump fallido lleva saltos de línea y comillas, y sin escapar rompes el JSON de Discord o de Slack.
Matchers por campo, severidad y calendario
Un matcher selecciona eventos y los enruta. Sus opciones son target (uno o varios), mode (all para exigir todas las reglas o any para al menos una), invert-match (true/false, invierte toda la lógica), match-calendar (evento de calendario systemd, por ejemplo mon..fri 9-17), match-field (exact:<campo>=<v1>,<v2> o regex:<campo>=<regex>), match-severity (lista de info,notice,warning,error,unknown) y comment.
Separar horario laboral de guardia se resuelve con invert-match, usando el mismo calendario dos veces:
matcher: workday
match-calendar mon..fri 9-17
target admin
comment Notify admins during working hours
matcher: night-and-weekend
match-calendar mon..fri 9-17
invert-match true
target on-call-admins
comment Separate target for non-working hours
Y separar por tipo de problema, que es lo que evita que el equipo de backups reciba avisos de corosync:
matcher: backup-failures
match-field exact:type=vzdump
match-severity error
target backup-admins
comment Send notifications about backup failures to one group of admins
matcher: cluster-failures
match-field exact:type=replication,fencing
target cluster-admins
comment Send cluster-related notifications to other group of admins
Con regex: puedes rutear por nombre de host (match-field regex:hostname=^prod-.+$), útil cuando un mismo cluster atiende varios entornos. Todo esto se gestiona también por API, que es lo que conviene si versionas la configuración:
pvesh get /cluster/notifications/endpoints
pvesh get /cluster/notifications/matchers
pvesh create /cluster/notifications/endpoints/gotify/oncall/test
Si los nombres de los parámetros no cuadran con tu versión, pvesh ls /cluster/notifications y pvesh usage <ruta> -v te dan el esquema real del sistema que tienes delante. No existe un binario dedicado tipo proxmox-notification: es GUI, fichero o API.
Plantillas y el puente del correo del sistema
El cuerpo y el asunto de cada notificación salen de plantillas Handlebars con nomenclatura <type>-<body|subject>.<html|txt>.hbs:
/usr/share/pve-manager/templates/default/
vzdump-body.html.hbs
vzdump-body.txt.hbs
fencing-subject.txt.hbs
Para sobrescribir una, copia el fichero a /etc/pve/notification-templates/default/ con el mismo nombre; al vivir en pmxcfs, el override se replica a todo el cluster. Los targets de correo usan la versión HTML y la de texto; los demás solo la de texto.
El correo del sistema. Todo lo que los daemons locales mandan a root (smartd avisando de sectores reasignados, cron con la salida de un script, mdadm con un array degradado) no se pierde: el binario proxmox-mail-forward lo captura y lo inyecta en el bus como un evento con type=system-mail y severidad unknown. Con targets sendmail se reenvían las cabeceras originales; con el resto se extraen asunto y cuerpo. Es lo que hace que las alertas SMART acaben en el mismo canal que las de Proxmox, y por eso merece un matcher explícito.
En los jobs de backup, el campo notification-mode decide el camino: notification-system para el sistema global de targets y matchers, legacy-sendmail para el mailto antiguo del job, y auto (por defecto) que escoge el legado si el job tiene mailto definido. El detalle está en el capítulo 13.
El stack de observabilidad y los metric servers
Notificaciones y métricas resuelven problemas distintos: las primeras avisan de eventos discretos que ya ocurrieron, las segundas dejan ver la tendencia que lleva al evento. Necesitas las dos.
flowchart LR
PSD["pvestatd en cada nodo"] --> CFG["/etc/pve/status.cfg"]
CFG --> GR["Graphite<br/>puerto 2003 UDP"]
CFG --> IN1["InfluxDB v1<br/>puerto 8089 UDP"]
CFG --> IN2["InfluxDB v2<br/>puerto 8086 HTTPS"]
API["API REST de PVE"] --> EXP["prometheus-pve-exporter<br/>puerto 9221 endpoint /pve"]
EXP --> PROM["Prometheus"]
PROM --> GRAF["Grafana y Alertmanager"]
IN2 --> GRAF
NOTIF["Sistema de notificaciones<br/>eventos que no son metricas"] --> OPS["Correo o Gotify de guardia"]
La diferencia de mecanismo importa: pvestatd empuja las métricas desde cada nodo, mientras que Prometheus tira de la API a través de un exporter. Con push no expones la API a la red de monitorización; con pull no necesitas que cada nodo conozca al servidor de métricas.
Los metric servers se configuran en /etc/pve/status.cfg o desde Datacenter → Metric Server. Graphite usa por defecto el puerto 2003, el path proxmox y protocolo UDP; si lo pasas a proto tcp hay que fijar además timeout 1, porque una conexión colgada bloquearía a pvestatd.
graphite: mi-graphite
server 10.0.0.50
port 2003
path proxmox
proto udp
mtu 1500
InfluxDB v1 recibe línea de protocolo por UDP en el 8089, y esto solo funciona si el servidor tiene el listener habilitado en influxdb.conf con [[udp]], enabled = true, bind-address = "0.0.0.0:8089" y database = "proxmox", que no viene activo de fábrica:
influxdb: influx-udp
server 10.0.0.51
port 8089
mtu 1500
InfluxDB v2 va por HTTP o HTTPS en el 8086 con token de escritura. Los valores por defecto verificados son organization = proxmox, bucket = proxmox, timeout = 1 s y max-body-size = 25000000 bytes:
influxdb: influx2
server influx.example.com
port 8086
influxdbproto https
organization proxmox
bucket proxmox
token EL-TOKEN-CON-PERMISO-DE-ESCRITURA
verify-certificate 1
timeout 1
max-body-size 25000000
Para desactivar un servidor sin borrarlo, disable 1. Por CLI se gestiona con pvesh get /cluster/metrics/server y pvesh create /cluster/metrics/server/<nombre> --type influxdb ....
Gotcha del UDP: si configuras Graphite o InfluxDB por UDP y no llega nada, no habrá ningún error en pvestatd, porque UDP no confirma entrega. La comprobación es del lado del servidor, o con un tcpdump en el puerto correspondiente. Es el fallo silencioso más común de esta parte.
Prometheus con prometheus-pve-exporter
Proxmox no expone métricas en formato Prometheus de forma nativa. El puente es prometheus-pve-exporter, que consulta la API REST y traduce. La versión de referencia es v3.9.0 (junio de 2026), requiere Python 3.9 o superior, escucha en el puerto 9221 y el endpoint de scrape es /pve.
Primero, el usuario de solo lectura. PVEAuditor sobre / es suficiente y no concede ninguna capacidad de escritura:
pveum user add prometheus@pve
pveum acl modify / --users prometheus@pve --roles PVEAuditor
pveum user token add prometheus@pve monitoring --privsep 0
El --privsep 0 hace que el token herede los permisos del usuario, que solo tiene auditoría. Con privsep 1 tendrías que replicar el ACL sobre el token con pveum acl modify / --tokens 'prometheus@pve!monitoring' --roles PVEAuditor. El secreto se muestra una sola vez al crearlo y no es recuperable.
Instalación en Debian 13, donde PEP 668 impide un pip install global, más la configuración del exporter:
apt install pipx
pipx install prometheus-pve-exporter
cat > /etc/prometheus/pve.yml <<'EOF'
default:
user: prometheus@pve
token_name: "monitoring"
token_value: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
verify_ssl: true
EOF
Alternativas equivalentes: un virtualenv en /opt/pve-exporter con pip install prometheus-pve-exporter, o la imagen prompve/prometheus-pve-exporter publicada solo en 127.0.0.1:9221. El scrape en Prometheus:
scrape_configs:
- job_name: 'pve'
static_configs:
- targets:
- 192.168.1.2:9221
metrics_path: /pve
params:
module: [default]
cluster: ['1']
node: ['1']
Las métricas que de verdad usas son estas:
| Métrica | Para qué |
|---|---|
pve_up | Nodo, VM o storage vivo o no |
pve_disk_size_bytes y pve_disk_usage_bytes | Ocupación de storages, la base de la alerta de disco lleno |
pve_memory_size_bytes y pve_memory_usage_bytes | Presión de RAM en el nodo |
pve_cpu_usage_ratio y pve_cpu_usage_limit | Uso de CPU contra la cuota configurada |
pve_uptime_seconds | Detectar reinicios no planificados |
pve_ha_state | Estado de cada recurso HA. La métrica clave para alertar de error o fence |
pve_lock_state | Guests con lock atascado, típico tras un backup muerto |
pve_subscription_status y pve_subscription_info | Suscripción caducada antes de que bloquee el repo enterprise |
pve_replication_* | Jobs de replicación ZFS y su última ejecución |
pve_not_backed_up_info | Guests que no están cubiertos por ningún backup |
Esa última merece atención: es la única forma automática de detectar la VM que alguien creó fuera del job de backup. En un equipo con rotación de gente, es la métrica que evita el desastre silencioso.
Advertencia: el exporter necesita alcanzar la API en el 8006 y su propio 9221 queda abierto a quien lo consulte. No lo publiques en una interfaz pública: aunque el usuario sea de solo lectura, /pve filtra el inventario completo del cluster.
Alertas que significan algo y no ruido
Una alerta útil cumple tres condiciones: describe un estado que requiere acción humana, tiene un for que evita disparar por un pico transitorio, y va a un canal que alguien mira. Todo lo demás es un dashboard, no una alerta.
groups:
- name: proxmox
rules:
- alert: PVENodeDown
expr: pve_up{id=~"node/.*"} == 0
for: 2m
- alert: PVEHAResourceError
expr: pve_ha_state{state="error"} == 1
for: 1m
- alert: PVEStorageAlmostFull
expr: pve_disk_usage_bytes / pve_disk_size_bytes > 0.85
for: 15m
El for: 2m de PVENodeDown no es casual: el failover de HA tiene «typical error detection and failover times of about 2 minutes», así que una alerta más impaciente avisaría de nodos que ya se están recuperando solos.
El reparto entre los dos sistemas queda así:
| Situación | Quién avisa | Por qué |
|---|---|---|
| Nodo fenceado | Notificación fencing | Evento discreto, sin tendencia previa observable |
| Replicación fallida | Notificación replication | Trae el job-id en los metadatos |
| Backup fallido | Notificación vzdump severidad error | También con job-id |
| Storage al 85% | Métrica pve_disk_usage_bytes | Es una tendencia; quieres verla venir |
Recurso HA en error | Métrica pve_ha_state | El estado persiste y no genera evento propio |
| Disco SMART degradado | Notificación system-mail | Viene de smartd vía proxmox-mail-forward |
| Guest sin backup | Métrica pve_not_backed_up_info | No hay evento para algo que no pasó |
Esa última fila es el principio general: el sistema de notificaciones no puede avisarte de la ausencia de algo. Para eso están las métricas.
Logs: qué unidad mira cada cosa y dónde viven las tareas
En PVE 9 todo va a journald. /var/log/syslog solo existe si instalas rsyslog, cosa que no hace falta. Estas son las unidades que tocan cuando algo falla:
| Unidad | Qué contiene |
|---|---|
corosync | Membresía, estado de los links, token timeouts, retransmits |
pve-cluster | pmxcfs: quorum, montaje de /etc/pve, sincronización |
pve-ha-crm | Decisiones de HA, fencing, recovery. Solo el master decide; en el resto está casi vacío |
pve-ha-lrm | Ejecución local de comandos HA, lock del agente, watchdog |
watchdog-mux | Apertura y cierre de /dev/watchdog, clientes conectados |
pvedaemon | API interna en 127.0.0.1:85 |
pveproxy | Peticiones HTTP a la GUI y a la API |
pvestatd | Recolección de estadísticas y envío a los metric servers |
pvescheduler | Lanzamiento de jobs de backup y replicación |
pvesr | Ejecución de la replicación de storage |
pve-firewall | Compilación y aplicación de reglas |
journalctl -b -u corosync
journalctl -eu pve-ha-crm
journalctl -b -p err # solo errores de este arranque
journalctl -u pveproxy --grep "authentication failure"
journalctl -k # kernel: watchdog, ZFS, vfio
journalctl --since "2026-08-09 03:00" --until "2026-08-09 05:00"
Las tareas de PVE no van a journald. Ese es el error de diagnóstico más frecuente: buscar en el journal por qué falló un backup. Las tareas viven en su propio árbol, y en la GUI son Nodo → Task History:
ls /var/log/pve/tasks/
cat /var/log/pve/tasks/index
pvesh get /nodes/localhost/tasks --limit 50
pvesh get /nodes/$(hostname)/tasks/<UPID>/log --output-format text
Mantenimiento del journal, que en un nodo con movimiento crece más de lo que esperas: journalctl --disk-usage para medirlo y journalctl --vacuum-time=14d para recortarlo.
pvereport como línea base antes de tocar nada
pvereport vuelca a stdout un informe completo del nodo, equivalente CLI de Nodo → Subscription → System Report. Su uso correcto no es “cuando algo se rompe” sino antes de cualquier cambio grande, para tener con qué comparar.
pvereport > /root/reporte-$(hostname)-$(date +%F).txt
Recolecta, entre otras cosas: pveversion --verbose, /etc/hosts, apt-cache policy, apt-mark showhold, lscpu, top y /proc/pressure/*; del storage, storage.cfg, pvesm status, findmnt y proxmox-boot-tool status; de los guests, qm list y pct list con todos sus .conf; de la red, ip -details address, /etc/network/interfaces y sdn/.running-config; del firewall, los .fw y iptables-save; del cluster, pvecm nodes, pvecm status, corosync.conf y ha-manager status; los jobs.cfg y replication.cfg; y del hardware, dmidecode, lspci -nnk, lsblk, pvs, lvs, vgs, zpool status, más las salidas de Ceph y multipath si aplican.
Advertencia: el informe contiene datos sensibles. storage.cfg lleva nombres de usuario de los backends, la sección de red revela tu topología completa y el volcado de firewall enseña qué está abierto. Revísalo antes de pegarlo en un foro.
Actualizaciones menores: repos, apt full-upgrade y pveupgrade
En PVE 9 los repositorios usan formato deb822 con extensión .sources. El de no-suscripción, en /etc/apt/sources.list.d/proxmox.sources:
Types: deb
URIs: http://download.proxmox.com/debian/pve
Suites: trixie
Components: pve-no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
El enterprise vive en pve-enterprise.sources con URIs: https://enterprise.proxmox.com/debian/pve y Components: pve-enterprise; los de Debian, en debian.sources, apuntando a trixie, trixie-updates y trixie-security. Si vienes de ficheros .list, el conversor oficial es apt modernize-sources (responde n la primera vez para ver el diff, y repite con Y).
El ciclo menor es corto y tiene una regla dura:
apt update
apt full-upgrade
pveversion -v
Nunca apt upgrade a secas. La documentación lo explica así: «apt full-upgrade can also remove existing packages to satisfy dependencies if needed, while apt upgrade cannot.» Como PVE es rolling release sobre Debian, un apt upgrade deja el stack a medias, con paquetes de versiones incompatibles entre sí. apt dist-upgrade sigue funcionando y es equivalente.
pveupgrade es el wrapper de Proxmox: actualiza y además avisa de si hace falta reiniciar y de si se instaló un kernel nuevo. pveversion -v lista las versiones de todos los paquetes del stack, y es lo primero que te van a pedir en cualquier hilo de soporte.
Si el nodo está en un cluster con HA, vacíalo antes de reiniciar:
ha-manager crm-command node-maintenance enable pve1
journalctl -u pve-ha-lrm -f | grep -i watchdog # esperar "watchdog closed (disabled)"
apt update && apt full-upgrade
reboot
ha-manager crm-command node-maintenance disable pve1
El modo mantenimiento manual no se desactiva solo: si te olvidas del último comando, el nodo se queda sin recibir recursos y nadie lo nota hasta el siguiente failover. Los detalles de HA y del watchdog están en el capítulo 11.
El upgrade mayor 8 a 9 paso a paso con pve8to9
Los prerrequisitos son innegociables: PVE 8.4 como mínimo con pve-manager 8.4.1 o superior; Ceph ya en Squid 19.2 si lo usas; Proxmox Backup Server coinstalado ya subido a PBS 4; consola fuera de banda funcionando (IKVM o IPMI, no solo SSH); al menos 5 GB libres en /, recomendado 10 o más; y backups probados de todas las VMs y contenedores. Probados, no solo hechos.
El script de comprobación es pve8to9, y su comportamiento está documentado literalmente: «This script only checks and reports things. By default, no changes to the system are made.» Se usa de forma iterativa: lo ejecutas, resuelves un warning, lo vuelves a ejecutar, hasta que salga limpio. También hay que ejecutarlo después del upgrade, porque cambia su salida según el estado del sistema.
pve8to9
pve8to9 --full
No existe pve9to10. PVE 10 no ha sido anunciado y el script vigente sigue siendo pve8to9, que viene en pve-manager desde 8.4 y permanece disponible tras el upgrade.
Secuencia completa en un nodo:
# 1. Vaciar el nodo
ha-manager crm-command node-maintenance enable pve1
# 2. Estar completamente al día en 8.4
apt update && apt dist-upgrade
pveversion # debe mostrar 8.4.1 o superior
ceph --version # si aplica: debe mostrar 19 (Squid)
# 3. Repositorios a Trixie y borrado de los .list antiguos
sed -i 's/bookworm/trixie/g' /etc/apt/sources.list
sed -i 's/bookworm/trixie/g' /etc/apt/sources.list.d/*.list
rm -f /etc/apt/sources.list.d/pve-enterprise.list
grep -r bookworm /etc/apt/ # debe no devolver nada
apt update && apt policy
# 4. El upgrade
apt update && apt dist-upgrade
# 5. Verificar y reiniciar
pve8to9
reboot
Si queda alguna referencia a bookworm, apt propondrá eliminar el paquete proxmox-ve: esa es la señal inequívoca de repos mal configurados. Sobre los prompts de conffiles, responde no a /etc/issue (cosmético, se autogenera) y a /etc/default/grub si tocaste la línea de kernel; sí a /etc/lvm/lvm.conf, /etc/ssh/sshd_config y /etc/chrony/chrony.conf si no los modificaste.
Reinicia aunque ya tuvieras el kernel nuevo instalado como paquete opcional: hace falta por compatibilidad de ABI y de compilador. Después:
pveversion -v
pvecm status
ha-manager status
journalctl -eu pve-ha-crm # ver la migración de HA groups a HA rules
apt modernize-sources
Y limpia la caché del navegador con CTRL+SHIFT+R, porque la GUI vieja contra la API nueva produce errores que parecen bugs y no lo son.
Orden en cluster y breaking changes que muerden
flowchart TD
A["Backups verificados de TODAS las VMs y CTs"] --> B["pve8to9 --full en TODOS los nodos"]
B --> C["Todo el cluster en PVE 8.4.1 o superior"]
C --> D["Ceph a Squid 19.2 si aplica"]
D --> E["Nodo 1: mantenimiento, repos, dist-upgrade y reboot"]
E --> F{"Nodo 1 sano?<br/>pvecm status y ha-manager status"}
F -->|No| G["Detener y diagnosticar<br/>NO seguir con mas nodos"]
F -->|Si| H["Nodo 2: mismo proceso"]
H --> I["Nodo 3 hasta nodo N"]
I --> J["Todo el cluster en 9.x"]
J --> K["HA groups migran automaticamente a HA rules"]
K --> L["apt modernize-sources y limpiar cache del navegador"]
Cuatro reglas de orden. Uno. Un nodo cada vez, nunca en paralelo. Dos. Un cluster mixto 8 y 9 está soportado temporalmente durante la ventana, pero no como estado permanente. Tres. La migración de una versión antigua a una nueva siempre funciona; la inversa puede fallar, así que no muevas guests de un nodo 9 a uno 8 durante la ventana. Cuatro. Los HA groups, deprecados desde 9.0, migran automáticamente a HA rules solo cuando todos los nodos han llegado a PVE 9.
| Cambio | Impacto | Mitigación |
|---|---|---|
| cgroup v1 eliminado | Contenedores con systemd anterior a 231, como CentOS 7 o Ubuntu 16.04, dejan de arrancar | Migrar el CT a una distro moderna o convertirlo en VM |
/tmp pasa a tmpfs | Usa hasta el 50% de la RAM y se vacía en cada reinicio | Revisar scripts que dejen datos en /tmp |
/etc/sysctl.conf ya no se honra | Tus ajustes de kernel dejan de aplicarse en silencio | Moverlos a /etc/sysctl.d/<NN>-<nombre>.conf |
| QEMU machine 10.0 o superior | Veeam Backup deja de ser compatible | Fijar la máquina a 9.2+pve1 o posponer el upgrade |
| Kernel nuevo | Algunas configuraciones de PCI passthrough se rompen | Pinnear el kernel anterior |
| Renombrado de interfaces de red | El nodo arranca sin red | Revisar /etc/network/interfaces; tener la consola IPMI abierta |
| Autoactivación de LVs desactivada | Problemas con storages LVM compartidos | Ejecutar /usr/share/pve-manager/migrations/pve-lvm-disable-autoactivation si pve8to9 lo sugiere |
| FRR en full-mesh de Ceph | El post-up ... restart frr.service provoca deadlock en el arranque | Sustituirlo por post-up /usr/bin/systemctl is-active --quiet frr.service && /usr/bin/systemctl restart frr.service || true antes del reboot |
Advertencia: no hay rollback nativo. La recomendación oficial es planificar con cuidado, probar primero en hardware que no sea de producción y tener backups válidos y verificados antes de empezar. Si el upgrade queda a medias, el primer intento de reparación es apt -f install con los repos ya apuntando a Trixie. Ceph durante el upgrade está en el capítulo 12, y la red del host en el capítulo 9.
proxmox-boot-tool y kernel pinning
Cuando el kernel nuevo rompe algo, la salida es arrancar el anterior. proxmox-boot-tool gestiona tanto systemd-boot (instalaciones con root en ZFS y ESP sincronizadas) como GRUB; proxmox-boot-tool status te dice cuál está en uso.
proxmox-boot-tool status # ESPs gestionadas y config de arranque
proxmox-boot-tool kernel list # kernels disponibles y marcados
proxmox-boot-tool kernel add <version>
proxmox-boot-tool kernel remove <version>
proxmox-boot-tool kernel pin <version>
proxmox-boot-tool kernel pin <version> --next-boot
proxmox-boot-tool kernel unpin
proxmox-boot-tool refresh # regenerar la config en todas las ESPs
El caso típico es passthrough roto tras el upgrade: miras qué hay con proxmox-boot-tool kernel list y uname -r, fijas el anterior con proxmox-boot-tool kernel pin 6.14.11-4-pve, aplicas con proxmox-boot-tool refresh y reinicias. Confirmas con uname -r y proxmox-boot-tool status, y cuando haya fix upstream deshaces con kernel unpin, refresh y otro reinicio.
La variante --next-boot es la que quieres mientras pruebas: fija el kernel solo para el próximo arranque, así que si el nodo no vuelve, el siguiente ciclo de energía lo devuelve al kernel por defecto. Con un servidor remoto sin IPMI, esa diferencia separa un susto de un viaje al datacenter. Y cualquier reinicio en un cluster con HA debe ir precedido de ha-manager crm-command node-maintenance enable <nodo> o de shutdown_policy=migrate.
Actualizaciones desatendidas: por qué no y qué hacer en su lugar
La postura de Proxmox y de la comunidad es clara: unattended-upgrades completo no se recomienda en un cluster PVE de producción, al menos no sin una cantidad considerable de pruebas. Las razones son concretas. Un Automatic-Reboot en true reinicia el nodo sin avisar, lo que con HA activo significa un failover imprevisto y guests sin onboot que no vuelven. Y con todos los orígenes habilitados, puedes acabar saltando de versión mayor de PVE sin haberlo decidido.
Si aun así lo necesitas por política de la organización, limítalo al origen de seguridad de Debian y excluye el stack de Proxmox. En /etc/apt/apt.conf.d/50unattended-upgrades:
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
};
Unattended-Upgrade::Package-Blacklist {
"proxmox-ve";
"proxmox-kernel";
"pve-kernel";
"pve-manager";
"qemu-server";
"pve-qemu-kvm";
"pve-container";
"libpve-.*";
"ceph.*";
};
Unattended-Upgrade::Automatic-Reboot "false";
Actívalo en /etc/apt/apt.conf.d/20auto-upgrades con APT::Periodic::Update-Package-Lists "1"; y APT::Periodic::Unattended-Upgrade "1";, y verifica antes de confiar con unattended-upgrade --dry-run --debug, journalctl -u unattended-upgrades y /var/log/unattended-upgrades/unattended-upgrades.log.
La alternativa recomendada es más simple: deja el evento package-updates activo con notify: package-updates=auto, recibe el aviso y aplica en una ventana de mantenimiento con el nodo vaciado. Más trabajo humano y muchísimo menos riesgo.
Runbook: quorum perdido
Síntoma: /etc/pve responde Read-only file system, no puedes arrancar ni crear guests, la GUI falla al guardar cualquier cosa. Los guests que ya estaban corriendo siguen corriendo: lo que se bloquea es la escritura de configuración.
flowchart TD
S["Sintoma: /etc/pve en solo lectura"] --> D["pvecm status<br/>mirar el campo Quorate"]
D --> Q{"Quorate: No?"}
Q -->|No| OTRO["systemctl status pve-cluster<br/>y journalctl -u pve-cluster"]
Q -->|Si| N{"Los nodos caidos<br/>son recuperables?"}
N -->|Si| REC["Recuperarlos y esperar<br/>El quorum vuelve solo"]
N -->|No| E{"corosync arranca?"}
E -->|Si| EXP["pvecm expected 1<br/>SOLO emergencia<br/>riesgo de split-brain"]
E -->|No| PMX["systemctl stop pve-cluster<br/>pmxcfs -l<br/>arreglar corosync.conf"]
PMX --> BACK["killall pmxcfs<br/>systemctl start pve-cluster"]
EXP --> GUEST["Recuperar guests del nodo muerto:<br/>mover los .conf con el nodo origen APAGADO"]
BACK --> GUEST
pvecm status # mirar "Quorate:" y "Expected votes"
pvecm nodes
corosync-cfgtool -s # estado de cada link
corosync-quorumtool -s
journalctl -b -u corosync -u pve-cluster
Forzar quorum en un nodo aislado, cuando no hay otra salida, es pvecm expected 1. Advertencia: la documentación es tajante: «Avoid cluster configuration changes (adding/removing nodes, storage, guests) when expected votes are set. Only use forced quorum for emergencies.» El riesgo real es que los otros nodos vuelvan mientras expected está bajado y acabes con dos particiones escribiendo /etc/pve. El valor no es persistente: se pierde al reiniciar corosync.
Si corosync ni siquiera arranca porque su configuración está rota, monta pmxcfs en modo local:
systemctl stop pve-cluster
pmxcfs -l # /etc/pve local y escribible, sin cluster
# ...corregir /etc/pve/corosync.conf, subiendo config_version...
killall pmxcfs
systemctl start pve-cluster
Y para recuperar los guests de un nodo que no va a volver:
mv /etc/pve/nodes/A/qemu-server/100.conf /etc/pve/nodes/B/qemu-server/100.conf
mv /etc/pve/nodes/A/lxc/200.conf /etc/pve/nodes/B/lxc/200.conf
qm start 100
pct start 200
Gotcha crítico: el nodo origen tiene que estar completamente apagado. Si vuelve a arrancar con el guest todavía asignado a él y el storage es compartido, tendrás la misma imagen montada dos veces y corrupción garantizada. Con replicación ZFS (capítulo 8), asume además la pérdida de todo lo escrito desde la última réplica.
Caso especial que no es emergencia: el cold start. Tras un corte de luz general ningún nodo tiene quorum; el servicio pve-guests arranca y espera a tenerlo antes de levantar los guests con onboot. No fuerces pvecm expected 1 aquí, simplemente espera a que arranquen todos.
Runbook: recurso HA en error, storage lleno y backup fallido
Recurso HA en estado error
Un servicio HA entra en error cuando agotó max_restart reintentos en el mismo nodo y max_relocate reubicaciones en otros. Requiere intervención manual: no sale solo. El ciclo de recuperación pasa obligatoriamente por disabled, que es el estado que limpia el flag:
ha-manager status --verbose
journalctl -u pve-ha-lrm -b
ha-manager set vm:100 --state disabled
# ...arreglar la causa raíz: storage, red, config del guest...
ha-manager set vm:100 --state started
Si ha-manager status muestra watchdog: standby, no hay CRM master: o perdiste quorum, o alguien desarmó HA. En PVE 9.2 el rearme es ha-manager crm-command arm-ha.
Storage lleno
pvesm status
df --human -T
zpool list -o name,size,alloc,free,frag,cap,health
lvs; vgs; pvs
journalctl --disk-usage
Los tres culpables habituales, por frecuencia: backups sin política de retención efectiva, snapshots olvidados y el journal creciendo sin límite. Para lo primero, revisa prune-backups en el job y en el storage; si el job lo define, gana sobre el del storage, y si ninguno lo define no se borra nada. Los snapshots de replicación se llaman __replicate_<jobid>_<timestamp>__ y se listan con zfs list -t snapshot | grep __replicate_. Para el journal, journalctl --vacuum-time=14d.
Gotcha de ZFS: un pool por encima del 80% degrada el rendimiento de escritura de forma notable y, cerca del 100%, puede impedir incluso borrar ficheros. La alerta de storage no debería estar en el 95% sino en el 85%, y con ZFS conviene bajarla más.
Backup fallido
Lo primero es leer el log de la tarea, no el journal:
pvesh get /nodes/$(hostname)/tasks --limit 50
cat /var/log/pve/tasks/index
ls /var/lib/vz/dump/*.log # storages de tipo fichero
| Mensaje | Qué hacer |
|---|---|
VM is locked (backup) | Un backup anterior murió sin soltar el lock. Verificar con ps aux | grep vzdump y luego qm unlock <vmid> o pct unlock <vmid> |
| El job no se ejecutó nunca | systemctl status pvescheduler y revisar el campo node en /etc/pve/jobs.cfg |
| El schedule no dispara cuando esperas | systemd-analyze calendar --iterations=5 '<tu schedule>' |
storage 'pbs01' is not online | Red, fingerprint, ACL o reloj: pvesm status --storage pbs01 y timedatectl en ambos extremos |
Todo el detalle de vzdump, retención y PBS está en el capítulo 13.
Runbook: nodo caído, red caída tras un reboot y consola inaccesible
Nodo caído. Con HA activo la secuencia es automática y tiene tiempos conocidos: el LRM deja de refrescar el watchdog, el CRM marca el nodo offline a los ~60 s, watchdog-mux deja de tocar /dev/watchdog y el servidor se resetea unos 10 s después. El total hasta que los guests arrancan en otro nodo es «about 2 minutes».
# desde otro nodo, mientras ocurre
watch -n2 ha-manager status
journalctl -f -u pve-ha-crm
# en el nodo que volvió, para confirmar que fue el watchdog
journalctl -k | grep -i watchdog
last -x reboot
wdctl
Si el nodo se reinicia solo repetidamente, la causa casi siempre es la red de corosync, no el hardware: búscalo con journalctl -u corosync | grep -i retransmit.
Red caída tras un reboot. Es el escenario que justifica exigir IPMI antes de un upgrade mayor: el kernel nuevo puede renombrar interfaces y dejar /etc/network/interfaces apuntando a nombres que ya no existen.
ip -br link # nombres reales que ve el kernel ahora
cat /etc/network/interfaces
journalctl -b -u networking
Con los nombres reales delante corriges el fichero y aplicas. Ojo con /etc/network/interfaces.new: si hay cambios pendientes desde la GUI viven ahí, y los aplica pvenetcommit en el arranque.
Consola web inaccesible. Si la GUI no responde en el 8006, revisa los daemons; si responde pero un nodo aparece gris, suele ser pvestatd caído o certificados desincronizados:
systemctl status pveproxy pvedaemon pvestatd pve-cluster
journalctl -u pveproxy -n 200 --no-pager
pvecm updatecerts --force
systemctl restart pvedaemon pveproxy pvestatd
Gotcha: systemctl restart pveproxy corta las consolas y shells abiertas, incluidas las de los guests. En horario de trabajo, avisa antes. La anatomía de estos servicios está en el capítulo 2.
Checklist de puesta en producción
-
pvereportde cada nodo guardado fuera del cluster, como línea base. - Correo de
root@pamapuntando a un buzón que alguien lee, ymail-to-rootprobado. - Target de guardia (
gotifyowebhook) con matcher paratype=fencing,replicationy severidaderror. - Matcher explícito para
type=system-mail, para no perder las alertas desmartd. -
notify: package-updates=autoendatacenter.cfgy nadie conunattended-upgradestocando paquetes de Proxmox. - Metric server en
status.cfg, o exporter de Prometheus con usuarioPVEAuditory tokenprivsep 0. - Alertas mínimas: nodo caído 2 min,
pve_ha_stateenerror, storage sobre el 85% durante 15 min. -
pve_not_backed_up_inforevisada: cero guests sin cobertura de backup. - Retención de backups definida en un solo sitio y verificada con una restauración real.
- Consola IPMI o IKVM accesible y probada desde fuera de la red de gestión.
- Watchdog por hardware en
/etc/default/pve-ha-manager, osoftdogasumido conscientemente. - Procedimiento de mantenimiento escrito:
node-maintenance enable, actualizar, reiniciar,disable, verificar. - Alguien que no seas tú ha ejecutado el runbook de quorum perdido en un laboratorio.
Errores comunes y diagnóstico
| Síntoma | Causa probable | Diagnóstico | Acción |
|---|---|---|---|
/etc/pve en solo lectura | Pérdida de quorum | pvecm status y mirar Quorate: | Recuperar nodos; en emergencia pvecm expected 1 |
| Nodo gris en la GUI pero responde por SSH | pvestatd caído o certificados desincronizados | systemctl status pvestatd pveproxy | pvecm updatecerts --force y reiniciar pvedaemon pveproxy pvestatd |
| El cluster parpadea, nodos que entran y salen | corosync compartiendo NIC con storage o migración | journalctl -u corosync | grep -i retransmit | Red dedicada de corosync o un segundo link |
| Nodos que se reinician solos con HA activo | El watchdog dispara por pérdida de quorum | journalctl -k | grep -i watchdog, last -x reboot | Arreglar la red de corosync, añadir link redundante |
QDevice en estado NA | TCP 5403 bloqueado hacia el qnetd | nc -zv <ip-del-qnetd> 5403 | Abrir el puerto y revisar el firewall del host externo |
Recurso HA atascado en error | Se agotaron max_restart y max_relocate | ha-manager status --verbose | Ciclo --state disabled y luego --state started |
ha-manager status muestra watchdog: standby | No hay CRM master o HA está desarmado | pvecm status, ha-manager status --verbose | Recuperar quorum o ha-manager crm-command arm-ha |
Job de replicación en error | Espacio, red o storage ID inexistente en destino | pvesr status, journalctl -u pvesr | Corregir y pvesr run --id <job>; reintenta solo cada 30 min |
| Los correos de PVE no llegan | Target sendmail sin MTA configurado | journalctl -u postfix, mailq | Configurar el relay de Postfix o cambiar a target smtp |
| Nada llega al servidor de métricas y no hay errores | Metric server por UDP, que no confirma entrega | tcpdump en el 2003 u 8089 del destino | Verificar el listener; con InfluxDB v1, el bloque [[udp]] |
| El exporter de Prometheus devuelve 401 | Token con privsep 1 sin ACL propio | pveum user token permissions prometheus@pve monitoring | Usar --privsep 0 o replicar el ACL sobre el token |
| Estado de paquetes roto tras actualizar | Se usó apt upgrade en vez de apt full-upgrade | apt policy | apt full-upgrade y apt --fix-broken install |
apt dist-upgrade propone eliminar proxmox-ve | Quedan repos apuntando a bookworm | grep -r bookworm /etc/apt/ | Corregir los .sources y apt update antes de reintentar |
| Tras el upgrade la red no levanta | El kernel nuevo renombró las interfaces | Consola IPMI, ip -br link | Ajustar /etc/network/interfaces con los nombres reales |
| Tras el upgrade el PCI passthrough no funciona | Regresión del kernel nuevo | dmesg | grep -i vfio | proxmox-boot-tool kernel pin <kernel-anterior> y refresh |
| Un contenedor antiguo no arranca tras el upgrade | cgroup v1 eliminado en PVE 9 | pct start <id> y leer el error | Migrar el CT a una distro con systemd 231 o superior |
| Ajustes de kernel que dejaron de aplicarse | /etc/sysctl.conf ya no se honra en PVE 9 | sysctl -a | grep <clave> | Mover el ajuste a /etc/sysctl.d/<NN>-<nombre>.conf |
| El job de backup no se ejecutó | pvescheduler parado o campo node equivocado | systemctl status pvescheduler, cat /etc/pve/jobs.cfg | Arrancar el servicio o corregir el job |
| El disco del nodo se llena sin motivo aparente | Journal sin límite | journalctl --disk-usage | journalctl --vacuum-time=14d |
Lo que queda operativo
Con este capítulo el cluster deja de depender de que alguien mire la GUI. Los eventos de fencing, replicación y backup salen hacia un canal de guardia con matchers que distinguen horario laboral de madrugada; las métricas de CPU, memoria, storage, estado HA y cobertura de backup se acumulan en Graphite, InfluxDB o Prometheus; y tienes un pvereport de cada nodo como línea base contra la que comparar cuando algo cambie.
Del lado del mantenimiento, el ciclo menor es apt full-upgrade con el nodo en modo mantenimiento, nunca apt upgrade. El mayor es pve8to9 hasta que salga limpio, un nodo cada vez, sin migrar hacia atrás, con IPMI a mano y proxmox-boot-tool kernel pin --next-boot como marcha atrás si el kernel nuevo muerde. Y el runbook cubre las emergencias reales con el comando concreto de cada una, en vez de la búsqueda a ciegas de las tres de la mañana.
Queda un escenario que no es operación diaria sino proyecto: traer cargas que hoy corren en otro hipervisor. En el capítulo 16 se aborda la migración desde VMware, Hyper-V y VirtualBox, con el importador de ESXi, la conversión de discos y los drivers que hay que instalar antes de apagar la máquina original.