For the complete documentation index, see llms.txt. This page is also available as Markdown.

Full rebuild

Build fresh nodes, run the production install, then layer backups on top — the disaster recovery runbook.

The disaster scenario: nodes destroyed or unreachable, hardware lost, region-down event. Build fresh and bring everything back from backups.

Pre-flight checklist

Before you start, gather:

If the backup host itself is also destroyed, recovery isn't possible — backups are gone with it. Offsite / 3-2-1 replication is still an operator plan (see Backups — what this does not do); protect the backup volume accordingly.

Step 1 — Provision fresh nodes

cd automation/production/aws/
# Edit aws-config.yaml as needed (project, region, instance types).
# Set backup_node.enabled: true if you want backups again.
./openg2p-aws-provision.sh --config aws-config.yaml

This creates 3 (or 4) new instances with new IPs. The provision-output.yaml is regenerated with the new addresses.

If your backup host survived, do not provision a new backup node — keep using the existing one. Manually update backup-config.yaml to point at it (skip the backup_node.enabled: true flag in aws-config).

Step 2 — Install the platform

This runs the full production automation. Result: a clean OpenG2P install with no customer data, default Rancher/Keycloak/etc.

Verify:

Step 3 — Make backup tooling available

If you provisioned a new backup node:

If your backup host survived: re-run install so the backup host learns the new IPs/SSH keys of the freshly-provisioned production nodes:

Kept the backup node with old repos? The fresh platform install created a new empty Postgres whose system-id does not match the surviving pgBackRest stanza. install may log pgBackRest error [028] ("backup and archive info files exist but do not match the database") and then skip the first full backup on purpose — do not delete or re-init /var/lib/openg2p-backup/pg; you need those files for Step 4 restore. Tooling and SSH trust are still installed. After you restore + cutover PG, run ./openg2p-backup.sh run --component pg to take a new full of the restored database.

NFS mount I/O error on /mnt/openg2p-nfs-ro? The backup host may still have a stale mount (and, on older installs, a systemd automount) to the old storage private IP. Re-run install --force with current scripts: they stop any leftover automount/mount units, force-unmount via /proc/mounts, rewrite /etc/fstab without x-systemd.automount (ro,soft,timeo=30,retrans=2,noauto,_netdev), remount, and fall back to /mnt/openg2p-nfs-ro-dr (marker: /var/lib/openg2p-backup/.nfs-mount-point) if the canonical path stays poisoned. Manual clear on the backup host if needed:

If install still hard-fails on an older script build before that skip existed, finish tooling without touching the PG repo:

Step 4 — Restore PostgreSQL

The platform install just laid down a clean Postgres on the storage node. You need to replace it with the restored data.

This stages the restored Postgres data dir at /var/lib/openg2p-backup-restore/pg-<ts>/ on the storage node. Follow the cutover steps in Postgres PITR — Step 4 to replace the live PG.

Verify:

Step 5 — Restore Kubernetes resources

The fresh helmfile install recreated the platform's own resources. We now layer the user-state on top via a rancher-backup Restore CR.

5a — Note the new storage NFS IP

After reprovision, the storage private IP changed. You need it for every NFS PV and for copying the tarball.

Write down:

Item
Example

New storage private IP

172.29.7.49

Old storage private IP (pre-disaster)

172.29.0.104 (from your old notes / restored PV YAML)

Rancher tarball path on NFS

/srv/nfs/openg2p/rancher-backup/

The pre-disaster rancher backup tarball and the PV objects inside it still reference the old NFS server IP. The new cluster's live PVs must use the new IP before and after restore.

5b — Point the rancher-backup store PV at the new NFS IP

install creates static PV openg2p-rancher-backup-store with the current storage IP.

Confirm it before you restore:

  • If server already equals the new storage private IP, you can skip the patch in this step.

  • Even if it’s correct, the restore tarball still contains old PV definitions; on full rebuilds the restore operator may hit immutable-field conflicts. That's why Step 5c uses ignoreErrors: true.

If server is wrong, patch it to the new storage IP (only safe when the PV is not bound to a running operator pod, or delete/recreate per your runbook):

If patch fails with immutable, delete any stuck Restore CR, keep the PV with the new IP, and use ignoreErrors: true on the Restore (Step 5d) so restore does not try to replace this PV.

5c — Orchestrator: restic → new NFS → Restore CR

Like postgres restore, DR rancher restore now pulls from the backup host restic repo, places the tarball on the new storage NFS (/srv/nfs/openg2p/rancher-backup/), and applies a Restore CR (ignoreErrors: true).

