Backup¶
Diese Seite erklärt, was wir wie sichern, was bewusst nicht, wie lange die Backups aufbewahrt werden und wie Fehler auffallen. Wie man eine App wiederherstellt, steht in App aus dem Backup wiederherstellen.
Aufbau¶
Gesichert werden die Daten der Apps auf werner, mit restic. Die Backups gehen über den WireGuard-Tunnel an containerhost und landen am Ende auf dem NAS.
flowchart LR
subgraph werner ["werner"]
timer["system-backup.timer<br>täglich nachts"]
script["system-backup"]
dbs["Datenbank-Container<br>/bin/container-backup"]
restic["Container restic<br>/bin/restic-backup"]
end
subgraph ch ["containerhost"]
rest["rest-server<br>append-only"]
backrest["Backrest<br>räumt auf"]
end
nas[("NAS<br>/mnt/backup")]
hc["healthchecks.io"]
timer --> script
script -->|1. Dumps| dbs
script -->|2. Backup| restic
restic -->|WireGuard-Tunnel| rest
rest -->|NFS| nas
backrest -->|forget / prune| nas
script -.->|Start, Erfolg, Fehler| hc
Die Teile im Einzelnen:
- restic auf werner
- Ein Container
restic, der dauerhaft läuft und alle zu sichernden Volumes eingebunden hat (syslet/werner/infra-backup.cue). Er macht nichts von selbst; das Backup startetsystem-backupmitpodman exec. - rest-server auf containerhost
- Nimmt die Backups von werner entgegen und schreibt sie auf das NAS (
syslet/containerhost/stack-resticserver.cue). Er läuft im Modus append-only und verlangt eine Anmeldung mit Benutzername und Passwort (HTTP Basic Auth); die Zugangsdaten liegen als Secret beim jeweiligen Host. - Repository auf dem NAS
- Der Ordner, in dem restic alle Backups von werner ablegt, verschlüsselt mit einem eigenen Passwort. Jeder Backup-Lauf legt darin einen Snapshot an.
- Backrest auf containerhost
- Eine Oberfläche für restic, unter
backrest.garage-lab.netim Netz vor Ort. Backrest greift direkt auf das Repository auf dem NAS zu, nicht über den rest-server, und räumt alte Snapshots auf (siehe Aufbewahrung).
Wegen des Modus append-only kann jemand, der werner übernimmt, mit dessen Zugangsdaten zwar an den rest-server, aber die vorhandenen Backups weder löschen noch überschreiben. Aufräumen kann nur Backrest auf containerhost, das direkt auf das NAS zugreift.
Der rest-server läuft auf containerhost, nicht auf dem NAS. Das NAS ist zu alt, um Container zu betreiben, und kann den rest-server deshalb nicht selbst ausführen. Das Repository direkt per NFS über den Tunnel an werner freizugeben, kam nicht in Frage, weil werner dann alle Backups ändern und löschen könnte. Auf containerhost läuft der rest-server als Container und wird wie alle anderen mit syslet ausgerollt; das NAS muss nur Speicher bereitstellen, und dafür reicht NFS.
Ablauf und Zeitplan¶
Jede Nacht um 3 Uhr (UTC), mit bis zu 30 Minuten zufälliger Verzögerung, startet system-backup.timer das Skript rootfs/werner/usr/bin/system-backup.
War werner zu dieser Zeit aus, holt systemd den Lauf nach dem Start nach.
Das Skript:
- meldet healthchecks.io den Start
- lässt jede Datenbank einen Dump schreiben, indem es in ihrem Container
/bin/container-backupaufruft; die Nextcloud schaltet es dafür in den Wartungsmodus und danach wieder zurück - ruft im Container
restic/bin/restic-backupauf, das alle Daten aller Apps in einem Snapshot sichert - meldet healthchecks.io Erfolg oder Fehler, mit den Fehlermeldungen
Schlägt ein Schritt fehl, macht das Skript mit den übrigen weiter und meldet am Ende alle Fehler zusammen.
Die Skripte, die in den Containern laufen, liegen in syslet/scripts/; syslet/restic.cue legt sie in die Container:
| Skript | im Container als | Aufgabe |
|---|---|---|
pg-backup.sh |
/bin/container-backup in jeder Postgres-Datenbank |
schreibt einen Dump nach /var/lib/pgbackup/pg.dump |
pg-restore.sh |
/bin/container-restore in jeder Postgres-Datenbank |
ersetzt die Datenbank durch den Inhalt von pg.dump |
restic-backup.sh |
/bin/restic-backup im Container restic |
sichert alle Pfade aller Apps in einen Snapshot |
restic-restore.sh |
/bin/restic-restore im Container restic |
holt die Pfade einer einzelnen App aus einem Snapshot zurück |
Weil das Backup-Skript in jeder Datenbank gleich heißt, ruft system-backup für jede App dasselbe auf; eine neue App braucht nur einen backup:-Block und eine Zeile im Skript.
Was gesichert wird¶
Gesichert wird nur, was im backup:-Block einer Stack-Datei steht, zum Beispiel am Ende von syslet/werner/stack-hedgedoc.cue:
| Text Only | |
|---|---|
Das heißt: Zur App hedgedoc gehören das ganze Volume hedgedoc-server-uploads und aus dem Volume hedgedoc-postgres-backups die Datei pg.dump.
Aus diesen Blöcken macht syslet/restic.cue zweierlei:
- Jedes genannte Volume wird zusätzlich in den Container
resticeingebunden, und zwar mitz, weil es dann von zwei Containern genutzt wird (siehe SELinux). - Für jede App entsteht eine Liste ihrer Pfade unter
/etc/restic/files-from.d/<app>.confim Containerrestic.
Für das backup:-Feld gibt es keine eigene Seite in der syslet-Doku; es ist ein Addon, das wir in syslet/syslet.cue einschalten (siehe syslet in diesem Repo).
Ein Backup sichert alle Apps zusammen, ein Restore holt eine einzelne App zurück. Ein gemeinsamer Snapshot je Nacht ist einfach und schnell. Zurückgeholt wird dagegen fast immer nur eine App, nach einem Fehler in genau dieser App; die anderen sollen dabei unberührt bleiben.
Datenbanken als Dump¶
Datenbanken sichern wir nicht als Kopie ihrer Dateien, sondern als Dump, den die Datenbank selbst schreibt. Kopiert man die Dateien einer laufenden Datenbank, erwischt man sie mitten im Schreiben. Ein solches Backup ist im schlimmsten Fall nicht wiederherstellbar und damit wertlos; ein Dump ist dagegen immer in sich stimmig. Die Nextcloud schalten wir zusätzlich in den Wartungsmodus, damit ihre Dateien zur Datenbank passen.
Der Dump liegt in einem eigenen Volume (<app>-postgres-backups), hat immer denselben Namen und wird jede Nacht überschrieben.
So muss sich niemand darum kümmern, alte Dumps auf werner zu löschen; die älteren Stände stecken in den Snapshots.
Zwei Apps machen es anders, weil sie eigene Werkzeuge dafür mitbringen, deren Ergebnis ebenfalls in sich stimmig ist:
- Discourse legt selbst Archive mit Datenbank und Uploads an, nach seinem eigenen Zeitplan; gesichert werden diese Archive (
/default/*.tar.gz). - Vaultwarden schreibt mit seinem Befehl
vaultwarden backupeine Kopie seiner SQLite-Datenbank;system-backupruft ihn über/bin/container-backupauf.
Beide setzen Datum und Uhrzeit in den Dateinamen, und das lässt sich nicht abschalten.
Eine Datenbank auf einen beliebigen Zeitpunkt zwischen zwei Backups zurückzusetzen (Point-in-Time-Recovery), können wir nicht; zurück geht es nur auf den Stand eines nächtlichen Laufs.
Was bewusst nicht gesichert wird¶
Caddy.
Die Volumes caddy-data und caddy-config mit den Zertifikaten und dem Konto bei Let's Encrypt stehen in keinem backup:-Block.
Gehen sie verloren, beantragt Caddy alle Zertifikate einfach neu (siehe Caddy).
Let's Encrypt begrenzt zwar, wie viele Zertifikate man in kurzer Zeit bekommt, aber das spielt keine Rolle, weil die Caddy-Volumes so gut wie nie verloren gehen.
containerhost.
Auf containerhost ist der restic-Container abgeschaltet, und kein Stack hat einen backup:-Block (siehe containerhost).
Apps, die noch nicht live sind.
Engelsystem und pretix haben noch keinen backup:-Block.
Aufbewahrung¶
Alte Snapshots räumt Backrest auf containerhost automatisch auf: Es entfernt Snapshots, die nach den eingestellten Regeln nicht mehr gebraucht werden (forget), und gibt dann den Platz frei, den nur sie belegt haben (prune).
Wie lange Snapshots aufbewahrt werden, ist in Backrest selbst eingestellt, über seine Web-Oberfläche; die Einstellungen liegen im Volume backrest-config und nicht im Repo.
Im Repo steht nur, wie der Container läuft (syslet/containerhost/stack-resticserver.cue).
Zum Aufräumen fasst Backrest die Snapshots nach Host und Tags zusammen, nicht wie restic standardmäßig nach Host und gesicherten Pfaden.
Unsere Pfade ändern sich nämlich bei jedem Lauf, weil die Pfadlisten Platzhalter wie *.tar.gz enthalten und die Archive von Discourse und Vaultwarden das Datum im Namen tragen.
Jeder Snapshot hätte dann seine eigene Gruppe, und es würde nie etwas aufgeräumt.
Die Tags sind dagegen bei jedem Lauf gleich; sie setzt restic-backup.sh (plan:… und created-by:…, Werte aus BACKREST_PLAN und BACKREST_CREATED_BY in infra-backup.cue).
Von Hand ist nichts zu tun. Nur falls Backrest einmal nicht aufräumt, geht es von Hand: Alte Snapshots von Hand aufräumen.
Überwachung¶
system-backup meldet jeden Lauf an healthchecks.io, einen Managed Service für die Überwachung regelmäßiger Aufgaben.
healthchecks.io schickt eine E-Mail an it-admin@garage-lab.de, wenn
- ein Lauf mit Fehlern endet, oder
- zur erwarteten Zeit gar kein Lauf stattfindet, etwa weil werner aus ist oder der Timer nicht läuft.
So muss niemand regelmäßig von Hand nach den Backups sehen.
Eingerichtet ist die Überwachung in der Web-Oberfläche von healthchecks.io (siehe Cronjob-Monitoring). Was bei einer Meldung zu tun ist, steht in Wenn healthchecks.io einen Fehler meldet.