Ir ao contido

Copia de seguridade, recuperación a un punto no tempo e proba de restauración

Fai copias de seguridade de Quire, restáuraas a un punto no tempo e comproba o proceso coa proba de restauración.

Ver como Markdown

O deseño descríbese na sección 8 de docs/architecture/23-ops.md. Este é o manual de operacións do produto Docker Compose. Está redactado para que o poida seguir alguén que non o escribiu; se algún paso non queda claro, é un defecto deste documento.

Que se protexe e como

Recurso Método Lugar
Base de datos Arquivo continuo de WAL, como máximo cada 60 segundos, desde o primeiro inicio Volume pgwal
Base de datos Copias base con pg_basebackup, unha vez ao día de xeito predeterminado (backup-scheduler) Volume pgbackup
Base de datos Copias cifradas das copias base e do WAL cada cinco minutos (backup-offsite) Almacén independente que indiques
Ficheiros Volume files. Copia coas ferramentas do host ou utiliza almacenamento de obxectos con versións Volume files
Segredos docker/.env, sobre todo QUIRE_MASTER_KEY (e calquera QUIRE_MASTER_KEY_RETIRED que aínda se use), QUIRE_BACKUP_ENCRYPTION_KEY e docker/secrets/audit-signing-key.pem Garda unha copia fóra deste host
Índices de busca, cachés e versións derivadas Non se gardan nas copias; recréanse

Obxectivos: perder como máximo os cambios dos últimos 60 segundos e completar a restauración en 60 minutos para unha base de datos de 500 GB.

Dous erros son habituais. Se restauras unha base de datos sen os ficheiros, as páxinas amosaranse danadas. Se a restauras sen QUIRE_MASTER_KEY, non poderás descifrar as credenciais SSO, de webhook e de integración que contén; ata completar unha rotación da clave mestra sen valores sen resolver (key-rotation.md), isto inclúe as claves retiradas. Ambas as cousas forman parte da copia de seguridade.

Facer copias de seguridade

Copia base de todo o clúster:

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

Conserva as copias base máis recentes indicadas por QUIRE_BACKUP_KEEP (5 de xeito predeterminado) e elimina o WAL que xa non precise a copia máis antiga, para que o arquivo non medre sen límite. Programa unha copia diaria cun cron ou temporizador systemd no host:

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

Ou deixa que a pila a programe: o perfil backup inicia backup-scheduler, que crea unha copia base cada QUIRE_BACKUP_INTERVAL_HOURS (24 de xeito predeterminado), e tamén o servizo backup-offsite, que se describe a continuación.

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

Copias cifradas fóra do host

Ambos os volumes están no mesmo host que a base de datos; unha copia na máquina que fallou non serve de nada. backup-offsite copia cada copia base e cada segmento WAL arquivado a un almacén diferente mediante o porto de almacenamento; cifra os datos e aplícalles a política de conservación:

  • Cifrado. AES-256-GCM mediante QUIRE_BACKUP_ENCRYPTION_KEY (ou o ficheiro indicado por QUIRE_BACKUP_ENCRYPTION_KEY_FILE): 32 bytes, xerados con openssl rand -hex 32. Cada ficheiro ten un nonce propio e unha etiqueta de autenticación, polo que non se pode ler a copia sen a clave e detéctase calquera modificación. Garda a clave xunto con QUIRE_MASTER_KEY, lonxe deste host e do almacén das copias. Sen a clave non se pode restaurar.
  • Destino. QUIRE_BACKUP_STORAGE_DRIVER é s3, azure ou local (un disco remoto montado en QUIRE_BACKUP_STORAGE_ROOT). Os axustes son os mesmos que para o almacenamento de ficheiros, co prefixo QUIRE_BACKUP_: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET, QUIRE_BACKUP_S3_ACCESS_KEY_ID e demais. Utiliza un bucket distinto e, se é posible, unha conta diferente da dos ficheiros, con credenciais que poidan escribir pero non eliminar, se o provedor o permite.
  • Conservación. Garda as copias base máis recentes indicadas por QUIRE_BACKUP_OFFSITE_KEEP (o valor de QUIRE_BACKUP_KEEP ou, se non está establecido, 7) e o WAL que precise a máis antiga; as copias e os segmentos anteriores elimínanse do almacén.
  • Frecuencia. Cada QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (300 segundos de xeito predeterminado). O envío é idempotente: omite o que xa está no almacén e só considera gardada unha copia base cando se escribiu o seu manifesto, como último paso.

Tamén podes executar o mesmo comando manualmente:

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

Para restaurar nun host novo, recupera primeiro un conxunto e, a continuación, segue os pasos máis abaixo utilizando o directorio descargado no lugar do volume pgbackup e o arquivo wal-archive descargado no lugar de pgwal:

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

Restaurar a un punto no tempo

