Add Ansible-driven DR bootstrap (#9) + PVC capture in backups

restore-k8s-apps.sh's own header explicitly scoped out fresh-cluster
provisioning ("assumes the target cluster/namespaces/PVCs already
exist"). This adds exactly that missing piece as an Ansible playbook
rather than another bash script — a better fit for "provision a target
into a working state" than for the actual data-restore logic, which stays
in restore-k8s-apps.sh unchanged and is just invoked as the final step.

dr-bootstrap.yml, given an app name + a backup archive:
  1. Checks the target's environment (disk/mem/cpu) against k3s minimums
     before touching anything
  2. Installs k3s only if not already present (checked, not assumed)
  3. Creates the namespace, PVC(s), Secret, and Deployments/Services/
     Ingress from the backup's captured manifests — sanitized first
     (sanitize_k8s_manifest.py strips resourceVersion/uid/status/
     volumeName, the same class of bug as the earlier Secret-apply fix,
     generalized) since a raw `kubectl get -o yaml` dump can't be
     reapplied to a different cluster as-is
  4. Hands off to the existing, already-validated restore-k8s-apps.sh for
     the actual data population

backup-k8s-apps.sh gets a small but necessary addition: PVC specs were
never captured at all before (only Deployment/Service/ConfigMap/Ingress),
so there was nothing for a fresh-cluster bootstrap to provision the PVC
from. Captured with the same label selector as the existing manifests
capture, so it stays in lockstep automatically.

Verified for real, not just syntax-checked: ran the full playbook against
the standby VM (freshly installed k3s, empty namespace) using a live n8n
backup. Namespace/PVCs/Secret/Deployments provisioned from nothing, data
restored, pod healthy, and the workflow count in the restored Postgres DB
(11) matched the real source server exactly.
This commit is contained in:
root
2026-08-21 14:30:58 +02:00
parent 459c2974f0
commit 054a9e1fcb
4 changed files with 254 additions and 0 deletions

View File

@@ -306,6 +306,19 @@ for app in $ALL_APPS; do
&& { echo "✅"; rm -f "$APP_DIR/manifests.err"; } \
|| { echo "⚠️ FAILED (see manifests.err)"; ERRORS+=("$app:manifests:$(tr '\n' ' ' < "$APP_DIR/manifests.err" | cut -c1-200)"); }
# ---- 1b. PVC specs — NOT covered by the manifests capture above, and
# needed to provision a namespace from scratch on a fresh/DR
# cluster (restore-k8s-apps.sh assumes the PVC already exists;
# this is what actually creates it there). Same label selector
# as the manifests capture, so this stays in lockstep with
# whatever PVCs are actually labeled as ours — no separate
# per-app name table to keep in sync. ----
echo -n " 💾 PVC specs ... "
kubectl get pvc -n "$ns" -l owner=ameni-boukattaya -o yaml \
> "$APP_DIR/pvc.yaml" 2>"$APP_DIR/pvc.err" \
&& { echo "✅"; rm -f "$APP_DIR/pvc.err"; } \
|| { echo "⚠️ FAILED (see pvc.err)"; ERRORS+=("$app:pvc_spec:$(tr '\n' ' ' < "$APP_DIR/pvc.err" | cut -c1-200)"); }
# ---- 2. Secret (plaintext, encryption-at-rest deferred) ----
echo -n " 🔑 Secret ($secret_name) ... "
kubectl get secret "$secret_name" -n "$ns" -o yaml \