Přejít k obsahu

Zálohy, obnova k určitému okamžiku a restore drill

Zálohujte Quire, obnovte ho k určitému okamžiku a ověřte obnovu pomocí restore drill.

Zobrazit jako Markdown

Architektura je popsána v části 8 dokumentu docs/architecture/23-ops.md. Toto je provozní příručka pro produkt Docker Compose. Je napsaná tak, aby se jí řídil člověk, který ji nevytvořil; pokud některý krok není jasný, jde o chybu dokumentu.

Co je chráněno a jak

Prostředek Jak Kde
Databáze WAL se průběžně archivuje, nejvýše každých 60 sekund, od prvního spuštění Svazek pgwal
Databáze Základní zálohy pomocí pg_basebackup, ve výchozím nastavení denně (backup-scheduler) Svazek pgbackup
Databáze Šifrované kopie základních záloh a WAL každých pět minut (backup-offsite) Samostatné úložiště, které určíte
Soubory Svazek files. Zkopírujte ho zálohovacím nástrojem hostitele nebo použijte verzované objektové úložiště Svazek files
Tajné údaje docker/.env, především QUIRE_MASTER_KEY (a případný stále používaný QUIRE_MASTER_KEY_RETIRED), QUIRE_BACKUP_ENCRYPTION_KEY a docker/secrets/audit-signing-key.pem Kopii uchovávejte mimo tento hostitel
Indexy vyhledávání, cache a rendice Nezálohují se; vytvoří se znovu

Cíle: bod obnovení nejvýše 60 sekund před selháním a obnova databáze o velikosti 500 GB do 60 minut.

Často se opakují dvě chyby. Obnovená databáze bez svých souborů zobrazuje poškozené stránky. Obnovená databáze bez QUIRE_MASTER_KEY nedokáže rozšifrovat uložené přístupové údaje SSO, webhooků a integrací. Dokud obměna hlavního klíče neskončí bez nevyřešených hodnot (key-rotation.md), patří mezi ně i starší klíče. Záloha musí obsahovat obojí.

Vytváření záloh

Základní záloha celého clusteru:

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

Uchová nejnovější základní zálohy v počtu QUIRE_BACKUP_KEEP (výchozí hodnota 5) a smaže starý WAL, který už nejstarší z nich nepotřebuje, takže archiv nemůže růst bez omezení. Naplánujte zálohu na každý den pomocí cron nebo časovače systemd na hostiteli:

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

Nebo plánování svěřte stacku: profil backup spustí backup-scheduler, který vytváří základní zálohu každých QUIRE_BACKUP_INTERVAL_HOURS hodin (výchozí hodnota 24), a také níže popsanou službu backup-offsite.

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

Šifrované kopie mimo hostitele

Oba svazky leží na stejném hostiteli jako databáze a záloha na stroji, který selhal, není záloha. Služba backup-offsite zkopíruje každou základní zálohu a každý archivovaný segment WAL přes port úložiště do samostatného úložiště, zašifruje je a ponechá podle pravidel uchovávání:

  • Šifrování. AES-256-GCM s klíčem QUIRE_BACKUP_ENCRYPTION_KEY (nebo souborem určeným v QUIRE_BACKUP_ENCRYPTION_KEY_FILE): 32 bajtů vytvořených příkazem openssl rand -hex 32. Každý soubor má vlastní nonce a autentizační značku; bez klíče ho nelze přečíst a případnou změnu lze zjistit. Klíč uchovávejte společně s QUIRE_MASTER_KEY, mimo tento hostitel i záložní úložiště. Bez něj nelze obnovit data.
  • Umístění. QUIRE_BACKUP_STORAGE_DRIVER má hodnotu s3, azure nebo local (připojený vzdálený disk v QUIRE_BACKUP_STORAGE_ROOT). Použijte stejná nastavení jako pro souborové úložiště, předponu mají QUIRE_BACKUP_: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET, QUIRE_BACKUP_S3_ACCESS_KEY_ID a další. Zvolte jiný bucket než pro soubory a ideálně také jiný účet. Pokud to poskytovatel umožňuje, použijte údaje, které dovolují zápis, ale nikoli mazání.
  • Uchovávání. Nejnovější počet základních záloh QUIRE_BACKUP_OFFSITE_KEEP (výchozí hodnota QUIRE_BACKUP_KEEP, jinak 7) a WAL, který potřebuje nejstarší z nich; starší sady a segmenty se z úložiště odstraní.
  • Interval. Každých QUIRE_BACKUP_SHIP_INTERVAL_SECONDS sekund (výchozí hodnota 300). Odesílání je idempotentní: již uložené soubory se přeskočí a základní záloha se považuje za uloženou až po zapsání svého manifestu jako poslední položky.

Stejný příkaz lze spustit ručně:

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

Chcete-li obnovovat na novém hostiteli, nejprve na něj přeneste záložní sadu. Potom postupujte podle kroků níže a místo svazku pgbackup použijte získaný adresář a místo archivu wal-archive použijte získaný svazek pgwal:

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

Obnova k určitému okamžiku

