Zum Inhalt springen

Sicherung, Point-in-Time-Wiederherstellung und Wiederherstellungsübung

Sichern Sie Quire, stellen Sie es auf einen früheren Zeitpunkt zurück und weisen Sie die Wiederherstellung mit einer Übung nach.

Als Markdown anzeigen

Das Design ist in Abschnitt 8 von docs/architecture/23-ops.md beschrieben. Dies ist das Runbook für das Docker-Compose-Produkt. Es ist so geschrieben, dass eine Person es befolgen kann, die nicht an seiner Erstellung beteiligt war. Ist ein Schritt unklar, ist das ein Fehler in diesem Dokument.

Was geschützt wird und wie

Element Vorgehen Speicherort
Datenbank WAL wird vom ersten Start an laufend archiviert, höchstens alle 60 Sekunden Volume pgwal
Datenbank Basis-Sicherungen mit pg_basebackup, standardmäßig täglich (backup-scheduler) Volume pgbackup
Datenbank Verschlüsselte Kopien der Basis-Sicherungen und des WAL alle fünf Minuten (backup-offsite) Ein von Ihnen angegebener separater Speicher
Dateien Volume files. Kopieren Sie es mit dem Sicherungswerkzeug Ihres Hosts oder verwenden Sie versionierten Objektspeicher Volume files
Secrets docker/.env, insbesondere QUIRE_MASTER_KEY (und jedes noch verwendete QUIRE_MASTER_KEY_RETIRED), QUIRE_BACKUP_ENCRYPTION_KEY sowie docker/secrets/audit-signing-key.pem Bewahren Sie eine Kopie außerhalb dieses Hosts auf
Suchindizes, Caches, abgeleitete Dateien Werden nicht gesichert, sondern neu erstellt

Ziele: ein Wiederherstellungspunkt höchstens 60 Sekunden vor dem Ausfall und eine Wiederherstellung innerhalb von 60 Minuten bei einer 500-GB-Datenbank.

Zwei Fehler passieren häufig. Eine Datenbank, die ohne ihre Dateien wiederhergestellt wird, zeigt defekte Seiten. Eine Datenbank, die ohne QUIRE_MASTER_KEY wiederhergestellt wird, kann enthaltene Zugangsdaten für SSO, Webhooks und Integrationen nicht entschlüsseln. Bis eine Master-Schlüssel-Rotation ohne ungelöste Werte abgeschlossen ist (key-rotation.md), gilt das auch für ausgemusterte Schlüssel. Beides gehört zur Sicherung.

Sicherungen erstellen

Eine Basissicherung des gesamten Clusters:

docker compose -f docker/compose.yaml --profile backup run --rm backup

Der Vorgang behält die neuesten QUIRE_BACKUP_KEEP Basissicherungen (standardmäßig 5) und löscht WAL, das die älteste davon nicht mehr benötigt, damit das Archiv unbegrenzt wachsen kann. Richten Sie auf dem Host einen täglichen Cronjob oder systemd-Timer ein:

15 2 * * * cd /srv/quire && docker compose -f docker/compose.yaml --profile backup run --rm backup >> /var/log/quire-backup.log 2>&1

Oder überlassen Sie die Planung dem Stack: Das Profil backup startet backup-scheduler, das alle QUIRE_BACKUP_INTERVAL_HOURS (standardmäßig 24) eine Basissicherung erstellt, sowie den unten beschriebenen Dienst backup-offsite.

docker compose -f docker/compose.yaml --profile backup up -d

Verschlüsselte externe Kopien

Beide Volumes liegen auf demselben Host wie die Datenbank; eine Sicherung auf dem ausgefallenen Rechner ist keine Sicherung. backup-offsite kopiert jede Basissicherung und jedes archivierte WAL-Segment über den Speicheranschluss verschlüsselt in einen separaten Speicher und bewahrt sie dort entsprechend der Aufbewahrungsfrist auf:

  • Verschlüsselung. AES-256-GCM mit QUIRE_BACKUP_ENCRYPTION_KEY (oder der in QUIRE_BACKUP_ENCRYPTION_KEY_FILE angegebenen Datei): 32 Byte, erzeugt mit openssl rand -hex 32. Jede Datei hat eine eigene Nonce und ein Authentifizierungs-Tag. Eine Kopie ist daher ohne Schlüssel unlesbar; jede Veränderung wird erkannt. Bewahren Sie den Schlüssel zusammen mit QUIRE_MASTER_KEY, aber getrennt von diesem Host und vom Sicherungsspeicher auf. Ohne diesen Schlüssel ist keine Wiederherstellung möglich.
  • Speicherort. QUIRE_BACKUP_STORAGE_DRIVER ist s3, azure oder local (ein eingehängtes externes Laufwerk unter QUIRE_BACKUP_STORAGE_ROOT). Die Einstellungen entsprechen denen des Dateispeichers und tragen den Präfix QUIRE_BACKUP_: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET, QUIRE_BACKUP_S3_ACCESS_KEY_ID und so weiter. Verwenden Sie einen anderen Bucket und idealerweise ein anderes Konto als für Dateien sowie Zugangsdaten, die Schreiben, aber nach Möglichkeit des Anbieters kein Löschen erlauben.
  • Aufbewahrung. Die neuesten QUIRE_BACKUP_OFFSITE_KEEP Basissicherungen (standardmäßig QUIRE_BACKUP_KEEP, andernfalls 7) und das von der ältesten benötigte WAL. Ältere Sicherungssätze und Segmente werden aus dem Speicher gelöscht.
  • Zeitpunkt. Alle QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (standardmäßig 300). Der Versand ist idempotent: Bereits gespeicherte Inhalte werden übersprungen. Eine Basissicherung gilt erst dann als gespeichert, wenn ihr Manifest zuletzt geschrieben wurde.