--point-in-time is a cutoff (not a restic snapshot id): the newest rancher-backup/*.tar.gz.enc whose filename timestamp is strictly before the cutoff is selected.

Watch progress:

Manual fallback (if the orchestrator cannot reach storage SSH): on the backup host, restic snapshots --tag nfs, restic restore … --include '**/rancher-backup/*.tar.gz.enc', stream the file to ubuntu@<new-storage>:/srv/nfs/openg2p/rancher-backup/, then apply a Restore CR with backupFilename: <basename>, prune: false, ignoreErrors: true.

The operator recreates Secrets, CRs, and PV/PVC objects from the tarball. Those PVs may still carry the old NFS server IP inside the backup.

5d — Fix NFS server IP on restored PVs (old IP → new IP)

After restore, some native NFS PVs may still point at the dead storage IP. Patch those if spec.nfs is present:

nfs-csi PVs cannot be patched this way (spec.csi / volume source is immutable). For CSI volumes, leave the Bound PV alone — Step 6 (nfs restore) pushes data under its subDir. Delete unused Released PVs after cutover.

If a native NFS patch fails with immutable, delete the conflicting empty PV/PVC from the fresh install and re-apply restore with ignoreErrors: true, or recreate the PV. Do not change openg2p-rancher-backup-store back to the old IP — keep it on the new storage IP.

Confirm rancher-backup store still correct:

Common Step 5 failures

Log / status
What to do

Restore used wrong / empty tarball

Re-run 5c with a cutoff before disaster; confirm file under /srv/nfs/openg2p/rancher-backup/ on new storage

openg2p-rancher-backup-storespec.nfs is immutable (old vs new IP)

Keep PV on new IP (5b); Restore CR already sets ignoreErrors: true

users.management.cattle.io / username already exists

Safe to ignore with ignoreErrors: true

App PVCs Pending / wrong NFS

Native NFS: patch spec.nfs.server (5d). CSI: nfs restore pushes under Bound subDir (Step 6); do not patch CSI attributes

Step 6 — Restore NFS data

Now the cluster knows about every original PV and PVC, but their NFS-backed data dirs are empty (or newly created empty UUID dirs) on the new storage node.

Like postgres restore, restore --component nfs stages from restic on the backup host and pushes onto the Bound PV path on the new storage node (CSI subDir or native NFS basename).

For each PVC that needs data (scale the workload down first):

Details (--tag nfs vs pvc-manifest, empty-restore failure, UUID matching) are in single-pvc.md.

Bound vs restored UUID (nfs-csi)

Tree
Origin
What uses it

Bound PV subDir (new UUID from helmfile)

Fresh install

Running pods — orchestrator push target

Restored dir (old UUID from restic)

Pre-disaster backup

Staging only; then pushed into Bound path

Manual tar/rsync fallback (only if SSH push fails) is in single-pvc.md.

Step 7 — Object store (opt-in)

Only if groups.objectstore was enabled before the disaster and the backup host (or its objectstore-restic repo) survived:

Then sync the restored tree into the new MinIO/S3 (see Restoration index — Object store). PVC-backed bucket data under NFS is already covered by Step 6.

Step 8 — Restore platform-level config (optional)

The fresh install regenerated:

  • The local CA on the RP node (different cert!)

  • Wireguard server keys (different pubkey!)

  • RKE2 cluster CA

If you want to keep the original identities (so admin laptops' Wireguard configs and trusted CA cert still work), restore the configs group — the orchestrator pushes onto the RP node and restarts the matching service:

Prior contents are moved aside to *.precrash on the RP. RKE2 tags (rke2-tls, rke2-cred, …) push onto compute instead (pair with etcd in-place restore when needed).

This is optional — most operators accept regenerating these and re-distributing Wireguard client configs to admin laptops.

Step 9 — Bounce workloads + verify

Update Postgres host for Keycloak and Superset (new storage IP)

The new storage node has a new private IP. Postgres now listens there, but rancher-restored (or previously rendered) app config may still embed the old storage IP.

Keycloak and Superset commonly keep failing until you point them at the new Postgres address:

  1. Find the new storage private IP (provision-output.yaml / storage_private_ip, or the live Postgres host you cut over to).

  2. Search ConfigMaps and Secrets for the old IP / old JDBC/DSN host.

  3. Update Keycloak and Superset DB connection settings (ConfigMaps and Secrets — Superset often stores the SQLAlchemy URI in a Secret).

  4. Restart those workloads and confirm they reach Postgres.

Until those values use the new IP, Keycloak and Superset will keep connecting to the dead address and stay CrashLooping / Not Ready.

Restart and sanity-check

Sanity tests:

  • Log into Rancher with original SAML credentials

  • Log into Keycloak with admin email (fails if still on old Postgres IP)

  • Open Superset (same — confirm DB host is the new storage IP)

  • Check at least one PVC-consuming app's data (e.g. MinIO buckets / Loki after NFS cutover)

  • Confirm a known-recent-but-pre-disaster Postgres row exists

Step 10 — Re-establish backup automation

This produces the first new backup of the rebuilt cluster. From this point forward, restoring this cluster again uses these new backups (older ones still recoverable but tied to the old cluster identity).

What you've lost

  • Anything written to PG between the last backup and the disaster

  • Anything written to NFS between the last NFS backup (nightly) and the disaster — up to 24h

  • Cluster-internal Prometheus history (not in scope for this automation)

  • In-flight requests at the moment of the disaster

How long this takes

Empirically, on AWS with t3a.xlarge backup + the standard production sizing:

  • AWS provisioning: ~5 min

  • Production install: 18–25 min

  • Backup install: ~5 min

  • PG restore + cutover: 5–15 min depending on dataset size

  • Rancher Restore CR: 5–10 min

  • NFS restore (per PVC): 1–10 min depending on size

  • End-to-end for a moderate-sized installation: 45–90 minutes

Validate this number quarterly via the Drills — Quarterly DR rehearsal procedure. Real numbers from your environment beat estimated ones.

Last updated

Was this helpful?