Zum Inhalt sprangen

Backup, Restauratioun op ene Zäitpunkt an den Restauratiounstest

Quire sécheren, op ene Zäitpunkt zrécksetzen an dat mam Restauratiounstest beweisen.

Als Markdown weisen

De Konzept ass docs/architecture/23-ops.md Kapitel 8. Dëst ass d’Operatiounsunweisung fir d’Docker-Compose-Versioun. En ass geschriwwen, fir vun engem gefollegt ze ginn, deen en net geschriwwen huet; wann e Schrëtt net kloer ass, ass dat en Defekt an dësem Dokument.

Wat geschützt gëtt, a wéi

Ressource Wéi Wou
D’Datebank WAL lückenlos archivéiert, héichstens all 60 Sekonnen, zënter dem éischten Boot pgwal-Volume
D’Datebank Basisbackup mat pg_basebackup, standardméisseg deeglech (backup-scheduler) pgbackup-Volume
D’Datebank Verschlësselt Kopien vun de Basisbackups an dem WAL, all fënnef Minutten (backup-offsite) En getrennte Späicher, deen Dir nennt
Fichieren D’files-Volume. Kopéiert se mat Ärem Backup-Tool vum Host, oder benotzt Objektspäicher mat Versiounen files-Volume
Geheimnisser docker/.env, virun allem QUIRE_MASTER_KEY (an all QUIRE_MASTER_KEY_RETIRED, déi nach am Gebrauch sinn), QUIRE_BACKUP_ENCRYPTION_KEY, an docker/secrets/audit-signing-key.pem Halt eng Kopie ausserhalb vun dësem Host
Sichindexen, Cache, Renderungen Net gesichert; nei gebaut

Ziler: en Erhuelungspunkt, deen héichstens 60 Sekonnen no dem Ausfalen läit, an eng Restauratioun bannendrem 60 Minutten fir eng 500 GB-Datebank.

Zwee Feler sinn heefeg. Wann eng Datebank ouni hir Fichieren restauréiert gëtt, sinn d’Säiten kaputt. Eng Datebank, déi ouni QUIRE_MASTER_KEY restauréiert gëtt, kann d’Zougangsdaten fir SSO, Webhooks an Integratiounen, déi se hält, net entschlësselen; bis eng Rotatioun vum Master-Schlëssel ouni ongeléist Wäerter fäerig ass (key-rotation.md), dozou gehéieren och déi pensionéiert Schlësselen. Béid sinn Deel vum Backup.

Backups huelen

E Basisbackup vum ganzz Cluster:

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

En hält déi neisten QUIRE_BACKUP_KEEP Basisbackups (standardméisseg 5) an de WAL ewechschneidet, deen net méi gebraucht gëtt, sou datt d’Archiv net ouni Grenz wuessen kann. Plangt en deeglech mat cron oder engem systemd-Timer op dem Host:

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

Oder loosst de Stack en plangen: de backup-Profile féiert backup-scheduler aus, deen all QUIRE_BACKUP_INTERVAL_HOURS (standardméisseg 24) en Basisbackup mécht, an backup-offsite, deen duerno beschriwwe gëtt.

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

Verschlësselt Kopien ausserhalb vum Host