Derselbe Vorgang kann manuell ausgeführt werden:

docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts ship
docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts verify

Um auf einem neuen Host wiederherzustellen, holen Sie zunächst einen Sicherungssatz zurück und führen Sie dann die nachfolgenden Schritte aus. Verwenden Sie dabei das abgerufene Verzeichnis anstelle des Volumes pgbackup und das abgerufene wal-archive anstelle von pgwal:

bun apps/worker/src/backups/main.ts fetch base-20260924T021500Z /srv/restore

Wiederherstellung zu einem bestimmten Zeitpunkt

Verwenden Sie dies nach Datenverlust: einem fehlerhaften Import, einem gelöschten Kurs oder einer Vertragsmigration, die rückgängig gemacht werden muss. Die aktive Datenbank wird ersetzt. Üben Sie daher zuerst die nachfolgend beschriebene Wiederherstellung.

  1. Wählen Sie den Zielzeitpunkt in UTC, kurz vor dem Schaden: 2026-09-24 09:30:00+00. Im Audit-Protokoll (/admin/audit) ist der Zeitpunkt üblicherweise zu sehen.
  2. Beenden Sie alle schreibenden Dienste: docker compose -f docker/compose.yaml stop web content worker scheduler collab
  3. Bewahren Sie den beschädigten Cluster auf, bis die Wiederherstellung geprüft wurde:
    docker compose -f docker/compose.yaml stop postgres
    docker run --rm -v quire_postgres18-data:/from -v quire_postgres-damaged:/to alpine cp -a /from/. /to/
  4. Entpacken Sie die neueste Basissicherung vor dem Zielzeitpunkt in das Daten-Volume und richten Sie eine gezielte Wiederherstellung ein:
    docker run --rm -v quire_pgbackup:/backups:ro -v quire_postgres18-data:/var/lib/postgresql postgres:18-alpine sh -euc '
      base="$(ls -1d /backups/base-* | sort | tail -n 1)"   # or the one before the target
      rm -rf /var/lib/postgresql/18/docker && mkdir -p /var/lib/postgresql/18/docker
      tar -xzf "$base/base.tar.gz" -C /var/lib/postgresql/18/docker
      touch /var/lib/postgresql/18/docker/recovery.signal
      chown -R postgres:postgres /var/lib/postgresql/18/docker && chmod 700 /var/lib/postgresql/18/docker'
  5. Führen Sie die Wiederherstellung durch: Starten Sie Postgres einmal mit den Wiederherstellungseinstellungen über eine Compose-Override-Datei, damit die reguläre Datei unverändert bleibt:
    # docker/compose.recover.yaml
    services:
      postgres:
        command: [postgres, -c, "restore_command=cp /var/lib/postgresql/wal-archive/%f %p",
                  -c, "recovery_target_time=2026-09-24 09:30:00+00",
                  -c, recovery_target_action=promote, -c, archive_mode=off,
                  -c, max_connections=200, -c, hba_file=/etc/postgresql/pg_hba.conf]
    Die Override-Datei ersetzt den gesamten Befehl und wiederholt deshalb die beiden benötigten Einstellungen: max_connections darf nicht kleiner als der Wert des Primärservers sein (sonst bricht die Wiederherstellung mit „insufficient parameter settings“ ab) und die eingehängte pg_hba.conf.
    docker compose -f docker/compose.yaml -f docker/compose.recover.yaml up -d postgres
    docker compose -f docker/compose.yaml logs -f postgres   # wait for "database system is ready"
  6. Prüfen Sie die Datenbank, bevor Sie wieder Zugriff ermöglichen: Prüfen Sie die Auditkette (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts) und ob die verlorenen Daten zurück sind.
  7. Kehren Sie zum Normalbetrieb zurück: docker compose -f docker/compose.yaml up -d. Dadurch wird Postgres mit aktivierter Archivierung neu gestartet und eine neue WAL-Zeitlinie begonnen. Erstellen Sie sofort eine neue Basissicherung.

Der Projektname quire wird jedem Volume vorangestellt; docker volume ls zeigt die genauen Namen.

Geplante Verifizierungsübung

