VM-level backup with block-level CBT (QEMU dirty bitmaps), continuous DR replication, cross-restore to Proxmox/Hyper-V/VMware, and streaming format conversion — capabilities the Bacula Enterprise CloudStack plugin does not cover.
Companion technical document to the PodHeitor CloudStack plugin page.
1. The problem: the Bacula Enterprise CloudStack plugin is API-only
The Bacula Enterprise CloudStack plugin operates strictly over the CloudStack API: snapshots a volume, downloads via API, restores via API. That has three hard limits:
- No real CBT. BEE incrementals are API-level snapshot diffs — volume granularity, not block granularity. For a 200 GB VM with 500 MB modified, BEE still transfers the full snapshot delta from CloudStack, not just the dirty blocks.
- No continuous replication. BEE has no DR push mode. Secondary-site replication requires a second tool (CloudStack Snapshot Schedule + manual copy).
- No cross-restore. BEE only restores CloudStack backups to CloudStack. Migration to Proxmox/Hyper-V/VMware requires manual
qemu-imgconversion plus config rebuild.
The PodHeitor CloudStack Plugin removes all three bottlenecks with direct KVM/libvirt access via NBD, native QEMU dirty bitmap, and a streaming conversion pipeline.
2. Architecture — 100% Rust cdylib
v2.0.0+ is a 100% Rust architecture. The FD-side .so is a pure Rust cdylib (built from ../PodHeitor Rust cdylib/crates/plugin-cloudstack/) that dlopens into bacula-fd. No Bacula AGPLv3 source is statically linked. The legacy C++ shim was removed — every responsibility (plugin identity, parameter validation, PTCOMM driver, virtual-file buffering, restore replay) is reimplemented in the Rust metaplugin-rs framework.
┌──────────────────────────────────────────────────────────┐
│ Bacula File Daemon (FD) │
│ ┌────────────────────────────┐ │
│ │ podheitor-cloudstack-fd.so│──PTCOMM──▶┌────────────┐ │
│ │ (Rust cdylib) │◀─────────│ Rust │ │
│ │ │ │ Backend │ │
│ └────────────────────────────┘ └─────┬──────┘ │
│ │ │
│ ┌──────────────────────────────────────────────┴─────┐ │
│ │ CloudStack API (HMAC-SHA1) + SSH/NBD data plane │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
The backend (~12,000 LOC Rust) is spawned as a subprocess per job — preserving the PTCOMM crash-isolation boundary. The cdylib is minimal: plugin identity + PTCOMM driver + virtual-file buffering. All backup, restore, replication and conversion logic lives in the backend.
3. Three-Layer Change Detection (CBT)
Real PodHeitor incrementals aren’t snapshot diffs — they are a three-layer pipeline that covers the happy path and the pathological cases:
| Layer | Mechanism | Function |
|---|---|---|
| L1 | QEMU QMP dirty bitmap | Only blocks changed since the last backup enter the pipeline |
| L2 | xxHash64 block hash | Eliminates false-positives from unclean bitmap resets (KVM hard reboot, host crash) |
| L3 | SIMD zero-block detection | Eliminates sparse/zeroed regions — thin-provisioning benefit |
Typical result: backup window reduction > 90% vs Full on successive incrementals.
4. Operating modes
| Mode | Purpose |
|---|---|
| (unset) | Normal Bacula backup (Full or Incremental) |
bitmap_push |
Continuous DR — sends dirty-bitmap deltas to dr_host |
seed |
Initial full sync before switching to bitmap_push |
receiver |
DR-side daemon — accepts incoming bitmap pushes |
daemon |
Long-running replication sender (out-of-band from Bacula jobs) |
failover_pre / failover_exec |
Promote DR replica to primary |
failback_pre / failback_exec |
Re-sync primary after failover and swap back |
reprotect |
Re-establish replication in reverse direction |
replication_status |
Sender/receiver health query |
5. Cross-restore — streaming conversion
The plugin restores a CloudStack VM into a different hypervisor than the source, selected by the cross_restore parameter:
| Target | Disk conversion | Guest adaptation |
|---|---|---|
proxmox |
qcow2 → qcow2 (passthrough) or raw | qm config generation; virtio driver tuning |
hyperv |
qcow2 → vhdx (streaming via qemu-img) | VMCX XML; integration services |
vmware |
qcow2 → vmdk (streaming) | VMX file; VMware Tools handoff |
Conversion is streaming: no intermediate file is materialised. The pipeline is NBD read → qemu-img convert (stdin) → SSH/SMB push (stdout). For a 100 GB VM going CloudStack→Hyper-V, that avoids 100 GB of temporary I/O on the FD host.
6. Data plane — three NBD modes
The nbd_access_mode parameter selects the byte-read path from the KVM hypervisor:
| Mode | Behaviour | When to use |
|---|---|---|
ssh_tunnel (default) |
SSH to KVM host, opens local tunnel to NBD port, NBD client connects via tunnel | Safe default — does not require opening 10809 in firewall |
direct |
NBD client connects directly to kvm-host:10809 |
When backup network is segregated and firewall allows |
api_only |
API-only fallback (snapshot download via CloudStack) | When SSH to KVM host fails — logs warning |
Typical arithmetic: direct delivers 3–5 GB/s on 10 GbE LAN; ssh_tunnel ~1.5–2.5 GB/s (crypto overhead bound); api_only rarely exceeds 200 MB/s.
7. Continuous DR replication (bitmap-push)
bitmap_push mode turns the plugin into an out-of-band replication agent: cycles every cycle_interval seconds (default 300) read the current dirty bitmap, compute delta vs last cycle, send to dr_host:dr_port over AES-256-GCM transport with derived PSK.
- PSK ≥ 32 chars required.
- Exponential backoff retries:
retry_count=3,retry_delay_ms=5000, jitterretry_jitter_ms=1000. - Resilience: if the receiver dies, the sender keeps the accumulated bitmap; reconnect resumes from the last confirmed cycle.
- Bandwidth saving: 95%+ vs full copy (delta-only push).
Failover automates promotion: failover_pre drains in-flight, failover_exec presents the replica disks to a new CloudStack VM at the DR site. failback_pre/failback_exec + reprotect close the loop.
8. Performance
| Metric | Typical value |
|---|---|
| Full backup throughput | 1.5–5 GB/s (NBD mode, network, disk dependent) |
| Incremental window reduction | 90%+ vs Full (dirty bitmap + hash dedup) |
| Replication bandwidth saving | 95%+ (delta-only push) |
| zstd compression (level 3) | 50–60% |
| Memory per concurrent disk backup | ~17 MB (NBD buffers + zstd context + hash working set) |
| Hash lookup | O(1) — flat mmap’d array, OS page cache |
9. Memory-mapped Hash DB
The PodHeitor hash DB is a flat memory-mapped array indexed by xxHash64. Unlike traditional DBs (LMDB, RocksDB), there is no SSD latency on the hot path — the kernel page cache keeps the working set resident. Result:
- O(1) lookup with no syscall (mmap-only).
- SSD-latency-independent: run on HDD if you want.
- Working set scales with unique blocks, not with total volume.
10. Documented anti-patterns
- Don’t disable
quiesceon DB workloads. Without qemu-guest-agent freeze/thaw, DB snapshots are crash-consistent — valid but not application-consistent. For Postgres/MySQL/MSSQL keepquiesce=true. - Don’t use
verify_ssl=falsein production. Import the CloudStack management CA into the FD host trust store.verify_ssl=falseexists for PoC/lab. - Don’t run
bitmap_pushover WAN without a cap. Default 300s cycle + delta can saturate 100 Mbps on write-heavy VMs. Configurebandwidth_limitor raisecycle_interval.
11. License posture
The plugin ships under LicenseRef-PodHeitor-Proprietary. No Bacula AGPLv3 source is statically linked. Binding is exclusively via independent extern "C" declarations.
Ready to evaluate?
Free 30-day trial for qualified CloudStack workloads (KVM, validated on CloudStack 4.16/4.19/4.20). We guarantee at least 50% discount vs Bacula Enterprise, Veeam, Commvault or NetBackup, with more features included — and cross-restore that none of those three offer.
Heitor Faria — Founder, PodHeitor International
✉ [email protected]
☎ +1 (789) 726-1749 · +55 (61) 98268-4220 (WhatsApp)
🔗 PodHeitor CloudStack plugin page
Disponível em:
Português (Portuguese (Brazil))
English
Español (Spanish)