Whitepaper técnico — PodHeitor Proxmox para Bacula

Arquitectura interna, modos de operación, replicación DR estilo Veeam, instant recovery vía NBD, conversión cross-hypervisor (vSphere/Hyper-V → PVE) y modelo de seguridad con fingerprint pinning TLS.

Documento técnico complementario a la página del plugin PodHeitor Proxmox.

1. Problema: Bacula stock no ve a Proxmox VE

Bacula Community en su forma stock no tiene awareness alguna de hipervisores. Hacer backup de VMs Proxmox sin plugin se reduce típicamente a una de tres opciones, todas malas:

  • Backup del filesystem del host — captura archivos .qcow2/.raw en estado inconsistente, sin quiesce, sin CBT. La restauración suele bootear con corrupción.
  • vzdump + dump en directorio, luego Bacula — duplica el storage (1× dataset PVE + 1× dump vzdump), y cada incremental retransmite el dump completo porque vzdump no emite delta nativo.
  • Plugin PVE de Bacula Enterprise — existe, pero a precio enterprise, sin replicación DR cross-site ni conversión cross-hypervisor.

El PodHeitor Proxmox Plugin cierra las tres brechas en un solo binario: backup VM-aware con CBT, replicación DR estilo Veeam entre nodos PVE, y restore cross-hypervisor (vSphere/Hyper-V → PVE).

2. Modelo arquitectural

El plugin sigue el patrón PodHeitor de cdylib + backend standalone, comunicándose vía PTCOMM (length-tagged framing en stdin/stdout). La motivación es triple:

  1. Aislamiento de crash. Un panic en el engine NBD o QMP mata al backend, no al bacula-fd. El cdylib observa EOF en el pipe, reporta el job como fallido, y el FD sigue atendiendo otros jobs.
  2. Libertad de paralelismo. El backend puede abrir conexiones PVE REST + NBD + QMP en paralelo sin violar el contrato Bacula de «una hilera por bpContext«.
  3. License firewall. Desde v2.0.0, ningún source AGPLv3 de Bacula se vincula estáticamente.

2.1 Topología de procesos

bacula-fd  →  podheitor-proxmox-fd.so (cdylib ~600 LoC)  →  podheitor-proxmox-backend (Rust ~4500 LoC)
                                                                ├─ PVE REST API (HTTPS/TLS pinned)
                                                                ├─ NBD Client (disk I/O)
                                                                └─ QMP Client (snapshot + dirty bitmap)

El backend hospeda cinco engines: BackupEngine, RestoreEngine, ReplicationSender, ReplicationReceiver e InstantRecoveryEngine. Seleccionado vía el parámetro mode= del plugin string.

3. Modos de operación

Mode Función Engine
backup Backup VM-aware (Full/Inc/Diff) con CBT vía QEMU dirty bitmaps BackupEngine
seed Sync inicial completa, auto-provisiona VM réplica en el DR ReplicationSender
incremental (DR) Replicación CBT incremental — sólo dirty-bitmap deltas ReplicationSender
receiver Daemon receiver en el DR target — escucha TCP dr_port (9190) ReplicationReceiver
failover-exec Boot de la réplica en el DR (planned failover) ReplicationSender
failback-pre Retorno de la réplica a standby ReplicationSender

4. CBT vía QEMU dirty bitmaps

En lugar de reenviar 100 GB cada noche, el plugin instala un dirty bitmap persistente en el QEMU del nodo PVE vía QMP block-dirty-bitmap-add. Cada incremental:

  1. Toma snapshot consistente (con quiesce=yes vía QEMU Guest Agent cuando esté disponible).
  2. Lee sólo bloques marcados dirty desde el último backup, vía NBD BLOCK_STATUS.
  3. Stream-ea esos bloques por PTCOMM con offset preservado.
  4. Resetea el bitmap tras confirmar que el backup terminó OK.

VM de 100 GB con 2 GB modificados → sólo 2 GB transferidos. Sin CBT (Bacula stock + filesystem), serían 100 GB cada noche.

5. Replicación DR estilo Veeam (v1.1.0)

El plugin implementa un pipeline DR cross-node PVE-1 → PVE-2 con semántica cercana a Veeam Replication, pero dirigido enteramente desde el Bacula Director (FileSet/Job).

5.1 Fases

  • Seed (mode=seed): sync inicial full. Auto-provisiona la VM réplica en el DR target (cores, RAM, NICs, SCSI controllers, storage spec).
  • Incremental continuo (mode=incremental): sólo deltas dirty-bitmap.
  • Restore points: snapshots auto-rotados en la réplica (default 7 puntos).
  • Verify (verify_sample_blocks=N): hash FNV-1a-64 de N bloques de muestra, comparado source ↔ DR. Mismatch = job fail (no OK silencioso).
  • Planned failover (mode=failover-exec): una ejecución bootea la réplica en el DR.
  • Planned failback (mode=failback-pre): retorna réplica a standby.

5.2 Autenticación del canal DR

Método Parámetro Cuándo usar
Token PSK (HMAC) dr_auth_token Default; setup rápido entre dos sitios controlados
TLS Mutual Auth dr_auth_cert + dr_auth_key Compliance / multi-tenant; rustls + PEM
Ambos los 3 Defense-in-depth

5.3 Daemon receiver standalone

