Restore als ein Kommando — die backup-Task-Kategorie

Ein VolSync-Restore ist eine sechsstufige Choreografie aus Flux-suspend, PVC-wipe und restic-restore. Wie das go-task-Setup im Lab daraus ein einziges, idempotentes Kommando macht — inklusive des Pod-Security-Tricks, der den wipe-Job überhaupt erst durchlässt.
Table of contents

Ein Restore ist der eine Moment, in dem man garantiert unter Stress steht: irgendwas ist kaputt, und ausgerechnet jetzt soll man sich die Reihenfolge aus Flux-suspend, PVC-leeren, restic restore und Flux-resume fehlerfrei aus dem Kopf klicken. Genau diese Reihenfolge gehört nicht in den Kopf, sondern in Code. Im go-task-Setup des Labs übernimmt das die backup-Kategorie — ein dünner Wrapper um VolSync und restic , der aus dem VolSync-Aufbau ein paar einprägsame Kommandos macht.

Drei Befehle für den Alltag

Der harmlose Teil zuerst. Snapshots ansehen:

task backup:list APP=snac NS=fediverse-system

Das startet einen ephemeren Job (restic:0.18.1 snapshots, der das ${APP}-volsync-secret als envFrom zieht), wartet auf dessen Abschluss, gibt die Logs aus und räumt sich selbst wieder weg. Einen Snapshot sofort erzwingen — etwa direkt vor einem riskanten Upgrade:

task backup:snapshot APP=snac NS=fediverse-system

Hier wird die ReplicationSource mit trigger.manual=<timestamp> gepatcht und auf den volsync-src-<app>-Job gewartet. Beide Tasks brauchen nur APP (und einen KUBE_CONTEXT); NS defaultet auf default, und ob die App ein Deployment oder ein StatefulSet ist, ermittelt der Task selbst.

Der gefährliche Teil: restore

Ein Restore ist kein einzelner Aufruf, sondern eine Kette — und jede Stufe kann man von Hand vergessen oder vertauschen:

task backup:restore APP=snac NS=fediverse-system PREV=2
        ├─ _label    ns → pod-security: privileged   (nur falls nicht ohnehin privileged)
        ├─ _suspend  flux suspend ks/snac  +  scale deploy|sts → 0  (auf Pod-Delete warten)
        ├─ _wipe     Job (busybox, privileged): cd /data && find . -delete
        ├─ _restore  ReplicationDestination → restic (copyMethod: Direct, previous: 2)
        ├─ _resume   flux resume ks/snac
        └─ _unlabel  ns → pod-security zurücksetzen

Der Clou steckt darin, dass der Task die Parameter des Restores nicht erfindet, sondern aus der vorhandenen ReplicationSource ausliest: den Quell-PVC (CLAIM) und die moverSecurityContext-UID/GID. So schreibt der Restore-Mover mit exakt der uid:gid der App zurück in genau die Live-PVC, an der die App später wieder hochkommt. PREV steuert, wie viele Snapshots zurück restauriert wird (0 = neuester) — direkt durchgereicht an previous im ReplicationDestination:

kind: ReplicationDestination
spec:
  trigger: { manual: restore-once }
  restic:
    repository: "${APP}-volsync-secret"
    destinationPVC: "$CLAIM"
    copyMethod: Direct      # schreibt direkt in die Live-PVC
    previous: $PREV

Weil copyMethod: Direct direkt auf das Live-Volume schreibt, ist die Reihenfolge nicht optional: erst die App stoppen (_suspend), dann das Volume leeren (_wipe), dann restaurieren. Würde man bei laufender App restaurieren, kämpften App und Mover um dieselben Dateien.

Der Pod-Security-Trick

Der wipe-Job ist bewusst brachial — eine busybox, die find . -delete auf dem gemounteten PVC ausführt, und dafür privileged: true braucht:

command: ["/bin/sh", "-c", "cd /data; find . -delete"]
securityContext:
  privileged: true

In einem Namespace mit strikten Pod Security Standards würde dieser Pod schlicht nicht zugelassen. Deshalb labelt _label den Namespace temporär auf pod-security.kubernetes.io/enforce=privileged — aber nur, wenn er nicht ohnehin schon privileged ist (sonst bliebe das Label nach dem _unlabel fälschlich hängen). Nach erfolgreichem Restore setzt _unlabel die ursprüngliche Posture wieder. Das ist die operative Kehrseite der privileged-movers-Notiz aus dem VolSync-Artikel: Mover und wipe-Job brauchen kurz mehr Rechte, als die App selbst je bekommen darf.

Wenn das Repo klemmt: unlock

Stirbt ein Mover mitten im Lauf, hinterlässt restic ein stehengebliebenes Lock — und jeder folgende Snapshot oder Restore hängt. Dafür gibt es den Notnagel:

task backup:unlock APP=snac NS=fediverse-system

Ein Job mit restic unlock --remove-all, mehr nicht. Aber genau die Art Einzeiler, an die man sich um drei Uhr nachts nicht erinnert.


Kein Hexenwerk — und genau das ist der Punkt. Der Unterschied zwischen „ich tippe sechs kubectl- und flux-Kommandos in der richtigen Reihenfolge" und „ich tippe task backup:restore" ist im Ernstfall der Unterschied, der zählt. Eine Restore-Prozedur, die man nicht regelmäßig übt, ist im Zweifel keine — als Task ist sie wenigstens reproduzierbar.