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/.rawen 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× dumpvzdump), y cada incremental retransmite el dump completo porquevzdumpno 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:
- 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. - Libertad de paralelismo. El backend puede abrir conexiones PVE REST + NBD + QMP en paralelo sin violar el contrato Bacula de «una hilera por
bpContext«. - 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:
- Toma snapshot consistente (con
quiesce=yesvía QEMU Guest Agent cuando esté disponible). - Lee sólo bloques marcados dirty desde el último backup, vía NBD
BLOCK_STATUS. - Stream-ea esos bloques por PTCOMM con offset preservado.
- 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:
- Bootea la VM en PVE apuntando a un disco virtual servido por NBD desde el backend, leyendo directo del stream Bacula restore.
- Los overlay writes (
ir_overlay_storage) capturan cambios del guest en storage local rápido. - 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.confdebe tener perms600, ownerbacula.- 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_fingerprinten producción. Setearpve_insecure=yesacepta cualquier cert presentado por el PVE host — incluyendo certs MITM. Sólo en laboratorio. - No corras
quiesce=yessin 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_porty el segundo falla al bind-ear. - No confundas
backup_type=incremental(Bacula level) conmode=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:
Português (Portugués, Brasil)
English (Inglés)
Español