Gå til indhold

Backup, gendannelse til et bestemt tidspunkt og gendannelsesøvelse

Tag backup af Quire, gendan til et bestemt tidspunkt, og bevis det med gendannelsesøvelsen.

Vis som Markdown

Designet beskrives i afsnit 8 i docs/architecture/23-ops.md. Dette er driftsvejledningen til Docker Compose-produktet. Den er skrevet, så en person, der ikke har skrevet den, kan følge den; hvis et trin er uklart, er det en fejl i dette dokument.

Hvad der beskyttes, og hvordan

Aktiv Metode Placering
Databasen WAL arkiveres løbende, højst hvert 60. sekund, fra første opstart pgwal-volumen
Databasen Grundbackups med pg_basebackup, som standard dagligt (backup-scheduler) pgbackup-volumen
Databasen Krypterede kopier af grundbackups og WAL, hvert femte minut (backup-offsite) Et særskilt lager, du angiver
Filer files-volumen. Kopiér den med værtens backuptool, eller brug objektlagring med versionering files-volumen
Hemmeligheder docker/.env, især QUIRE_MASTER_KEY (og enhver QUIRE_MASTER_KEY_RETIRED, der stadig er i brug), QUIRE_BACKUP_ENCRYPTION_KEY og docker/secrets/audit-signing-key.pem Gem en kopi uden for denne vært
Søgeindekser, cacher, visninger Sikkerhedskopieres ikke; de genopbygges

Mål: et gendannelsespunkt højst 60 sekunder før fejlen og gendannelse inden for 60 minutter for en database på 500 GB.

To fejl går ofte igen. En database, der gendannes uden sine filer, viser ødelagte sider. En database, der gendannes uden QUIRE_MASTER_KEY, kan ikke dekryptere SSO-, webhook- og integrationsoplysningerne, den indeholder. Indtil en rotation af hovednøglen er gennemført uden uløste værdier (key-rotation.md), omfatter det også de udfasede nøgler. Begge dele er en del af backuppen.

Tag backup

En grundbackup af hele klyngen:

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

Den beholder de nyeste QUIRE_BACKUP_KEEP grundbackups (standard 5) og sletter WAL, som den ældste ikke længere behøver, så arkivet ikke vokser uden grænser. Planlæg den dagligt med cron eller en systemd-timer på værten:

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

Eller lad stacken planlægge det: profilen backup starter backup-scheduler, der tager en grundbackup hver QUIRE_BACKUP_INTERVAL_HOURS (standard 24), og backup-offsite, som beskrives næste gang.

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

Krypterede kopier uden for værten

Begge volumener ligger på samme vært som databasen, og en backup på maskinen, der fejlede, er ingen backup. backup-offsite kopierer hver grundbackup og hvert arkiveret WAL-segment til et separat lager via lagerporten, krypterer dem og opbevarer dem efter en opbevaringspolitik:

  • Kryptering. AES-256-GCM med QUIRE_BACKUP_ENCRYPTION_KEY (eller filen angivet af QUIRE_BACKUP_ENCRYPTION_KEY_FILE): 32 byte fra openssl rand -hex 32. Hver fil har sin egen nonce og en autentificeringstag, så kopien ikke kan læses uden nøglen, og enhver ændring opdages. Opbevar nøglen sammen med QUIRE_MASTER_KEY, adskilt fra denne vært og backup-lageret. Uden nøglen kan intet gendannes.
  • Placering. QUIRE_BACKUP_STORAGE_DRIVER er s3, azure eller local (en monteret ekstern disk under QUIRE_BACKUP_STORAGE_ROOT). Indstillingerne er de samme som til fillagring, blot med præfikset QUIRE_BACKUP_: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET, QUIRE_BACKUP_S3_ACCESS_KEY_ID osv. Brug en anden bucket og helst en anden konto end til filerne, med legitimationsoplysninger, der kan skrive, men ikke slette, hvis udbyderen tillader det.
  • Opbevaring. De nyeste QUIRE_BACKUP_OFFSITE_KEEP grundbackups (standard QUIRE_BACKUP_KEEP, ellers 7) og den WAL, som den ældste behøver; ældre sæt og segmenter slettes fra lageret.
  • Tidspunkt. Hver QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (standard 300). Overførslen er idempotent: Det, der allerede findes, springes over, og en grundbackup regnes først som overført, når manifestet er skrevet til sidst.

Den samme kommando kan køres manuelt:

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

Hvis du vil gendanne på en ny vært, skal du først hente et sæt. Følg derefter trinnene nedenfor med den hentede mappe i stedet for pgbackup-volumen og det hentede wal-archive i stedet for pgwal:

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

Gendan til et bestemt tidspunkt