Utiliza este procedemento tras unha perda de datos: unha importación incorrecta, un curso eliminado ou unha migración de eliminación que haxa que desfacer. Substitúe a base de datos activa, polo que antes debes ensaiar a operación coa proba máis abaixo.

  1. Escolle a hora de destino, en UTC e xusto antes do dano: 2026-09-24 09:30:00+00. O rexistro de auditoría (/admin/audit) adoita indicar o momento.
  2. Detén todo o que escriba datos: docker compose -f docker/compose.yaml stop web content worker scheduler collab.
  3. Conserva o clúster danado ata verificar a restauración:
    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. Extrae no volume de datos a copia base máis recente anterior ao destino e activa a recuperación para o punto seleccionado:
    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. Recupera: inicia Postgres unha vez cos axustes de recuperación, mediante un ficheiro de substitución de Compose para non modificar o ficheiro 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]
    A substitución reemplaza o comando completo, polo que repite os dous axustes dos que depende a recuperación: max_connections debe ser polo menos igual ao da instancia principal (se non, a recuperación detense cun erro «insufficient parameter settings») e tamén hai que indicar o ficheiro montado 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. Comproba a restauración antes de permitir o acceso: valida a cadea de auditoría (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts) e confirma que se recuperaron os datos perdidos.
  7. Volve á execución normal: docker compose -f docker/compose.yaml up -d. Postgres reiníciase co arquivo activado e comeza unha nova liña temporal de WAL. Fai inmediatamente unha nova copia base.

O nome de proxecto quire úsase como prefixo de cada volume; docker volume ls amosa os nomes exactos.

Proba de verificación programada

backup-offsite tamén executa unha proba cada QUIRE_BACKUP_DRILL_INTERVAL_HOURS (168 horas, é dicir, cada semana, de xeito predeterminado), e repítea na seguinte execución se unha proba falla. Descarga a copia base externa máis recente e todos os segmentos WAL posteriores, descifra cada ficheiro (o que demostra que a clave segue abríndoos e que non se modificaron), compara cada ficheiro co seu manifesto, comproba que o arquivo é un directorio de datos Postgres e verifica que non haxa ocos no WAL desde a copia base. O informe gárdase no almacén como reports/drill-<time>.json e no rexistro do servizo; se a proba falla, indícase o ficheiro ou o primeiro segmento que falta.

Proba de restauración

Unha copia de seguridade que nunca se restaurou non está probada. A proba restaura a copia base completa máis recente anterior ao obxectivo, xunto co arquivo WAL, nunha instancia Postgres temporal que non comparte nada coa instancia activa, e comproba o resultado:

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

O destino debe estar en UTC e seguir exactamente ese formato. Precisa unha copia base anterior e segmentos WAL posteriores: nunha instalación nova, fai unha copia base e agarda ao seguinte segmento arquivado (como máximo un minuto se hai escrituras) antes de escoller un destino posterior á copia. Para executala só fan falta Docker e bash no host.

A proba falla se falla calquera dos seguintes pasos:

  1. Recuperabilidade: o clúster temporal volve ao destino e ábrese correctamente.
  2. Integridade: compárase o número de filas de cada táboa coa base activa (tooling/restore-drill). Como a base activa avanzou desde o momento de destino, unha táboa pode diferir nunha cantidade igual ao maior destes valores: 500 filas ou unha décima parte do seu tamaño, en calquera dirección (as escrituras reducen o número restaurado e as eliminacións fan que a copia teña máis); unha partición creada despois do destino non se considera unha táboa perdida. Podes ampliar a tolerancia nunha instalación con máis actividade mediante QUIRE_DRILL_MAX_BEHIND e QUIRE_DRILL_MAX_DRIFT_RATIO. A ausencia dunha táboa ou que estea baleira fan fallar a proba.
  3. Integridade: a cadea hash de auditoría verifícase na copia restaurada.
  4. Usabilidade: o rol da aplicación pode ler aplicando a seguridade a nivel de fila.
  5. Tempo: mide o tempo desde o inicio ata o resultado correcto e compárao con QUIRE_DRILL_RTO_SECONDS (3600 de xeito predeterminado).

A proba nunca escribe na base de datos activa nin nos seus volumes: os volumes da copia base e do WAL móntanse en modo de só lectura, e o clúster temporal elimínase ao rematar, tanto se a proba pasa como se falla.

Establece QUIRE_DRILL_REPORT cunha ruta para escribir un informe JSON tanto se a proba pasa como se falla, e prográmaa desde o 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

Execútaa cada mes e antes de cada actualización. Se falla, non actualices. Unha vez por trimestre, pide a alguén que non escribise este manual que faga unha restauración real a un punto no tempo nun host de proba utilizando só este documento.

Ficheiros

Os ficheiros locais están no volume files. Fai a copia ao mesmo tempo que a da base de datos e restáuraas xuntas:

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 .

Se utilizas almacenamento de obxectos, activa o versionado do bucket e conserva durante 35 días as versións que deixaron de estar vixentes; a recuperación a un punto no tempo dos ficheiros depende entón do propio bucket.

Navegación

Escribe para buscar…

↑↓ navegar↵ seleccionarEsc pechar