Прескокни до содржината

Резервно копирање, враќање во точка во време и вежба за враќање

Правете резервни копии на Quire, вратете го во точка во време и докажете го со вежбата за враќање.

Прикажи како Markdown

Дизајнот е docs/architecture/23-ops.md оддел 8. Ова е оперативната постапка за производот Docker Compose. Напишана е за да ја следи некој што ја нема напишано; ако некој чекор е нејасен, тоа е мана во овој документ.

Што е заштитено и како

Ресурс Како Каде
Базата на податоци WAL се архивира континуирано, на секои 60 секунди најмногу, од првото подигнување волум pgwal
Базата на податоци Основни резервни копии со pg_basebackup, дневно по стандард (backup-scheduler) волум pgbackup
Базата на податоци Шифрирани копии на основните резервни копии и на WAL, на секои пет минути (backup-offsite) Посебна продавница што ќе ја наведете
Датотеки Волумот files. Копирајте го со алатката за резервно копирање на вашиот домаќин или користете складирање на објекти со верзии волум files
Тајни docker/.env, над сè QUIRE_MASTER_KEY (и сите QUIRE_MASTER_KEY_RETIRED сè уште во употреба), QUIRE_BACKUP_ENCRYPTION_KEY и docker/secrets/audit-signing-key.pem Чувајте копија надвор од овој домаќин
Индекси за пребарување, кеширања, претворања Не се резервирани; се градат повторно

Цели: точка за закрепнување во рок од 60 секунди од отказот и враќање во рок од 60 минути за база од 500 GB.

Две грешки се чести. База вратена без нејзините датотеки прикажува скршени страници. База вратена без QUIRE_MASTER_KEY не може да ги дешифрира потврдите за SSO, веб-куки и интеграции што ги држи; сè додека ротацијата на главниот клуч не заврши без нерешени (key-rotation.md), во тоа вклучува и повлечените клучеви. И двете се дел од резервното копие.

Правење резервни копии

Основна резервна копија на целиот кластер:

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

Таа ги задржува најновите QUIRE_BACKUP_KEEP основни резервни копии (по стандард 5) и го отсекува WAL-от што најстарата повеќе не го бара, така што архивата не може да расте без граница. Закажете ја дневно со cron или со systemd-мерач на домаќинот:

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

Или оставете го стекот да ја закаже: профилот backup го извршува backup-scheduler, кој прави основна резервна копија на секои QUIRE_BACKUP_INTERVAL_HOURS (по стандард 24), и backup-offsite, опишан следно.

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

Шифрирани копии надвор од домаќинот

Двата волума се на истиот домаќин како базата, а резервна копија на машината што отказала не е резервна копија. backup-offsite ја копира секоја основна резервна копија и секој архивиран WAL-сегмент во посебна продавница преку портата за складирање, шифрирано, и ги чува таму под чување:

  • Шифрирање. AES-256-GCM со QUIRE_BACKUP_ENCRYPTION_KEY (или датотеката наведена со QUIRE_BACKUP_ENCRYPTION_KEY_FILE): 32 бајти, од openssl rand -hex 32. Секоја датотека има свој nonce и таг за автентикација, така што копијата е нечитлива без клучот и секоја промена врз неа се детектира. Чувајте го клучот со QUIRE_MASTER_KEY, далеку од овој домаќин и далеку од продавницата за резервни копии. Без клучот нема враќање.
  • Каде. QUIRE_BACKUP_STORAGE_DRIVER е s3, azure или local (монтиран ремотски диск на QUIRE_BACKUP_STORAGE_ROOT). Поставките се оние за складирање на датотеки со префикс QUIRE_BACKUP_: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET, QUIRE_BACKUP_S3_ACCESS_KEY_ID и така натаму. Користете посебен bucket и, по можност, посебна сметка од датотеките, со потврди што можат да запишуваат, но не и да бришат, ако давателот го дозволува тоа.
  • Чување. Најновите QUIRE_BACKUP_OFFSITE_KEEP основни резервни копии (по стандард QUIRE_BACKUP_KEEP, инаку 7) и WAL-от што најстарата од нив го бара; постарите збирови и сегменти се бришат од продавницата.
  • Кога. На секои QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (по стандард 300). Испраќањето е идемпотентно: она што веќе е зачувано се прескокнува, а основната резервна копија се брои како зачувана само откако нејзиниот манифест ќе се запише, последно.

Истата команда се извршува и рачно:

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

За да вратите на нов домаќин, прво вратете збир, па следете ги чекорите подолу со преземената папка наместо волумот pgbackup и преземениот wal-archive наместо pgwal:

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

Враќање во точка во време

