Към съдържанието

Резервни копия, възстановяване към момент и проба за възстановяване

Архивирайте Quire, възстановете го към определен момент и докажете възможността с пробно възстановяване.

Преглед като Markdown

Проектът е описан в раздел 8 на docs/architecture/23-ops.md. Това е оперативното ръководство за продукта 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 timer на хост:

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 и други. Използвайте различна кофа и, в идеалния случай, различен акаунт от този за файловете; задайте идентификационни данни за запис, но не и за изтриване, ако доставчикът го позволява.
  • Съхранение. Най-новите базови резервни копия в брой 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 override, така че нормалният файл да остане непроменен:
    # 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 заменя цялата команда, затова включва двете настройки, от които зависи възстановяването: 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. Възможност за възстановяване: временният клъстер възпроизвежда WAL до целта и се стартира.
  2. Пълнота: броят редове във всяка таблица се сравнява с активната база (tooling/restore-drill). Активната база се е променила след целта, затова разликата в таблицата може да е по-голямата стойност измежду 500 реда и една десета от размера ѝ, в която и да е посока (записите я карат да изостава, изтриванията оставят повече във възстановеното копие); дял, създаден след целта, не се счита за загубена таблица. Разширете допустимата разлика в по-натоварена инсталация чрез QUIRE_DRILL_MAX_BEHIND и QUIRE_DRILL_MAX_DRIFT_RATIO. Липсваща или изпразнена таблица води до неуспех.
  3. Цялост: хеш верига за одит се проверява във възстановеното копие.
  4. Използваемост: ролята на приложението чете данните чрез row-level security.
  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 .

При използване на обектно хранилище включете версиониране на кофата и пазете старите версии 35 дни; тогава възстановяването на файлове към определен момент се осигурява от самата кофа.

Навигация

Въведете текст за търсене…

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