Ves al contingut

Còpies de seguretat, recuperació en un moment concret i simulacre de restauració

Feu còpies de seguretat de Quire, restaureu-lo a un moment concret i comproveu-ho amb un simulacre.

Mostra com a Markdown

El disseny es descriu a la secció 8 de docs/architecture/23-ops.md. Aquest és el manual operatiu del producte Docker Compose. Està escrit perquè el pugui seguir algú que no l’hagi redactat; si un pas no és clar, és un defecte d’aquest document.

Què es protegeix i com

Recurs Mètode Ubicació
Base de dades WAL arxivat contínuament, com a màxim cada 60 segons, des de la primera arrencada Volum pgwal
Base de dades Còpies base amb pg_basebackup, cada dia per defecte (backup-scheduler) Volum pgbackup
Base de dades Còpies xifrades de les còpies base i del WAL, cada cinc minuts (backup-offsite) Magatzem separat que indiqueu
Fitxers Volum files. Copieu-lo amb l’eina de còpia del vostre host o utilitzeu emmagatzematge d’objectes amb versions Volum files
Secrets docker/.env, sobretot QUIRE_MASTER_KEY (i qualsevol QUIRE_MASTER_KEY_RETIRED que encara s’utilitzi), QUIRE_BACKUP_ENCRYPTION_KEY i docker/secrets/audit-signing-key.pem Conserveu-ne una còpia fora d’aquest host
Índexs de cerca, memòries cau i versions derivades No se’n fa còpia; es tornen a crear

Objectius: recuperar-se fins a un punt situat a menys de 60 segons de l’error i restaurar en menys de 60 minuts una base de dades de 500 GB.

Dos errors són habituals. Si restaureu la base de dades sense els fitxers, les pàgines apareixen malmeses. Si la restaureu sense QUIRE_MASTER_KEY, no podreu desxifrar les credencials SSO, webhook i integració que conté; fins que una rotació de la clau mestra acabi sense res pendent (key-rotation.md), també hi calen les claus retirades. Totes aquestes dades formen part de la còpia de seguretat.

Fer còpies de seguretat

Per fer una còpia base de tot el clúster:

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

Es conserven les QUIRE_BACKUP_KEEP còpies base més recents (5 per defecte) i s’eliminen els arxius WAL que ja no necessita la còpia més antiga, perquè l’arxiu no creixi sense límit. Programeu-ho cada dia amb cron o un temporitzador systemd a l’host:

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

O deixeu que ho programi el stack: el perfil backup executa backup-scheduler, que crea una còpia base cada QUIRE_BACKUP_INTERVAL_HOURS (24 per defecte), i backup-offsite, que s’explica a continuació.

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

Còpies xifrades fora de l’host

Tots dos volums són al mateix host que la base de dades, i una còpia de seguretat a la màquina que ha fallat no és cap còpia de seguretat. El servei backup-offsite copia totes les còpies base i tots els segments WAL arxivats a un magatzem separat a través del port d’emmagatzematge, els xifra i els conserva segons la política de retenció:

  • Xifratge. AES-256-GCM amb QUIRE_BACKUP_ENCRYPTION_KEY (o el fitxer indicat per QUIRE_BACKUP_ENCRYPTION_KEY_FILE): 32 bytes generats amb openssl rand -hex 32. Cada fitxer té un nonce i una etiqueta d’autenticació propis, de manera que no es pot llegir cap còpia sense la clau i qualsevol modificació es detecta. Conserveu la clau amb QUIRE_MASTER_KEY, lluny d’aquest host i del magatzem de còpies. Sense la clau no es pot restaurar.
  • Ubicació. QUIRE_BACKUP_STORAGE_DRIVER pot ser s3, azure o local (disc remot muntat a QUIRE_BACKUP_STORAGE_ROOT). S’utilitzen les opcions d’emmagatzematge de fitxers amb el prefix QUIRE_BACKUP_: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET, QUIRE_BACKUP_S3_ACCESS_KEY_ID i altres. Feu servir un bucket diferent i, idealment, un compte diferent del dels fitxers, amb credencials que puguin escriure però no suprimir, si el proveïdor ho permet.
  • Retenció. Les còpies base més recents, QUIRE_BACKUP_OFFSITE_KEEP (per defecte, QUIRE_BACKUP_KEEP o 7), i el WAL que necessita la més antiga; els conjunts i segments anteriors se suprimeixen del magatzem.
  • Freqüència. Cada QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (300 per defecte). L’enviament és idempotent: se salta el que ja hi ha desat i una còpia base només es considera desada quan el seu manifest s’ha escrit al final.

Les ordres es poden executar manualment:

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

Per restaurar en un host nou, recupereu primer un conjunt i, després, seguiu els passos següents fent servir el directori recuperat en lloc del volum pgbackup i l’arxiu wal-archive recuperat en lloc de pgwal:

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

Restaurar a un moment concret

