Backup Automation
Техническое руководство из исходного проекта Notty. Примеры, параметры и эксплуатационные ограничения.
Документ описывает автоматизированный backup-контур Notty CMS поверх встроенного
backup API (packages/server/src/lib/backup.ts, /api/admin/backups*). Цель —
RPO в часах, off-site копии и проверяемый restore, без ручных шагов в обычной
жизни.
Что бэкапится
Notty backup snapshot включает (по флагам):
schemas— content-type схемы и расширения.components— компоненты и dynamic zone определения.config— admin settings, API tokens metadata, webhook конфиги.content— записи всех content types.media— метаданные media-library + сами файлы (если storage поддерживает чтение через провайдер).
Snapshot создаётся на стороне сервера и регистрируется в backup catalog; данные файла хранятся через текущий storage provider. Это не замена pg_dump — для DR-уровня managed Postgres + его собственный PITR остаются обязательными. Backup API закрывает application-level снимок, согласованный по schemas/content/media.
Слои защиты
1. Managed PG snapshots (PITR, daily, инфра-уровень) ← обязателен
2. Notty application backups (Notty backup API, расписание) ← основное
3. Off-site copy в S3 / Glacier / GCS Coldline ← на случай region failure
4. Quarterly restore drill ← подтверждает работоспособность
Не пропускайте ни один слой: managed PG спасёт от аппаратной потери, Notty backup — от логических ошибок (например, удалили content type), off-site — от региональной катастрофы, drill — от того, что бэкапы не восстанавливаются.
Расписание
docker-compose / VM
# /etc/cron.d/notty-backup
0 2 * * * notty BASE_URL=http://127.0.0.1:2102 \
ADMIN_TOKEN_FILE=/run/secrets/notty_admin_token \
/opt/notty/deployment/scripts/backup.sh \
>> /var/log/notty/backup.log 2>&1
Скрипт deployment/scripts/backup.sh:
- ходит в
POST /api/admin/backupsсинхронно; - принимает
ADMIN_TOKENилиADMIN_TOKEN_FILE; - считает 2xx-ответ как успех;
- при заданном
BACKUP_S3_BUCKETкопирует*.response.jsonна S3 (для audit/restore handoff); - сам чистит локальные ответы старше
RETENTION_DAYS(snapshot'ы внутри Notty чистит встроенныйbackup.cleanupjob).
Kubernetes
deployment/kubernetes/cronjob-backup.yaml — CronJob 0 2 * * *,
concurrencyPolicy: Forbid, Forbid-overlap, successfulJobsHistoryLimit: 3.
Прокидывает NOTTY_ADMIN_TOKEN из notty-secrets. Это должен быть API token,
созданный super-admin пользователем со scope admin:backups:write или
admin:*. Failure → стандартная алёртинг-цепочка k8s (Prometheus alertmanager
на kube_job_failed).
Через встроенный scheduler Notty
Альтернативно (без внешнего cron'а) — встроенный
POST /api/admin/backups/schedule:
curl -X PUT "$BASE_URL/api/admin/backups/schedule" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"enabled": true,
"cronExpression": "0 2 * * *",
"retentionDays": 30,
"sections": {
"schemas": true, "components": true,
"config": true, "content": true, "media": true
}
}'
Подходит для single-instance: расписание исполняется in-process. Для k8s стоит предпочесть внешний CronJob, чтобы запуск не зависел от того, какой именно pod лидер scheduler'а.
Retention
| Слой | Default retention | Где меняется |
|---|---|---|
| Notty backup catalog | 30 дней | retentionDays в API/расписании |
| Off-site (S3 lifecycle) | 90 дней + Glacier | bucket lifecycle policy на стороне cloud |
| Managed PG PITR | 7-35 дней | настройки provider'а |
| Quarterly archive | 1 год | вручную помечайте важные snapshot'ы как keep |
Off-site копия
Стратегии:
- Через storage provider. Если
STORAGE_TYPE=s3, и snapshot bytes складываются в media-bucket — настройте cross-region replication на bucket'е. Простейший путь. - Pull от backup-runner'а. CronJob, которого нет в Notty: ходит в backup API, выгружает архив (когда такой endpoint включён в вашей сборке) и копирует в отдельный архивный bucket.
- pg_dump в архив. Параллельный CronJob, который снимает application- independent dump и кладёт его в Coldline/Glacier. Самый «тупой и надёжный» вариант.
Минимум — стратегия 1 + managed PG PITR.
Verification (drill)
Каждый квартал, plus после крупного апгрейда:
- Возьмите non-prod кластер.
- Запустите
deployment/scripts/restore.sh --dry-runпротив последнего snapshot'а — проверьте, что dry-run возвращаетsuccess: trueбез diff'ов schema-mismatch. - Затем без
--dry-run. Замерьте RTO. - Прогоните smoke (
deployment/scripts/verify-deploy.sh) и базовые critical path сценарии: логин, чтение и публикация content type, media upload. - Зафиксируйте результат в
docs/testflow/runs/<date>-restore-drill/.
Если drill не сошёлся — заведите инцидент. Бэкап считается рабочим только после успешного drill.
Алёрты
Минимальный набор PromQL-алёртов (см. docs/observability/):
# Бэкап CronJob упал
ALERT NottyBackupJobFailed
IF kube_job_failed{job_name=~"notty-backup-.*"} > 0
FOR 10m
# Backup catalog не пополняется
ALERT NottyBackupStale
IF (time() - notty_last_backup_completed_seconds) > 60*60*36 # >36ч
FOR 30m
Метрика notty_last_backup_completed_seconds поднимается в lib/backup.ts
после успешного снимка (если в вашей сборке метрик она не экспортируется —
заведите алёрт по логам или используйте kube_job_status_succeeded как
прокси).
DR / Restore
Сценарий восстановления — отдельный документ:
runbooks/database-restore.md. Он покрывает
выбор snapshot'а, dry-run, full restore и post-validation.
Источник: docs/operations/backup-automation.md. Снимок документации исходного проекта. Технический справочник сохраняет язык оригинала.