Langkau ke kandungan

Sandaran, pemulihan pada titik masa dan latihan pemulihan

Sandarkan Quire, pulihkannya ke satu titik masa, dan buktikannya dengan latihan pemulihan.

Reka bentuknya ialah docs/architecture/23-ops.md seksyen 8. Ini ialah buku panduan untuk produk Docker Compose. Ia ditulis untuk diikuti oleh seseorang yang tidak menulisnya; jika sesuatu langkah tidak jelas, itu ialah satu kecacatan dalam dokumen ini.

Apa yang dilindungi, dan bagaimana

Aset Cara Di mana
Pangkalan data WAL diarkibkan berterusan, paling banyak setiap 60 saat, daripada but pertama Volum pgwal
Pangkalan data Sandaran asas dengan pg_basebackup, harian secara lalai (backup-scheduler) Volum pgbackup
Pangkalan data Salinan bersulit daripada sandaran asas dan WAL, setiap lima minit (backup-offsite) Sebuah stor berasingan yang anda namakan
Fail Volum files. Salin dengan alat sandaran hos anda, atau gunakan storan objek berversi Volum files
Rahsia docker/.env, di atas segalanya QUIRE_MASTER_KEY (dan mana-mana QUIRE_MASTER_KEY_RETIRED yang masih digunakan), QUIRE_BACKUP_ENCRYPTION_KEY, dan docker/secrets/audit-signing-key.pem Simpan satu salinan di luar hos ini
Indeks carian, cache, rendition Tidak disandarkan; dibina semula

Sasaran: satu titik pemulihan dalam 60 saat selepas kegagalan, dan satu pemulihan dalam 60 minit untuk sebuah pangkalan data 500 GB.

Dua kesilapan adalah biasa. Sebuah pangkalan data yang dipulihkan tanpa failnya memaparkan halaman yang rosak. Sebuah pangkalan data yang dipulihkan tanpa QUIRE_MASTER_KEY tidak dapat menyahsulit kelayakan SSO, webhook dan integrasi yang disimpannya; sehinggalah satu pusingan kunci induk selesai tanpa apa-apa yang tidak selesai (key-rotation.md), itu termasuk kunci yang bersara. Kedua-duanya adalah sebahagian daripada sandaran itu.

Mengambil sandaran

Satu sandaran asas untuk seluruh kelompok:

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

Ia mengekalkan QUIRE_BACKUP_KEEP sandaran asas terbaharu (lalai 5) dan memangkas WAL yang tidak lagi diperlukan oleh yang paling lama, supaya arkif tidak boleh membesar tanpa batas. Jadualkannya harian dengan cron atau satu pemasa systemd pada hos:

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

Atau biarkan tindanan itu menjadualkannya: profil backup menjalankan backup-scheduler, yang mengambil satu sandaran asas setiap QUIRE_BACKUP_INTERVAL_HOURS (lalai 24), dan backup-offsite, yang diterangkan seterusnya.

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

Salinan bersulit di luar hos

Kedua-dua volum berada pada hos yang sama dengan pangkalan data, dan sebuah sandaran pada mesin yang gagal bukanlah satu sandaran. backup-offsite menyalin setiap sandaran asas dan setiap segmen WAL yang diarkibkan ke sebuah stor berasingan melalui port storan, disulitkan, dan menyimpannya di sana di bawah pengekalan:

  • Penyulitan. AES-256-GCM dengan QUIRE_BACKUP_ENCRYPTION_KEY (atau fail yang dinamakan oleh QUIRE_BACKUP_ENCRYPTION_KEY_FILE): 32 bait, daripada openssl rand -hex 32. Setiap fail mempunyai nonce sendiri dan satu tag pengesahan, jadi sebuah salinan tidak boleh dibaca tanpa kunci dan sebarang perubatan kepadanya dikesan. Simpan kunci itu bersama QUIRE_MASTER_KEY, jauh daripada hos ini dan jauh daripada stor sandaran. Tanpa kunci tiada pemulihan.
  • Di mana. QUIRE_BACKUP_STORAGE_DRIVER ialah s3, azure atau local (sebuah cakera jauh yang dilekatkan pada QUIRE_BACKUP_STORAGE_ROOT). Tetapannya ialah tetapan storan fail dengan awalan QUIRE_BACKUP_: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET, QUIRE_BACKUP_S3_ACCESS_KEY_ID, dan sebagainya. Guna baldi yang berbeza dan, sebaiknya, akaun yang berbeza daripada fail, dengan kelayakan yang boleh menulis tetapi tidak memadam jika pembekal membenarkannya.
  • Pengekalan. QUIRE_BACKUP_OFFSITE_KEEP sandaran asas terbaharu (lalai QUIRE_BACKUP_KEEP, selain itu 7) dan WAL yang diperlukan oleh yang paling lama antaranya; set dan segmen yang lebih lama dipadam daripada stor.
  • Bila. Setiap QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (lalai 300). Penghantaran adalah idempoten: apa yang sudah disimpan dilangkau, dan sebuah sandaran asas dikira sebagai disimpan hanya apabila manifesnya ditulis, terakhir.

Arahan yang sama dijalankan secara manual:

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

Untuk memulihkan pada sebuah hos baharu, bawa satu set kembali terlebih dahulu, kemudian ikuti langkah-langkah di bawah dengan direktori yang dibawa sebagai ganti volum pgbackup dan wal-archive yang dibawa sebagai ganti pgwal:

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

Memulihkan ke satu titik masa

