Sistema de backup pull para Proxmox VE
Sistema de backup basado en snapshots LVM para maquinas virtuales en Proxmox VE. Utiliza una arquitectura pull donde el servidor de storage inicia las conexiones SSH hacia Proxmox y tira de los datos. El servidor Proxmox nunca tiene acceso de escritura al storage.
Motivacion
El modelo tradicional push (Proxmox envia al storage) tiene un problema de seguridad critico: si el servidor Proxmox es comprometido, el atacante tiene credenciales SSH con acceso de escritura al storage y puede destruir todos los backups.
Con el modelo pull:
- Proxmox no conoce las credenciales del storage
- Proxmox no puede escribir ni borrar en el storage
- Solo el storage decide cuando y que se respalda
- Un Proxmox comprometido no puede destruir los backups existentes
Arquitectura
Storage Server Proxmox Server
| |
|-- ssh "list" ------------------------>| (inventario de VMs)
|<-- CSV: vmid,status,vg,lv,size -------|
| |
|-- ssh "snapshot vg lv 10 1" --------->| (crea snapshot LVM)
|<------------ dd|pigz stream ----------| (stream comprimido)
|-- guarda en incoming/ |
|-- ssh "cleanup vg snap-name" -------->| (elimina snapshot)
|-- mv incoming/ -> snapshots/ | (movimiento atomico)
|-- prune copias antiguas |
Componentes
| Componente | Ubicacion | Funcion |
|---|---|---|
proxmox-backup-helper.sh |
Proxmox: /usr/local/bin/ |
Manejador SSH restringido con 8 comandos |
proxmox-pull-backup.sh |
Storage: /root/backups/pull/ |
Orquestador de backups |
proxmox-pull-restore.py |
Storage: /root/backups/pull/ |
Herramienta de restauracion (Python 3.6+) |
backup-access-shell.sh |
Storage: /usr/local/bin/ |
Shell de solo lectura para restore |
install-pull-backup.sh |
Storage: /root/backups/pull/ |
Instalador automatizado |
Requisitos previos
- Proxmox: Version 7.x o 8.x con almacenamiento LVM
- Storage: Servidor Linux con espacio suficiente
- Ambos servidores:
pigzinstalado (apt-get install pigz) - Storage: Python 3.6+ (solo stdlib, sin dependencias externas)
- Red: Acceso SSH desde storage hacia Proxmox
Instalacion
Paso 1: Instalar pigz en ambos servidores
apt-get install -y pigz
Paso 2: Subir los scripts al storage
# Desde la maquina local o mediante rsync
rsync -avz proxmox-backup/pull/ storage:/root/backups/pull/
chmod +x /root/backups/pull/*.sh
Paso 3: Ejecutar el instalador
Desde el servidor de storage:
cd /root/backups/pull/
./install-pull-backup.sh \
--proxmox-host prox06.example.com \
--proxmox-port 22 \
--proxmox-name prox06 \
--storage-dir /srv/storage/backups_kvm
El instalador ejecuta 8 pasos automaticos:
-
Genera clave SSH ed25519 en
/root/backups/pull/keys/ -
Copia el helper a Proxmox en
/usr/local/bin/ -
Configura
authorized_keyscon restriccioncommand= -
Crea directorios
incoming/ysnapshots/ -
Auto-detecta discos LVM en Proxmox
-
Anade el host al inventario
proxmox_hosts.cfg -
Instala
backup-access-shell.shen storage -
Ejecuta tests de conectividad
Paso 4: Crear la configuracion principal
cp /root/backups/pull/config/pull-backup.cfg.example \
/root/backups/pull/pull-backup.cfg
Editar los valores segun el entorno:
# Directorios
incoming_dir="/srv/storage/backups_kvm/incoming"
final_dir="/srv/storage/backups_kvm/snapshots"
# Retencion
numcopias=3
# Rendimiento
min_speed_mbs=150 # MB/s minimos esperados
min_free_gb=1024 # GB libres requeridos
ssh_timeout=30 # Timeout SSH en segundos
# Notificaciones (solo en errores)
correo="admin@example.com"
Paso 5: Revisar el inventario de discos
cat /root/backups/pull/hosts/disks_prox06.txt
Comentar con # los discos que no se quieran respaldar.
Paso 6: Configurar cron
# Backup diario a las 3:00 AM
echo "0 3 * * * /root/backups/pull/proxmox-pull-backup.sh" | crontab -
Uso diario
Ejecutar backup manual
# Dry-run (no ejecuta nada, muestra lo que haria)
./proxmox-pull-backup.sh --host prox06 --verbose --dry-run
# Backup real
./proxmox-pull-backup.sh --host prox06 --verbose
# Backup de todos los hosts configurados
./proxmox-pull-backup.sh --verbose
Listar backups disponibles
python3 proxmox-pull-restore.py --config pull-backup.cfg list
# Filtrar por host
python3 proxmox-pull-restore.py --config pull-backup.cfg list --host prox06
# Filtrar por VM
python3 proxmox-pull-restore.py --config pull-backup.cfg list --vm 316
Restaurar una VM
Escenario A: la VM existe y esta parada (sobreescribe los discos):
python3 proxmox-pull-restore.py --config pull-backup.cfg \
restore --host prox06 --vm 316 --data-only
Escenario B: la VM no existe (crea LV, config y restaura datos):
python3 proxmox-pull-restore.py --config pull-backup.cfg \
restore --host prox06 --vm 316 --from-scratch
Restaurar desde una fecha concreta:
python3 proxmox-pull-restore.py --config pull-backup.cfg \
restore --host prox06 --vm 316 --date 2026-02-08
Restaurar varias VMs en paralelo:
python3 proxmox-pull-restore.py --config pull-backup.cfg \
restore --host prox06 --vm 316 900 --workers 2
IMPORTANTE: el argumento --config va ANTES del subcomando (list, restore).
Modelo de seguridad
Lado Proxmox
El helper (proxmox-backup-helper.sh) se ejecuta exclusivamente via la restriccion command= en authorized_keys. Esto significa que cualquier conexion SSH con esa clave ejecuta el helper, ignorando el comando solicitado por el cliente.
El helper valida cada argumento con expresiones regulares estrictas:
- Volume groups: solo alfanumericos, guiones y guiones bajos
- Logical volumes: solo patron
vm-\d+-disk-\d+ - Snapshot names: solo patron
snap-\d+-disk\d+ - VMIDs: solo numeros entre 100 y 999999
- Snapshot porcentaje: solo numeros entre 1 y 100
Comandos no reconocidos se rechazan con error. Todas las operaciones se registran en syslog.
Lado Storage
El storage inicia todas las conexiones. Proxmox nunca se conecta al storage para los backups. El unico acceso inverso (Proxmox hacia storage) es para restore, y se canaliza a traves de backup-access-shell.sh que solo permite operaciones de lectura (pigz -dc, ls, test, cat, du) y valida rutas con readlink -f contra symlinks maliciosos.
Estructura de ficheros
En el servidor de storage
/root/backups/pull/
proxmox-pull-backup.sh # Orquestador
proxmox-pull-restore.py # Herramienta de restore
backup-access-shell.sh # Shell read-only
install-pull-backup.sh # Instalador
pull-backup.cfg # Configuracion principal
hosts/
proxmox_hosts.cfg # Inventario de hosts
disks_prox06.txt # Discos de prox06
disks_prox07.txt # Discos de prox07
keys/
id_pull_backup # Clave privada SSH
id_pull_backup.pub # Clave publica SSH
logs/
pull-backup-YYYY-MM-DD.log # Log diario
En el servidor Proxmox
/usr/local/bin/proxmox-backup-helper.sh # Helper restringido
/root/.ssh/authorized_keys # Clave con command=
Directorio de backups
/srv/storage/backups_kvm/
incoming/ # Transferencias en curso
snapshots/
kvm-316-disk0.2026-02-08-03-00-12.gz # Disco comprimido
kvm-316.conf.2026-02-08-03-00-12 # Config de la VM
Nomenclatura de ficheros
kvm-{vmid}-disk{N}.{YYYY-MM-DD-HH-MM-SS}.gz # Discos
kvm-{vmid}.conf.{YYYY-MM-DD-HH-MM-SS} # Configuraciones
Comandos del helper
El helper acepta 8 comandos, todos validados con regex:
| Comando | Argumentos | Funcion |
|---|---|---|
list |
(ninguno) | Lista todas las VMs con discos LVM |
list-snapshots |
(ninguno) | Lista snapshots activos (deteccion de huerfanos) |
snapshot |
vg lv porcentaje [force] |
Crea snapshot y envia stream comprimido |
cleanup |
vg snap_name |
Elimina un snapshot LVM |
config |
vmid |
Devuelve el fichero de configuracion de la VM |
restore |
vg lv |
Recibe stream comprimido y lo escribe al disco |
create-disk |
vg nombre size_gb |
Crea un nuevo volumen logico |
restore-config |
vmid |
Recibe y escribe la config de la VM |
Funcionalidades avanzadas
Timeout dinamico
El timeout de cada transferencia se calcula automaticamente segun el tamano del disco:
timeout = (tamano_disco_gb * 1024) / min_speed_mbs
Con un minimo de 300 segundos. Para un disco de 620GB a 150 MB/s el timeout es ~70 minutos.
Deteccion de snapshots huerfanos
Antes de procesar cada host, el orquestador ejecuta list-snapshots y compara con los snapshots esperados. Los huerfanos (de ejecuciones previas fallidas) se limpian automaticamente si force_orphan_cleanup=1.
Estimacion de tamano
Si existe un backup previo del mismo disco, se usa su tamano como estimacion. Si no, se estima el 50% del tamano raw del disco. Esto se usa para la verificacion de espacio libre.
Reintentos
Si un backup falla, se reintenta una vez tras 10 minutos de espera. Solo se reporta error si el segundo intento tambien falla.
Pruning automatico
Tras completar los backups, se mantienen solo las ultimas numcopias copias de cada disco. Las configs asociadas a backups eliminados tambien se limpian.
Rendimiento validado
Datos reales del staging (avanza02 + stor01avanzait, febrero 2026):
| Operacion | Disco | Tiempo | Velocidad | Resultado |
|---|---|---|---|---|
| Backup | 5GB | 11s | 6 MB/s | 73MB comprimido |
| Restore data-only | 5GB | 10s | 7 MB/s | OK |
| Restore from-scratch | 5GB | 9s | 7 MB/s | LV + config + datos |
| Deteccion huerfanos | -- | instantaneo | -- | Auto-limpieza |
| Pruning 4 a 3 copias | -- | <1s | -- | Retencion exacta |
Troubleshooting
El backup genera un fichero .gz invalido
Causa: Los comandos lvcreate/lvremove escriben mensajes a stdout que contaminan el stream gzip.
Solucion: Verificar que el helper redirige stdout de lvm a stderr (>&2). Actualizar el helper a v1.1.0 o superior.
Error "integer expression expected" en el installer
Causa: grep -c via SSH devuelve multiples lineas (0\n0), bash no puede comparar.
Solucion: Bug menor conocido, no afecta a la instalacion. El paso 3 completa correctamente.
El installer falla en el test de restriccion
Causa: set -euo pipefail captura el exit code 1 del helper cuando rechaza un comando no autorizado.
Solucion: Bug menor conocido. La restriccion funciona correctamente. Verificar manualmente con:
ssh -i keys/id_pull_backup proxmox "ls /"
# Debe devolver: ERROR: Unknown command: ls
El restore dice "pigz: unrecognized format"
Causa: El fichero .gz esta corrupto (ver primer problema).
Solucion: Rehacer el backup con el helper corregido.
Timeout en backups de discos grandes
Causa: El timeout dinamico depende de min_speed_mbs en la config.
Solucion: Reducir min_speed_mbs si la red es lenta. Por ejemplo, para 50 MB/s reales:
min_speed_mbs=40 # Dejar margen del 20%
Repositorio
El codigo fuente esta en el repositorio utilidades bajo proxmox-backup/pull/.
Documentacion completa: proxmox-backup/README.md y proxmox-backup/docs/SPEC-pull-backup-architecture.md.
Ultima actualizacion
- Fecha: 2026-02-08
- Version: 1.1.0
- Autor: Abkrim