Architecture
Tool choices for the backup automation, and what is deliberately not used.
The backup stack is a hybrid of well-established tools, glued together by a thin orchestrator script. We delegate everything that touches actual data bytes — WAL replay, deduplication, compression, encryption, PITR — to tools that have been beaten on by thousands of ops teams. The custom code is limited to: config parsing, SSH orchestration, cron rendering, and the drill harness.
Topology
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Reverse Proxy│ │ Compute │ │ Storage │ │ Backup │
│ (RP node) │ │ (RKE2) │ │ (NFS + PG) │ │ (4th node) │
├──────────────┤ ├──────────────┤ ├──────────────┤ ├──────────────┤
│ wireguard │ │ etcd snap │ │ postgres │ │ pgBackRest │
│ nginx │ │ rancher- │ │ NFS export │ │ repo host │
│ local CA │ │ backup CR │ │ pgbackrest │ │ restic repos │
│ │ │ rke2 TLS │ │ (client) │ │ etcd archive │
│ │ │ state │ │ │ │ cron │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │
│ tar over SSH │ rsync pull │ pgbackrest │
└───────────────────┴────────────────────┴────────────────────┘
pull-based, encryptedPull-based — the backup node SSHes outward to the production nodes, never the other way around. A compromised production node can't reach back and erase its own backups.
Remote commands run under sudo bash -lc with TERM=dumb so login-shell profile hooks (clear_console, etc.) do not pollute captured stdout over non-interactive SSH.
Tools and why each is here
pgBackRest — PostgreSQL
Why this and not pg_dump: dumps are point-in-time only; recovery loses every transaction since the dump. We need WAL streaming for ~1-minute RPO. pgBackRest does WAL archiving, parallel full/diff, native compression, AES-256 repo encryption, and restore with PITR (--type=time --target=...).
Why this and not Barman: both work. pgBackRest's config is simpler for our shape (one stanza, one repo host, no separate barman user vs. postgres SSH key dance), and parallel WAL push is built in.
User guide · Command reference
RKE2 built-in etcd-snapshot — etcd
Why built-in and not external: RKE2 already has the snapshot mechanism, schedule, and cluster-reset-from-snapshot path. Adding etcdctl-based jobs would duplicate that and risk drift. We just configure the schedule (etcd-snapshot-schedule-cron) and rsync-pull the resulting files.
rancher-backup operator — Kubernetes resources
Why this and not Velero: Velero needs an always-on S3-compatible store for its primary workflow. Volume snapshots are not our main gap — we handle PVC bytes via restic on the NFS export. Opt-in MinIO/S3 content backup (when an environment runs object storage) is a separate objectstore group using rclone + restic onto the backup host. rancher-backup does what we need for Kubernetes API objects: a curated ResourceSet of GVKs, encrypted tarball output, schedules. Despite the "rancher-" name, it backs up arbitrary GVKs — we use it for Secrets, ConfigMaps, PV/PVCs, and curated CRD groups (cert-manager, monitoring, Istio, Keycloak, Logging) in addition to Rancher's own state.
Storage model: The operator writes to a PVC mounted at /var/lib/backups inside its pod. We provision a static NFS PV (openg2p-rancher-backup-store) bound to /srv/nfs/<cluster>/rancher-backup on the storage export — not dynamic per-helm-revision PVCs (which rotate empty on every helm upgrade). Backup / Restore CRs omit storageLocation (the operator only supports explicit S3 there). Tarballs are AES-encrypted (*.tar.gz.enc) via an encryptionConfigSecretName Secret applied at install.
Backup-restore-operator · Rancher docs
restic — NFS data and config files
Why this and not borg/duplicity: single Go binary (air-gap friendly), encrypted-by-default, content-addressed dedup, supports backup from stdin (we use this for SSH-tar streams of remote config dirs). restic's local-filesystem repo backend works fine without S3.
The NFS export is mounted read-only on the backup host — a compromised backup process cannot accidentally write into the live NFS volume. Install refreshes /etc/fstab with noauto,_netdev (no x-systemd.automount). After a DR rebuild that changes the storage private IP, _nfs_ensure_ro_mount force-unmounts stale mounts and may fall back to /mnt/openg2p-nfs-ro-dr.
rclone + restic — object store (opt-in)
When groups.objectstore: true, the backup host mounts a MinIO/S3 remote read-only with rclone and takes an encrypted restic snapshot into $backup_repo_root/objectstore-restic. This backs up object-store contents onto the backup volume; it is not Velero and not an offsite replica by itself.
Sidecar PVC manifest — UUID → app mapping
NFS data is stored under directories named after the PV's CSI subdir or native NFS path (e.g. cattle-resources-system-<pvc>-<pv-uuid> under /srv/nfs/<cluster>/ on the nfs-csi StorageClass). On its own, restic just sees opaque dirs. Each backup run writes a sidecar YAML manifest (<repo>/nfs/.pvc-mapping.yaml) joining kubectl get pv -o json against the live NFS file listing. The jq filter matches both native NFS PVs (spec.nfs.path) and CSI PVs (spec.csi.volumeAttributes.subdir / reconstructed <namespace>-<pvc>-<pv> names), so restore knows which directory belongs to which (namespace, pvc, app) triple.
Tools we considered and rejected
Velero
Primary workflow needs S3; heavier than rancher-backup + NFS restic for our topology
Barman
Functionally equivalent to pgBackRest; less ergonomic for our SSH-pull setup
etcdctl scheduled snapshots
Duplicates RKE2's built-in mechanism
Hand-rolled kubectl get -o yaml
Doesn't strip managed fields cleanly, no restore tooling
K10 / commercial backup
Out of scope for OpenG2P open-source baseline
Where custom code is
automation/backups/ in the deployment repo:
openg2p-backup.sh— orchestrator (subcommand dispatch, install, restore, wal-health, daily-report)lib/utils.sh— sources the production lib's logger, cfg parser, ssh helpers; adds group toggles, status JSON, passphrase resolution, backup-host preflightlib/{pgbackrest,etcd,rancher,nfs,configs,objectstore,restic,drills}.sh— per-group lifecycle (install / run / verify / list / restore / drill)lib/{metrics,notify,wal_health}.sh— Prometheus textfile / optional Pushgateway, SMTP notify, independent WAL probemanifests/prometheusrule-backup.yaml.template— applied intocattle-monitoring-systemat installroles/storage/configure-pg.sh— runs on storage node to configure pgBackRest client + WAL archivingroles/backup-host/install.sh+cron.template— bootstrap on the 4th node (includes wal-health + daily-report + objectstore cron lines)
Everything that touches data bytes is still delegated to pgBackRest / restic / rclone / rancher-backup / RKE2.
Where keys live
The operator's p12 keystore holds:
restic.pass— passphrase for both NFS and configs restic repos (one passphrase, two repos)pgbackrest.pass— pgBackRest repo cipher passphraseetcd-aescbc.key— etcd encryption-at-rest key (only if encryption is enabled)(Optional)
smtp.env— SMTP settings for operator email (Alerting)(Optional)
rclone.conf+objectstore-restic.pass— whengroups.objectstoreis enabled
These are pushed to the backup host at install time as mode-0600 files under /etc/openg2p-backup/ (except rclone/objectstore only when that group is enabled). The orchestrator never commits them to the repo. Losing restic/pgBackRest passphrases renders the corresponding backups unrecoverable — same custody model as TLS keys for the platform.
Last updated
Was this helpful?