backup-offsite führt außerdem alle QUIRE_BACKUP_DRILL_INTERVAL_HOURS (standardmäßig 168, also wöchentlich) eine Übung aus. Nach einem Fehlschlag wird sie beim nächsten Durchlauf erneut ausgeführt. Sie ruft die neueste externe Basissicherung sowie alle danach archivierten WAL-Segmente ab, entschlüsselt sie (damit wird geprüft, dass der Schlüssel sie noch öffnen kann und nichts verändert wurde), vergleicht jede Datei mit ihrem Manifest, prüft, ob das Archiv ein Postgres-Datenverzeichnis ist, und vergewissert sich, dass das WAL seit der Sicherung lückenlos ist. Der Bericht wird als reports/drill-<time>.json im Speicher und im Protokoll des Diensts abgelegt. Bei einem Fehlschlag wird die Datei oder das erste fehlende Segment genannt.

Die Wiederherstellungsübung

Eine Sicherung, die nie wiederhergestellt wurde, ist keine Sicherung. Die Übung stellt die neueste vollständige Basissicherung vor dem Zielzeitpunkt samt WAL-Archiv in einer isolierten Test-Postgres-Instanz wieder her und prüft das Ergebnis:

docker/scripts/restore-drill.sh                                  # to ninety minutes ago
docker/scripts/restore-drill.sh --target "2026-09-24 09:30:00+00"

Der Zielzeitpunkt muss UTC und exakt in diesem Format angegeben sein. Es muss eine ältere Basissicherung und archiviertes WAL nach dem Zielzeitpunkt geben. Erstellen Sie bei einer neuen Installation eine Basissicherung und warten Sie das nächste archivierte Segment ab (bei Schreibvorgängen höchstens eine Minute), bevor Sie einen späteren Zielzeitpunkt wählen. Die Übung benötigt auf dem Host nur Docker und bash.

Jeder der folgenden Schritte kann die Übung fehlschlagen lassen:

  1. Wiederherstellbarkeit: Der Test-Cluster spielt die Protokolle bis zum Zielzeitpunkt ein und lässt sich öffnen.
  2. Vollständigkeit: Die Zeilenanzahl jeder Tabelle wird mit der aktiven Datenbank verglichen (tooling/restore-drill). Die aktive Datenbank hat sich seit dem Zielzeitpunkt weiterentwickelt. Eine Tabelle darf daher in beide Richtungen um den größeren Wert aus 500 Zeilen und einem Zehntel ihrer Größe abweichen (Schreibvorgänge lassen sie zurückfallen, Löschungen führen zu mehr wiederhergestellten Zeilen). Eine Partition, die erst nach dem Zielzeitpunkt erstellt wurde, gilt nicht als verlorene Tabelle. Erhöhen Sie bei stärker ausgelasteten Installationen die Toleranz mit QUIRE_DRILL_MAX_BEHIND und QUIRE_DRILL_MAX_DRIFT_RATIO. Eine fehlende oder geleerte Tabelle lässt die Prüfung fehlschlagen.
  3. Integrität: Die Audit-Hash-Kette wird in der wiederhergestellten Kopie verifiziert.
  4. Nutzbarkeit: Die Anwendungsrolle liest Daten über Row-Level-Security.
  5. Dauer: Zeit vom Start bis zum Erfolg, verglichen mit QUIRE_DRILL_RTO_SECONDS (standardmäßig 3600).

Die Übung schreibt niemals in die aktive Datenbank oder ihre Volumes: Sicherungs- und WAL-Volumes sind schreibgeschützt eingehängt und der Test-Cluster wird am Ende – unabhängig vom Ergebnis – entfernt.

Setzen Sie QUIRE_DRILL_REPORT auf einen Pfad, damit in jedem Fall ein JSON-Bericht geschrieben wird. Richten Sie die Übung auf dem Docker-Host nach einem Zeitplan ein:

30 3 1 * * cd /srv/quire && QUIRE_DRILL_REPORT=/var/log/quire-drill.json docker/scripts/restore-drill.sh >> /var/log/quire-drill.log 2>&1

Führen Sie sie monatlich und vor jedem Upgrade durch. Ein fehlgeschlagener Test verhindert das Upgrade. Lassen Sie einmal pro Quartal eine Person, die dieses Runbook nicht geschrieben hat, auf einem Reserve-Host anhand ausschließlich dieses Dokuments eine echte Point-in-Time-Wiederherstellung durchführen.

Dateien

Lokale Dateien liegen im Volume files. Sichern Sie es gleichzeitig mit der Datenbank und stellen Sie beides gemeinsam wieder her:

docker run --rm -v quire_files:/files:ro -v "$PWD":/out alpine tar -czf /out/files-$(date -u +%Y%m%d).tar.gz -C /files .

Aktivieren Sie bei Objektspeicher die Bucket-Versionierung und bewahren Sie nicht aktuelle Versionen 35 Tage lang auf. Die Point-in-Time-Wiederherstellung von Dateien wird dann durch den Bucket selbst ermöglicht.

Navigation

Suchbegriff eingeben…

↑↓ navigieren↵ auswählenEsc schließen