Brug dette efter datatab: en fejlbehæftet import, et slettet kursus eller en kontraktions- migrering, du har brug for at fortryde. Det erstatter databasen, der er i drift, så prøv det først med øvelsen nedenfor.

  1. Vælg måltidspunktet i UTC, lige før skaden: 2026-09-24 09:30:00+00. Auditloggen (/admin/audit) viser normalt tidspunktet.
  2. Stop alle processer, der skriver: docker compose -f docker/compose.yaml stop web content worker scheduler collab
  3. Behold den beskadigede klynge, indtil gendannelsen er kontrolleret:
    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. Pak den nyeste grundbackup, der ligger før måltidspunktet, ud i datavolumen, og anmod om målrettet gendannelse:
    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. Gendan: Start Postgres én gang med gendannelsesindstillingerne fra en Compose-override, så den almindelige fil ikke ændres:
    # 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-filen erstatter hele kommandoen, så den gentager de to indstillinger, gendannelsen afhænger af: max_connections, som ikke må være lavere end den primæres (ellers afbrydes gendannelsen med “insufficient parameter settings”), og den monterede 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ér databasen, før nogen får adgang: auditkæden (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts) og at de manglende data er tilbage.
  7. Gå tilbage til normal drift: docker compose -f docker/compose.yaml up -d. Det genstarter Postgres med arkivering slået til, og en ny WAL-tidslinje begynder. Tag straks en ny grundbackup.

Projektnavnet quire føjes foran hver volumen; docker volume ls viser de præcise navne.

Den planlagte kontroløvelse

backup-offsite kører også en øvelse hver QUIRE_BACKUP_DRILL_INTERVAL_HOURS (standard 168, ugentligt) og igen ved næste gennemgang efter en fejl. Den henter den nyeste offsite-grundbackup og alle WAL-segmenter efter den, dekrypterer hver fil (hvilket beviser, at nøglen stadig kan åbne dem, og at de ikke er ændret), sammenligner hver fil med dens manifest, kontrollerer, at arkivet er en Postgres-datamappe, og kontrollerer, at WAL fra backuppen og frem ikke mangler segmenter. Rapporten skrives til lageret som reports/drill-<time>.json og til tjenestens log. En mislykket øvelse angiver filen eller det første manglende segment.

Gendannelsesøvelsen

En backup, der aldrig er blevet gendannet, er ingen backup. Øvelsen gendanner den nyeste komplette grundbackup fra før måltidspunktet samt WAL-arkivet til en separat Postgres-scratchinstans, der ikke deler noget med den aktive instans, og dokumenterer resultatet:

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

Målet er UTC i præcis dette format. Der kræves en grundbackup fra før målet og arkiveret WAL efter det: På en ny installation skal du tage en grundbackup og vente på næste arkiverede segment (højst ét minut med skrivninger), før du vælger et tidspunkt efter backuppen. Øvelsen kræver Docker og bash på værten, intet andet.

Hvert trin kan få øvelsen til at fejle:

  1. Gendannelsesevne: Scratch-klyngen afspiller til målet og åbner.
  2. Fuldstændighed: Antal rækker i hver tabel sammenlignes med den aktive database (tooling/restore-drill). Den aktive database er ændret siden måltidspunktet, så en tabel må afvige med højst den største af 500 rækker og en tiendedel af dens størrelse, i begge retninger (skrivninger gør den gendannede database bagefter, sletninger betyder, at gendannelsen indeholder flere rækker); en partition oprettet efter målet er ikke en manglende tabel. Forøg tolerancen på en travlere installation med QUIRE_DRILL_MAX_BEHIND og QUIRE_DRILL_MAX_DRIFT_RATIO. En manglende eller tømt tabel medfører fejl.
  3. Integritet: Audit-hashkæden valideres på den gendannede kopi.
  4. Anvendelighed: Applikationsrollen kan læse data via row-level security.
  5. Tid: Tiden fra start til godkendt resultat holdes op mod QUIRE_DRILL_RTO_SECONDS (standard 3600).

Øvelsen skriver aldrig til den aktive database eller dens volumener: backup- og WAL- volumenerne monteres skrivebeskyttet, og scratch-klyngen fjernes til sidst, uanset om den består eller fejler.

Angiv QUIRE_DRILL_REPORT til en sti for at få skrevet en JSON-rapport, uanset om øvelsen består eller fejler, og kør den efter en tidsplan fra Docker-værten:

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

Kør den hver måned og før hver opgradering. En mislykket øvelse blokerer opgraderingen. En gang i kvartalet bør en person, der ikke har skrevet denne vejledning, udføre en reel gendannelse til et bestemt tidspunkt på en reservevært kun ved hjælp af dette dokument.

Filer

Lokale filer ligger i files-volumen. Tag backup af den samtidig med databasen, og gendan begge sammen:

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 .

Hvis du bruger objektlagring, skal du aktivere versionering af bucketten og beholde tidligere versioner i 35 dage; gendannelse af filer til et bestemt tidspunkt håndteres da af buckettens egen funktion.

Navigation

Skriv for at søge…

↑↓ naviger↵ vælgEsc luk