Guna ini selepas kehilangan data: satu import yang buruk, sebuah kursus yang dipadam, satu migrasi kontrak yang perlu anda batalkan. Ia menggantikan pangkalan data langsung, jadi latihkannya dengan latihan di bawah terlebih dahulu.

  1. Pilih masa sasaran, dalam UTC, tepat sebelum kerosakan: 2026-09-24 09:30:00+00. Log audit (/admin/audit) biasanya menunjukkan saat itu.
  2. Hentikan segala-galanya yang menulis: docker compose -f docker/compose.yaml stop web content worker scheduler collab
  3. Kekalkan kelompok yang rosak sehingga pemulihan disahkan:
    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. Buka paket sandaran asas terbaharu yang lebih lama daripada masa sasaran ke dalam volum data, dan minta pemulihan bertarget:
    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. Pulihkan: mulakan Postgres sekali dengan tetapan pemulihan, daripada satu override Compose supaya fail biasa tidak disentuh:
    # 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 itu menggantikan seluruh perintah, jadi ia mengulang dua tetapan yang bergantung kepada pemulihan: max_connections tidak lebih rendah daripada yang utama (selain itu pemulihan dibatalkan dengan “insufficient parameter settings”) dan pg_hba.conf yang dilekatkan.
    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. Semaknya sebelum membenarkan sesiapa masuk: rantai audit (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts), dan bahawa data yang hilang telah kembali.
  7. Kembali kepada keadaan biasa: docker compose -f docker/compose.yaml up -d. Ini memulakan semula Postgres dengan pengarkiban hidup, dan satu garis masa WAL baharu bermula. Ambil satu sandaran asas baharu serta-merta.

Nama projek quire menjadi awalan setiap volum; docker volume ls menunjukkan nama yang tepat.

Latihan pengesahan berjadual

backup-offsite juga menjalankan satu latihan setiap QUIRE_BACKUP_DRILL_INTERVAL_HOURS (lalai 168, mingguan), dan sekali lagi pada laluan seterusnya selepas satu gagal. Ia membawa masuk sandaran asas di luar hos terbaharu dan setiap segmen WAL selepasnya, menyahsulit setiap satu (yang membuktikan kunci itu masih membukanya dan tiada apa-apa yang diubah), membandingkan setiap fail dengan manifesnya, menyemak bahawa arkif itu ialah satu direktori data Postgres, dan menyemak bahawa WAL daripada sandaran dan seterusnya tiada jurang. Laporan itu ditulis ke stor sebagai reports/drill-<time>.json dan ke log perkhidmatan; satu latihan yang gagal menamakan fail itu atau segmen hilang yang pertama.

Latihan pemulihan

Sebuah sandaran yang tidak pernah dipulihkan bukanlah satu sandaran. Latihan itu memulihkan sandaran asas lengkap terbaharu yang diambil sebelum masa sasaran, tambah arkif WAL, ke dalam sebuah Postgres sementara yang tidak berkongsi apa-apa dengan yang langsung, dan membuktikan hasilnya:

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

Masa sasaran ialah UTC dalam bentuk tepat itu. Ia memerlukan satu sandaran asas yang lebih lama daripadanya dan WAL yang diarkibkan melampaunya: pada sebuah pemasangan baharu, ambil satu sandaran asas dan tunggu segmen diarkibkan seterusnya (paling banyak seminit dengan tulisan) sebelum memilih satu masa sasaran selepas sandaran itu. Latihan memerlukan Docker dan bash pada hos, tiada apa-apa lagi.

Setiap langkah menggagalkan latihan itu:

  1. Boleh dipulihkan: kelompok sementara itu dimainkan semula ke masa sasaran dan dibuka.
  2. Kelengkapan: kiraan baris setiap jadual terhadap pangkalan data langsung (tooling/restore-drill). Pangkalan data langsung telah bergerak sejak masa sasaran, jadi sebuah jadual mungkin berbeza dengan yang lebih besar antara 500 baris dan sepuluh peratus saiznya, dalam mana-mana arah (tulisan menjadikannya ketinggalan, pemadaman menjadikan pemulihan memegang lebih); satu sekatan yang dicipta selepas masa sasaran bukanlah sebuah jadual yang hilang. Luaskan kelonggaran pada pemasangan yang lebih sibuk dengan QUIRE_DRILL_MAX_BEHIND dan QUIRE_DRILL_MAX_DRIFT_RATIO. Sebuah jadual yang hilang atau dikosongkan menggagalkan.
  3. Integriti: rantai hash audit disahkan pada salinan yang dipulihkan.
  4. Kebolehgunaan: peranan aplikasi membaca melalui keselamatan tahap baris.
  5. Masa: daripada mula hingga hijau, berbanding QUIRE_DRILL_RTO_SECONDS (lalai 3600).

Ia tidak pernah menulis ke pangkalan data langsung atau volumnya: volum sandaran dan WAL dilekatkan hanya-baca dan kelompok sementara itu dibuang di penghujungnya, sama ada lulus atau gagal.

Tetapkan QUIRE_DRILL_REPORT kepada satu laluan untuk memiliki satu laporan JSON ditulis, sama ada lulus atau gagal, dan jalankannya secara berjadual daripada hos 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

Jalankannya setiap bulan dan sebelum setiap naik taraf. Sebuah latihan yang gagal menyekat naik taraf itu. Sekali setiap suku tahun, minta seseorang yang tidak menulis buku panduan ini melakukan satu pemulihan titik masa sebenar pada sebuah hos terbiang, menggunakan dokumen ini sahaja.

Fail

Fail setempat berada dalam volum files. Sandarkannya bersama pangkalan data, pada masa yang sama, dan pulihkan kedua-duanya bersama:

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 .

Dengan storan objek, hidupkan penghantaran versi baldi dan kekalkan 35 hari versi bukan semasa; pemulihan pada titik masa untuk fail kemudian ialah milik baldi itu sendiri.

Navigasi

Taip untuk mencari…

↑↓ navigasi↵ pilihEsc tutup