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.

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:

1task 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:

1task 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:

1task backup:restore APP=snac NS=fediverse-system PREV=2
23        ├─ _label    ns -> pod-security: privileged   (nur falls nicht ohnehin privileged)
4        ├─ _suspend  flux suspend ks/snac  +  scale deploy|sts -> 0  (auf Pod-Delete warten)
5        ├─ _wipe     Job (busybox, privileged): cd /data && find . -delete
6        ├─ _restore  ReplicationDestination -> restic (copyMethod: Direct, previous: 2)
7        ├─ _resume   flux resume ks/snac
8        └─ _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:

1kind: ReplicationDestination
2spec:
3  trigger: { manual: restore-once }
4  restic:
5    repository: "${APP}-volsync-secret"
6    destinationPVC: "$CLAIM"
7    copyMethod: Direct      # schreibt direkt in die Live-PVC
8    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:

1command: ["/bin/sh", "-c", "cd /data; find . -delete"]
2securityContext:
3  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:

1task 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.