Користете го ова по губење податоци: лош увоз, избришан курс, миграција за склучување што треба да се поништи. Тоа ја заменува живата база, затоа прво вежбајте ја со вежбата подолу.

  1. Изберете ја целната временска точка, на UTC, токму пред штетата: 2026-09-24 09:30:00+00. Дневникот за ревизија (/admin/audit) обично го покажува моментот.
  2. Запрете сè што запишува: docker compose -f docker/compose.yaml stop web content worker scheduler collab
  3. Задржете го оштетениот кластер додека враќањето не се провери:
    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. Распакувајте ја најновата основна резервна копија постара од целната во волумот за податоци и побарајте насочено закрепнување:
    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. Закрепнете: стартувајте го Postgres еднаш со поставките за закрепнување, од отфрлување на Compose за да не се допре обичната датотека:
    # 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]
    Отфрлувањето ја заменува целата команда, така што ги повторува двете поставки од кои зависи закрепнувањето: max_connections не пониско од главниот сервер (инаку закрепнувањето се прекинува со „insufficient parameter settings“) и монтираниот 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. Проверете го пред да пуштите некого внатре: низата за ревизија (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts) и дека изгубените податоци се назад.
  7. Вратете се во нормала: docker compose -f docker/compose.yaml up -d. Ова го рестартира Postgres со вклучено архивирање и започнува нова временска линија на WAL. Направете свежа основна резервна копија веднаш.

Името на проектот quire е префикс на секој волум; docker volume ls ги покажува точните имиња.

Закажана вежба за проверка

backup-offsite исто така извршува вежба на секои QUIRE_BACKUP_DRILL_INTERVAL_HOURS (по стандард 168, неделно) и повторно на следниот помин откако една ќе не успее. Таа го презема најновата основна резервна копија надвор од домаќинот и секој WAL-сегмент по неа, ги дешифрира сите (што докажува дека клучот сè уште ги отвора и дека ништо не е изменето), го споредува секоја датотека со нејзиниот манифест, проверува дека архивата е директориум за податоци на Postgres и проверува дека WAL-от од резервната копија натаму нема празнина. Извештајот се запишува во продавницата како reports/drill-<time>.json и во дневникот на сервисот; неуспешната вежба го наведува датотеката или првиот сегмент што недостасува.

Вежба за враќање

Резервна копија што никогаш не е вратена не е резервна копија. Вежбата го враќа најновиот целосен основен збир земен пред целта, плус архивата на WAL, во пробна база на Postgres што не споделува ништо со живата, и го докажува резултатот:

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

Целта е UTC, токму во тој формат. Таа бара основна резервна копија постара од неа и архивиран WAL зад неа: на нова инсталација, направете основна резервна копија и почекајте го следниот архивиран сегмент (најмногу една минута со запишувања) пред да изберете цел после резервната копија. Вежбата бара Docker и bash на домаќинот, ништо друго.

Секој чекор ја не успева вежбата:

  1. Можност за закрепнување: пробниот кластер се репродуцира до целта и се отвора.
  2. Целосност: бројот на редови на секоја табела спроти живата база (tooling/restore-drill). Живата база се поместила од целта, така што табела може да се разликува за поголемото од 500 редови и една десетина од нејзината големина, во која било насока (запишувањата ја прават да заостанува, бришењата прават враќањето да држи повеќе); партиција создадена по целта не е изгубена табела. Проширете ја дозволата на позафатена инсталација со QUIRE_DRILL_MAX_BEHIND и QUIRE_DRILL_MAX_DRIFT_RATIO. Табела што недостасува или е испразнета не успева.
  3. Интегритет: хеш-низата за ревизија се проверува на вратената копија.
  4. Употребливост: улогата на апликацијата чита низ безбедност на ниво на ред.
  5. Време: од почеток до успешно, спроти QUIRE_DRILL_RTO_SECONDS (по стандард 3600).

Таа никогаш не запишува во живата база или нејзините волуми: волумите за резервна копија и WAL се монтирани само за читање, а пробниот кластер се отстранува на крајот, успешно или не.

Поставете го QUIRE_DRILL_REPORT на патека за да се запише JSON-извештај, успешно или не, и извршувајте ја по распоред од домаќинот на Docker:

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

Извршувајте ја месечно и пред секоја надградба. Неуспешната вежба ја блокира надградбата. Еднаш тримесечно, нека некој што ја нема напишано оваа постапка изведе вистинско враќање во точка во време на резервен домаќин, користејќи го само овој документ.

Датотеки

Локалните датотеки се наоѓаат во волумот files. Резервирајте ги со базата, во исто време, и вратете ги и двете заедно:

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 .

Со складирање на објекти, вклучете верзионирање на bucket-от и чувајте 35 дена неверзиски верзии; враќањето во точка во време за датотеки тогаш е сопственото на bucket-от.

Навигација

Внесете текст за пребарување…

↑↓ навигација↵ избериEsc затвори