Béid Volumes liewen op deemselwechten Host wéi d’Datebank, an e Backup op der Maschinn, déi ausgefall ass, ass keen Backup. backup-offsite kopéiert all Basisbackup an all archivéierten WAL-Segment iwwer de Späicherport, verschlësselt, an hält se do ënner Retentioun:

  • Verschlësselung. AES-256-GCM mat QUIRE_BACKUP_ENCRYPTION_KEY (oder dem Fichier, deen duerch QUIRE_BACKUP_ENCRYPTION_KEY_FILE benannt gëtt): 32 Bytes, vu openssl rand -hex 32. All Fichier huet seng eegen Nonce an en Tag fir d’Authentifikatioun, sou datt eng Kopie ouni de Schlëssel net liesbar ass an all Ännerung dran erkannt gëtt. Halt de Schlëssel mat QUIRE_MASTER_KEY, weit vun dësem Host an weit vum Späicher fir d’Backups. Ouni de Schlëssel gëtt et keng Restauratioun.
  • Wou. QUIRE_BACKUP_STORAGE_DRIVER ass s3, azure oder local (eng Fern-Disk, déi op QUIRE_BACKUP_STORAGE_ROOT mountéiert ass). D’Astellungen sinn déi vum Fichierspäicher mat engem QUIRE_BACKUP_-Präfix: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET, QUIRE_BACKUP_S3_ACCESS_KEY_ID, an dergleichen. Benotzt en aneren Bucket an, idealerweis, en aneren Kont wéi deen fir d’Fichieren, mat Zougangsdaten, déi schreiwen awer net läschen kënnen, wann de Ubidder et erlaabt.
  • Retentioun. Déi neisten QUIRE_BACKUP_OFFSITE_KEEP Basisbackups (standardméisseg QUIRE_BACKUP_KEEP, soss 7) an de WAL, deen den alste vun hinne braucht; méi al Ensembles an Segmente ginn aus dem Späicher geläscht.
  • Wéini. All QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (standardméisseg 300). Dat Iwwerbréngen ass idempotent: wat scho gespäichert ass gëtt iwwersprongen, an e Basisbackup zielt eréischt dann als gespäichert, wann seng ManifestDatei als leschts geschriwwen ass.

D’Selwecht Kommando leeft vun Hand aus:

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

Fir op engem neie Host ze restauréieren, huelt éischten e Backup-Saz zréck, a fuert dunn déi Schrëtt hei drënner mat dem erofgeluedenen Ordner amplaz der pgbackup-Volume an dem erofgeluedenen wal-archive amplaz pgwal:

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

Restauratioun op ene Zäitpunkt

Benotzt dëst, wann Donnéeën verluer gi sinn: eng schlecht Importatioun, e geläscht Cours, eng Contract-Migratioun, déi Dir réckgängeg maache musst. En ersetzt d’Datebank, déi elo benotzt gëtt, also probéiert en éischte mam Test hei drënner.

  1. Wielt déi Zäit, an UTC, grad virun dem Schied: 2026-09-24 09:30:00+00. D’Audit-Log (/admin/audit) weist normalerweis de Moment.
  2. Stoppt alles, wat schreift: docker compose -f docker/compose.yaml stop web content worker scheduler collab
  3. Halt de beschiedegten Cluster, bis d’Restauratioun verifizéiert ass:
    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. Packt de neisten Basisbackup, deen méi al ass wéi d’Zilzäit, an den Donnéeë-Volume an frot no enger geziilter Erhuelung:
    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. Erhuelen: start Postgres eemol mat den Astellungen fir d’Erhuelung, vun engem Compose-Override, sou datt déi normal Datei net beréiert gëtt:
    # 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]
    Den Override ersetzt dat ganz Kommando, sou datt en déi zwee Astellungen erëmhëlt, vun deenen d’Erhuelung ofhänkt: max_connections net méi niddereg wéi beim Primä (soss brécht d’Erhuelung mat “insufficient parameter settings”) an de mountéierte 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. Kontrolléiert en, ier een erangeet gëtt: d’Auditkette (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts), an datt d’Donnéeën, déi verluer warn, erëm do sinn.
  7. Zeréck normal: docker compose -f docker/compose.yaml up -d. Dat start Postgres nei op mat Archivéierung un, an eng nei WAL-Zäitlinn fänkt un. Huelt direkt en neien Basisbackup.

De Projetnumm quire steet viru all Volume; docker volume ls weist d’exakt Nimm.

De geplanten Verifizéierungstest