El DR target no necesita ejecutar bacula-fd. El paquete instala la plantilla systemd [email protected]; el receiver escucha en dr_port (default 9190) y acepta streams autenticados, escribiendo discos vía NBD al storage dr_storage del PVE local. Esto reduce superficie de ataque en el DR y simplifica air-gap parcial.

6. Instant Recovery vía NBD overlay

La recuperación tradicional de una VM de 500 GB puede tomar horas de escritura a disco antes de que vuelva el servicio. El InstantRecoveryEngine elude esto:

  1. Bootea la VM en PVE apuntando a un disco virtual servido por NBD desde el backend, leyendo directo del stream Bacula restore.
  2. Los overlay writes (ir_overlay_storage) capturan cambios del guest en storage local rápido.
  3. En background, con ir_auto_migrate=yes, el disco se migra al storage final (ir_target_storage) sin downtime.

RTO típico cae de horas a minutos. Parámetros clave: ir_nbd_bind, ir_nbd_port, ir_overlay_storage, ir_target_storage, ir_timeout (default 3600s).

7. Conversión cross-hypervisor

El plugin lee backups producidos por los plugins-hermanos PodHeitor vSphere y PodHeitor Hyper-V y los restaura directamente en PVE — sin reconversión manual:

Origen Formato disco origen Conversión
VMware vSphere VMDK VMDK → qcow2/raw vía librería interna (no shell-out)
Hyper-V VHDX VHDX → qcow2/raw vía librería interna

Validado en laboratorio: Job 805 (Hyper-V → PVE) y Job 865 (VMware → PVE) restauraron VMs con boot exitoso en el PVE de destino.

8. Modelo de seguridad

8.1 TLS Fingerprint Pinning (PVE API)

Bacula stock confía típicamente en el trust store del sistema; los certs PVE son self-signed por default. El plugin obliga SHA-256 fingerprint pinning explícito vía pve_fingerprint=AA:BB:CC:..., con pve_insecure=no como default. Para obtener el fingerprint:

openssl s_client -connect pve-host:8006 </dev/null 2>/dev/null 
  | openssl x509 -noout -fingerprint -sha256 
  | sed 's/SHA256 Fingerprint=//'

Mismatch (cert rotado, MITM, host cambiado) → job aborta con ERROR: TLS fingerprint mismatch. Expected AA:BB:... got CC:DD:....

8.2 Credenciales

  • Contraseñas y tokens pasados vía FileSet plugin string (no almacenados en disco por el plugin).
  • bacula-dir.conf debe tener perms 600, owner bacula.
  • Para producción: integrar con vault externo (HashiCorp Vault, AWS Secrets Manager) y generar plugin string vía templating del Director.

9. Validación en laboratorio

Métrica Resultado
Bacula Jobs ejecutados (secuencial) 1.290+ JobIDs
Backup/Restore Full/Inc/Diff (same-host + cross-host) OK
Cross-hypervisor Hyper-V → PVE (Job 805) OK
Cross-hypervisor VMware → PVE (Job 865) OK
Replication seed 100 GB 107 GB en 93,7 min, 19 MB/s sostenido
Replication incrementales 15 / 15 back-to-back, promedio 14,5 s
Integrity verify 150 sample blocks, 0 mismatches
mTLS DR channel Cert v3 con IP SAN — handshake + integrity OK
Planned failover + failback One-command, exit 0
Bacula-driven JobId 3448 Termination=OK

Entorno: Director Oracle Linux 9.6 + Bacula 15.0.3; PVE Site A Debian 12 + PVE 8.4.18; PVE Site B Debian 13 + PVE 9.x.

10. Anti-patterns documentados

  • No deshabilites pve_fingerprint en producción. Setear pve_insecure=yes acepta cualquier cert presentado por el PVE host — incluyendo certs MITM. Sólo en laboratorio.
  • No corras quiesce=yes sin QEMU Guest Agent instalado y activo en la VM. Sin agente, el plugin cae automáticamente a crash-consistent, pero el operador debe saber que esto pasó (verifica el log del job).
  • No corras receiver y sender en el mismo host PVE. Abren el mismo dr_port y el segundo falla al bind-ear.
  • No confundas backup_type=incremental (Bacula level) con mode=incremental (DR mode). El primero es nivel de backup del FileSet; el segundo es modo del engine de replicación. Son ortogonales.

11. Postura de licencia

Desde v2.0.0, el plugin se distribuye bajo LicenseRef-PodHeitor-Proprietary. Ningún source AGPLv3 de Bacula se vincula estáticamente al .so. El cdylib se construye sobre el crate puro PodHeitor plugin-proxmox en el workspace PodHeitor Rust cdylib, con bindings extern "C" independientes vía crate bacula-fd-abi.

¿Listo para evaluar?

Trial gratuito de 30 días para flotas Proxmox VE en producción. Garantizamos al menos 50% de descuento vs Bacula Enterprise, Veeam o Commvault, con más funcionalidades incluidas (replicación DR, instant recovery, conversión cross-hypervisor).

Heitor Faria — Fundador, PodHeitor International
[email protected]
☎ +1 (789) 726-1749 · +55 (61) 98268-4220 (WhatsApp)
🔗 Página del plugin PodHeitor Proxmox

Disponível em: pt-brPortuguês (Portugués, Brasil)enEnglish (Inglés)esEspañol

Deja una respuesta