Tento postup použijte po ztrátě dat: chybném importu, smazání kurzu nebo migraci odebírající starou podobu, kterou potřebujete vrátit zpět. Nahradí živou databázi, proto si ho nejprve vyzkoušejte pomocí níže uvedeného drill.

  1. Zvolte cílový čas v UTC těsně před poškozením: 2026-09-24 09:30:00+00. Okamžik obvykle najdete v protokolu auditu (/admin/audit).
  2. Zastavte všechny služby, které zapisují: docker compose -f docker/compose.yaml stop web content worker scheduler collab
  3. Zachovejte poškozený cluster, dokud ověření obnovy neskončí:
    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. Rozbalte nejnovější základní zálohu pořízenou před cílovým časem do datového svazku a vyžádejte cílenou obnovu:
    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. Proveďte obnovu: spusťte Postgres jednou s nastavením obnovy v override souboru Compose, aby se běžný soubor nezměnil:
    # 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]
    Override nahrazuje celý příkaz, proto opakuje dvě nastavení nutná pro obnovu: max_connections nesmí být nižší než u primární databáze (jinak se obnova přeruší s chybou „insufficient parameter settings“) a musí se uvést připojený 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. Zkontrolujte obnovenou databázi, než kohokoli pustíte dovnitř: ověřte auditní řetězec (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts) a zkontrolujte, že se ztracená data vrátila.
  7. Vraťte se k běžnému provozu: docker compose -f docker/compose.yaml up -d. Tím se Postgres restartuje se zapnutou archivací a začne nová časová osa WAL. Ihned vytvořte novou základní zálohu.

Každý svazek má na začátku názvu prefix projektu quire; přesné názvy vypíše docker volume ls.

Plánovaný ověřovací drill

Služba backup-offsite provádí drill každých QUIRE_BACKUP_DRILL_INTERVAL_HOURS hodin (výchozí hodnota 168, tedy týdně) a znovu při dalším průchodu po selhání. Stáhne nejnovější základní zálohu mimo hostitele a všechny navazující segmenty WAL, každý dešifruje (ověří, že ho klíč stále odemyká a soubor nebyl změněn), porovná soubory s jejich manifesty, zkontroluje, že archiv obsahuje datový adresář Postgresu, a ověří souvislou posloupnost WAL od zálohy. Report se zapíše do úložiště jako reports/drill-<time>.json a do protokolu služby. Při selhání drill uvede soubor nebo první chybějící segment.

Restore drill

Záloha, která nebyla nikdy obnovena, není ověřenou zálohou. Drill obnoví nejnovější úplnou základní zálohu vytvořenou před cílovým časem a archiv WAL do testovacího Postgresu, který nic nesdílí s živou databází. Pak ověří výsledek:

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

Cílový čas musí být UTC přesně v uvedeném formátu. Potřebuje základní zálohu starší než cíl a archivovaný WAL, který sahá dál. Při nové instalaci vytvořte základní zálohu a před volbou času po jejím dokončení počkejte na další archivovaný segment (při zápisech nejvýše minutu). Drill potřebuje na hostiteli Docker a bash, nic dalšího.

Drill selže při každém nesplněném kroku:

  1. Obnovitelnost: testovací cluster se přehraje k cílovému času a otevře.
  2. Úplnost: porovnají se počty řádků všech tabulek s živou databází (tooling/restore-drill). Živá databáze od cílového času pokročila, takže tabulka se může v obou směrech lišit o větší hodnotu z 500 řádků a desetiny své velikosti (zápisy obnovu opožďují, mazání znamená více řádků v obnovených datech); oddíl vytvořený po cílovém čase se nepovažuje za ztracenou tabulku. V rušnější instalaci toleranci rozšiřte pomocí QUIRE_DRILL_MAX_BEHIND a QUIRE_DRILL_MAX_DRIFT_RATIO. Chybějící nebo prázdná tabulka znamená selhání.
  3. Integrita: auditní hashový řetězec se v obnovené kopii ověří.
  4. Použitelnost: aplikační role dokáže číst data přes row-level security.
  5. Čas: od spuštění po úspěšné dokončení, ve srovnání s QUIRE_DRILL_RTO_SECONDS (výchozí hodnota 3600).

Drill nikdy nezapisuje do živé databáze ani jejích svazků: záložní svazky a WAL se připojí jen pro čtení a testovací cluster se na konci odstraní, ať už drill uspěje, nebo selže.

Nastavte QUIRE_DRILL_REPORT na cestu k JSON reportu, který se zapíše při úspěchu i selhání, a naplánujte spuštění z hostitele Dockeru:

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

Spouštějte ho každý měsíc a před každou aktualizací. Neúspěšný drill aktualizaci blokuje. Jednou za čtvrt roku nechte skutečné obnovení k určitému okamžiku na náhradním hostiteli provést někoho, kdo tento postup nepsal, a dovolte mu použít pouze tento dokument.

Soubory

Místní soubory jsou ve svazku files. Zálohujte ho současně s databází a obnovte oba najednou:

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 .

U objektového úložiště zapněte verzování bucketu a ponechte předchozí verze 35 dnů. Obnovu souborů k určitému okamžiku pak zajistí přímo bucket.

Navigace

Začněte psát a hledejte…

↑↓ procházení↵ vybratEsc zavřít