backup-offsite féiert och all QUIRE_BACKUP_DRILL_INTERVAL_HOURS (standardméisseg 168, wöchentlech) e Test aus, an nach eng Kéier beim nächste Mol, nodeems en feelgeschloen ass. En holt de neisten Basisbackup, deen ausserhalb vum Host läit, an all WAL-Segment duerno, entschlësselt jiddereen (wat beweist, datt de Schlëssel se nach ëmmer opmaacht an näischt verännert ass), vergläicht all Fichier mat senger ManifestDatei, kontrolléiert, datt d’Archiv e Postgres-Donnéeë-Ordner ass, an kontrolléiert, datt de WAL zënter dem Backup keen Lach huet. De Bericht gëtt am Späicher an am Log vum Service geschriwwen, als reports/drill-<time>.json; e feelgeschloener Test nennt den Fichier oder den éischte fehlenden Segment.

De Restauratiounstest

E Backup, deen nach ni restauréiert gouf, ass keen Backup. De Test restauréiert de neisten kompletten Basisbackup, deen virun dem Zil gemaach gouf, zesumme mat dem WAL-Archiv, an en temporäre Postgres, deen näischt mam lopende Postgres deelt, a beweist d’Resultat:

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

D’Zilzäit ass UTC a genauso an dëser Form. Si brauch e Basisbackup, deen méi al ass wéi si, an archivéierte WAL, deen no där Zäit archivéiert ass: bei enger neier Installatioun hëlt een e Basisbackup a waart, bis den nächsten Archivsegment dran ass (höchstens eng Minutt mat Schreiwen), ier een eng Zilzäit wielt, déi no dem Backup läit. De Test braucht Docker an bash op dem Host, soss näischt.

All Schrëtt feelt den Test:

  1. Erhuelbarkeet: de Test-Cluster spillt bis op d’Zilzäit an opet sech.
  2. Vollstännegkeet: d’Zuel vun den Zeilen vun all Tabell, verglach mat der Datebank, déi elo läuft (tooling/restore-drill). D’Datebank ass weidergelaaf zënter der Zilzäit, sou datt eng Tabell sech ëm méi kann ënnerscheeden wéi 500 Zeilen oder eng Zéngtel vun hirer Gréisst, an béide Richtungen (Schreiwen maachen, datt se zréckbleiwen, Läschtungen maachen, datt d’Restauratioun méi enthält); eng Partition, déi no der Zilzäit erstallt gëtt, ass keng verluer Tabell. Verbreet d’Toleranz bei enger méi vill benotzter Installatioun mat QUIRE_DRILL_MAX_BEHIND an QUIRE_DRILL_MAX_DRIFT_RATIO. Eng Tabell, déi feelt oder eidel gemaach ass, brécht den Test.
  3. Integritéit: d’Audit-Hash-Kette gëtt op der restauréierter Kopie verifizéiert.
  4. Benotzbarkeet: d’Applikatiounsroll lies iwwer d’Sécherheet op Zeilniveau.
  5. Zäit: vum Start un bis den Test grénge gëtt, géint QUIRE_DRILL_RTO_SECONDS (standardméisseg 3600).

En schreift ni an d’Datebank, déi elo läuft, oder an hir Volumes: d’Volume fir Backups an WAL ginn nëmmen fir Liesen mountéiert an de Test-Cluster gëtt um Enn ewechgeholl, grénge oder net.

Setzt QUIRE_DRILL_REPORT op e Wee, fir en JSON-Bericht ze schreiwen, grénge oder net, a féiert en aus engem Plang vum Docker-Host aus:

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éiert en all Mount an ier all Upgrade aus. E feelgeschloener Test blockéiert den Upgrade. All dräi Méint loosst een een, deen dëst Runbook net geschriwwen huet, eng echt Restauratioun op ene Zäitpunkt op engem Reserve-Host maachen, mat nëmmen dësem Dokument.

Fichieren

Lokal Fichieren liewen am files-Volume, séiert se zesumme mat der Datebank, zur selwechter Zäit, a restauréiert béid zesummen:

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 .

Mat Objektspäicher aktivéiert Dir d’Versiounéierung vun de Buckets an hält d’Versioune vun den leschten 35 Deeg; d’Restauratioun op ene Zäitpunkt fir Fichieren ass dann déi vum Bucket selwer.

Navigatioun

Tippt fir ze sichen…

↑↓ navigéieren↵ auswielenEsc zoumaachen