Feu-ho després d’una pèrdua de dades: una importació incorrecta, un curs suprimit o una migració de retirada que cal desfer. Se substituirà la base de dades activa; per tant, abans practiqueu el simulacre següent.

  1. Trieu l’hora de destinació, en UTC, just abans del dany: 2026-09-24 09:30:00+00. L’hora sol aparèixer al registre d’auditoria (/admin/audit).
  2. Atureu tots els serveis que escriuen: docker compose -f docker/compose.yaml stop web content worker scheduler collab.
  3. Conserveu el clúster danyat fins que s’hagi comprovat la restauració:
    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. Descomprimiu la còpia base més recent anterior a l’hora de destinació al volum de dades i demaneu una recuperació limitada:
    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. Recupereu les dades: inicieu Postgres una vegada amb les opcions de recuperació mitjançant una configuració alternativa de Compose, per no modificar el fitxer habitual:
    # 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]
    La configuració alternativa substitueix l’ordre sencera; per tant, cal repetir les dues opcions necessàries per a la recuperació: max_connections no pot ser inferior al valor del servidor principal (si no, la recuperació s’atura amb «insufficient parameter settings») i cal tornar a muntar 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. Comproveu-ho abans de permetre l’accés: verifiqueu la cadena d’auditoria (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts) i que les dades perdudes s’hagin recuperat.
  7. Torneu a l’operació habitual amb docker compose -f docker/compose.yaml up -d. Això torna a iniciar Postgres amb l’arxivament activat i comença una nova línia de temps del WAL. Feu immediatament una còpia base nova.

El nom de projecte quire és el prefix dels volums; docker volume ls mostra els noms exactes.

Simulacre de verificació programat

backup-offsite també executa un simulacre cada QUIRE_BACKUP_DRILL_INTERVAL_HOURS (168 hores, una vegada a la setmana per defecte) i, després d’un error, el repeteix a la següent execució. Recupera del host extern la còpia base més recent i tots els segments WAL posteriors, els desxifra (per comprovar que la clau encara els pot obrir i que ningú no els ha modificat), compara cada fitxer amb el seu manifest, comprova que l’arxiu és un directori de dades Postgres i que no hi ha cap buit al WAL des de la còpia base. L’informe s’escriu al magatzem com a reports/drill-<time>.json i al registre del servei; si el simulacre falla, n’indica el fitxer o el primer segment absent.

Simulacre de restauració

Una còpia que no s’ha restaurat mai no és cap còpia. El simulacre restaura la còpia base completa més recent anterior a l’hora de destinació i l’arxiu WAL en un Postgres temporal, independent del servidor en ús, i en comprova el resultat:

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

L’hora de destinació ha d’estar en UTC i tenir exactament aquest format. Cal una còpia base anterior i segments WAL arxivats posteriors: en una instal·lació nova, feu una còpia base i espereu que s’arxivi el segment següent (com a màxim un minut si hi ha escriptures) abans de triar una hora posterior a la còpia. El simulacre només necessita Docker i bash a l’host.

Cada pas següent pot fer fallar el simulacre:

  1. Recuperabilitat: el clúster temporal reprodueix les dades fins a l’hora de destinació i s’inicia.
  2. Integritat: es comprova el nombre de files de cada taula en relació amb la base activa (tooling/restore-drill). La base activa ha avançat des de l’hora de destinació, així que cada taula pot diferir en el valor més gran entre 500 files i una dècima part de la seva mida, en totes dues direccions (les escriptures poden deixar-la endarrerida i les supressions poden fer que la còpia restaurada en tingui més). Una partició creada després de l’hora de destinació no compta com una taula perduda. En una instal·lació amb més activitat, augmenteu el marge amb QUIRE_DRILL_MAX_BEHIND i QUIRE_DRILL_MAX_DRIFT_RATIO. Si manca una taula o és buida, el simulacre falla.
  3. Integritat: es verifica la cadena hash d’auditoria de la còpia restaurada.
  4. Usabilitat: el rol d’aplicació pot llegir respectant la seguretat a nivell de fila.
  5. Durada: temps fins al resultat correcte en relació amb QUIRE_DRILL_RTO_SECONDS (3600 per defecte).

No s’escriu mai a la base de dades en ús ni als seus volums: els volums de còpia i WAL es munten només per a lectura, i el clúster temporal s’elimina al final, tant si la prova s’aprova com si falla.

Establiu QUIRE_DRILL_REPORT amb el camí del fitxer per obtenir un informe JSON, tant si s’aprova com si falla. Executeu periòdicament l’script des de l’host 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

Executeu-lo cada mes i abans de cada actualització. Si falla, no es pot actualitzar. Un cop cada trimestre, demaneu a algú que no hagi redactat aquest manual que faci una restauració real a un host auxiliar utilitzant només aquest document.

Fitxers

Els fitxers locals es troben al volum files. Feu-ne una còpia alhora que la base de dades i restaureu-les totes dues juntes:

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 .

Si utilitzeu emmagatzematge d’objectes, activeu el control de versions del bucket i conserveu durant 35 dies les versions substituïdes. Així, la recuperació de fitxers a un moment concret la proporciona el mateix bucket.

Navegació

Escriviu per cercar…

↑↓ per navegar↵ per seleccionarEsc per tancar