Перейти к содержанию

Резервное копирование и восстановление PostgreSQL (CNPG)

Инструкция для развёртывания AppSec.Registry в Kubernetes с database.mode: cnpg. Описывает backup метаданных Registry в PostgreSQL (CloudNativePG → S3-совместимое object storage) и восстановление из него.

В примерах используются условные имена:

Плейсхолдер Смысл Типичное значение по умолчанию чарта
<namespace> Namespace release Задаётся при helm install -n …
foo Имя Helm release (Release.Name)
foo-db CNPG Cluster По умолчанию {chart-name}-db; в примерах задано явно: cnpg.cluster.name: foo-db
registry.example.com Ingress host ingress.host
s3://example-bucket/foo-db Путь Barman в object store cnpg.backup.destinationPath
https://s3.example.com Endpoint S3 API cnpg.backup.endpointURL

Подставьте свои значения. Секреты (access keys, пароли) в документацию и тикеты не копируйте.

Что покрывает CNPG backup

Резервная копия PostgreSQL — это метаданные Registry (конфигурация, пользователи, репозитории и т.п. в БД).
Артефакты в blob store (внешний S3, SeaweedFS и т.д.) и PVC core (/registry-data) этим механизмом не копируются — для них нужна отдельная политика (snapshot PVC, backup object store и т.п.).

Требования

  • Helm-чарт appsec-registry с поддержкой cnpg.backup и cnpg.bootstrap.mode (initdb | recovery).
  • database.mode: cnpg, cnpg.cluster.enabled: true.
  • S3-совместимое хранилище и учётные данные с правами на чтение/запись в выбранный bucket/prefix.
  • доступ kubectl к namespace установки.

Оператор CloudNativePG устанавливается subchart’ом (cnpg.enabled: true) или уже присутствует в кластере (cnpg.crds.create: false, если CRD уже есть).


Включение backup

Добавьте в values (фрагмент):

database:
  mode: cnpg

cnpg:
  enabled: true
  cluster:
    enabled: true
    name: foo-db
    instances: 2
    storage:
      size: 50Gi
      storageClass: standard
  backup:
    enabled: true
    retentionPolicy: "30d"
    target: prefer-standby
    destinationPath: s3://example-bucket/foo-db
    endpointURL: https://s3.example.com
    # serverName: ""   # пусто = имя Cluster (foo-db)
    s3secret:
      create: true
      accessKeyId: "<access-key>"
      secretAccessKey: "<secret-key>"
      region: "<region>"
    scheduledBackup:
      enabled: true
      schedule: "0 0 2 * * *"   # cron: секунды минуты часы … (UTC)

Либо создайте Secret заранее и укажите cnpg.backup.secretName / s3secret.create: false. Ключи Secret для CNPG: ACCESS_KEY_ID, SECRET_ACCESS_KEY, region (не AWS_*).

При cnpg.backup.enabled: true чарт создаёт:

  1. Cluster.spec.backup.barmanObjectStore — continuous WAL + base backups.
  2. ScheduledBackup (по умолчанию {cluster}-daily, здесь foo-db-daily).
  3. Secret credentials (если s3secret.create: true).
  4. Role / RoleBinding для ServiceAccount Cluster на чтение Secret.

Штатный bootstrap — cnpg.bootstrap.mode: initdb (default). Режим recovery нужен только при восстановлении из object store.

Проверка

NS=<namespace>

kubectl -n "$NS" get cluster.postgresql.cnpg.io foo-db
kubectl -n "$NS" get scheduledbackup.postgresql.cnpg.io
kubectl -n "$NS" get backup.postgresql.cnpg.io -o wide

kubectl -n "$NS" get cluster.postgresql.cnpg.io foo-db \
  -o jsonpath='{.status.firstRecoverabilityPoint}{"\n"}{.status.lastSuccessfulBackup}{"\n"}'

Ожидаемый результат: phase: Cluster in healthy state, условия Continuous Archiving / Last Backup Succeeded в статусе Cluster, есть completed Backup, ненулевой firstRecoverabilityPoint.

Внеплановый backup

kubectl -n "$NS" apply -f - <<EOF
apiVersion: postgresql.cnpg.io/v1
kind: Backup
metadata:
  name: foo-db-manual
  namespace: ${NS}
spec:
  method: barmanObjectStore
  cluster:
    name: foo-db
EOF

kubectl -n "$NS" get backup foo-db-manual -w

Дождитесь phase: completed.


Важно про restore в CNPG

CloudNativePG не выполняет in-place restore на уже работающий Cluster. Recovery используется для bootstrap нового Cluster из физического бэкапа в object store (имя Cluster может остаться тем же), см. CNPG Recovery.

Типовой порядок:

  1. Остановить запись со стороны Registry (scale down core).
  2. Удалить Cluster и PVC инстансов PostgreSQL.
  3. Создать Cluster с bootstrap.recovery + externalClusters и сразу с spec.backup (другой serverName для новых WAL).
  4. Дождаться Ready → поднять core.

Обычный helm upgrade / sync с bootstrap.mode: initdb не восстанавливает данные из S3 — получится пустая БД.

Bootstrap vs day-2 backup

Секция Роль
cnpg.bootstrap.mode: recovery (+ externalClusters) Только первичный bootstrap Job (когда у Cluster ещё нет данных / latestGeneratedNode == 0). На существующих PVC повторно не гоняется.
cnpg.backupspec.backup.barmanObjectStore Continuous WAL и base backups после restore.

После restore обязательно разделите serverName (документация CNPG):

  • Чтение старого архива: cnpg.bootstrap.recovery.serverName = имя папки исходного бэкапа (часто = имя Cluster, напр. foo-db).
  • Запись новых WAL/backup: cnpg.backup.serverName = другое значение, напр. foo-db-restored.

В противном случае оператор может остановиться с ошибкой вида Expected empty archive. Аннотация cnpg.io/skipEmptyWalArchiveCheck в документации CNPG не рекомендуется как штатный обход.


Восстановление (повреждение БД, стенд в порядке)

Предполагается, что бэкапы уже есть в s3://example-bucket/foo-db, release foo в namespace <namespace> работает, Cluster называется foo-db.

1. Зафиксируйте точку восстановления

NS=<namespace>

kubectl -n "$NS" get backup.postgresql.cnpg.io -o wide
kubectl -n "$NS" get cluster.postgresql.cnpg.io foo-db \
  -o jsonpath='{.status.firstRecoverabilityPoint} -> {.status.lastSuccessfulBackup}{"\n"}'

Для PITR (Point in time recovery, восстановление на определенный момент времени) задайте recoveryTarget.targetTime (UTC) в окне между firstRecoverabilityPoint и последним archived WAL, см. PITR.

2. Остановите запись

Standalone (Deployment):

kubectl -n "$NS" scale deploy/foo --replicas=0

HA (StatefulSet, registryCore.clustered: true):

kubectl -n "$NS" scale sts/foo --replicas=0

Дождитесь завершения pods core.

3. Не удаляйте эти Secret

Secret (типичные имена) Назначение
foo-db-backup-s3 Credentials object store
foo-db-app-user Пароль пользователя БД (должен совпадать с credentials.database.password)
Secret с JDBC/host для core (создаётся чартом при CNPG) Подключение Registry к PostgreSQL

Имена зависят от cnpg.cluster.name / cnpg.backup.secretName / cnpg.database.secretName. Проверьте: kubectl -n "$NS" get secret | grep foo-db.

4. Удалите Cluster и PVC PostgreSQL

kubectl -n "$NS" delete cluster.postgresql.cnpg.io foo-db

# Только PVC инстансов CNPG (имена вида {cluster}-{ordinal}).
# НЕ удаляйте PVC core (данные /registry-data).
kubectl -n "$NS" get pvc
kubectl -n "$NS" delete pvc foo-db-1 foo-db-2

Список PVC уточните командой get pvc — число и суффиксы зависят от cnpg.cluster.instances. Namespace целиком не удаляйте.

5. Поднимите Cluster из бэкапа

Предпочтительно — через values чарта (cnpg.bootstrap.mode: recovery):

cnpg:
  bootstrap:
    mode: recovery
    recovery:
      source: s3-origin
      serverName: foo-db                 # читать исходный архив
      # recoveryTarget:
      #   targetTime: "2026-01-15T01:00:00Z"
  backup:
    enabled: true
    serverName: foo-db-restored          # писать новые WAL/backup
    destinationPath: s3://example-bucket/foo-db
    endpointURL: https://s3.example.com
    # secretName / s3secret — как при штатном backup

Затем:

helm upgrade foo . -n "$NS" -f my-values.yaml

Перед upgrade Cluster и PVC БД уже должны быть удалены. Иначе bootstrap Job на дисках с данными / при latestGeneratedNode != 0 не выполнит recovery как ожидается.

Альтернативаkubectl apply манифеста Cluster с spec.bootstrap.recovery, externalClusters и spec.backup (тот же смысл полей). После аварийного apply синхронизируйте values в Git/Helm, чтобы desired state не откатил Cluster к initdb.

6. Дождитесь Ready

kubectl -n "$NS" get cluster.postgresql.cnpg.io foo-db -w
kubectl -n "$NS" get pods -l cnpg.io/cluster=foo-db

Ожидаемый результат: healthy Cluster, pods инстансов Running, archiving снова успешен.

7. Поднимите Registry

# Deployment
kubectl -n "$NS" scale deploy/foo --replicas=1

# или StatefulSet HA
kubectl -n "$NS" scale sts/foo --replicas=3

Проверьте UI: https://registry.example.com.

8. После restore

  • Оставьте cnpg.backup.serverName: foo-db-restored (и ScheduledBackup), иначе новые бэкапы снова упрутся в непустой архив.
  • bootstrap.mode: recovery на живых PVC повторно bootstrap не запускает; возвращать initdb в values можно только понимая риск: повторный wipe Cluster+PVC при initdb даст пустую БД.
  • Убедитесь, что ScheduledBackup на месте: kubectl -n "$NS" get scheduledbackup.postgresql.cnpg.io.

Восстановление на новый стенд

  1. Убедитесь, что в object store есть бэкапы по нужному serverName / prefix.
  2. Установите release чартом (Secrets, RBAC, ingress, core/UI, CNPG).
  3. Не оставляйте Cluster на пустом initdb с записью core, если цель — разлить бэкап:
    • scale down core;
    • удалите Cluster + PVC PostgreSQL;
    • выставьте cnpg.bootstrap.mode: recovery и cnpg.backup.serverName: foo-db-restoredhelm upgrade.
  4. Ready → scale up core → smoke-test.
  5. Проверьте ScheduledBackup и continuous archiving.

Пароль в Secret пользователя БД должен совпадать с credentials.database.password в values.


Checklist

До инцидента

  • Continuous archiving и регулярные completed Backup.
  • Bucket/prefix доступны, Secret credentials на месте.
  • Версия чарта поддерживает cnpg.bootstrap.mode: recovery и cnpg.backup.serverName.

При restore

  • Выбрана точка (полный backup / PITR).
  • Core scale → 0.
  • Удалены только Cluster + PVC PostgreSQL (не PVC /registry-data).
  • Recovery через values или манифест; serverName чтения = исходный архив.
  • cnpg.backup.serverName = новое значение (…-restored).
  • Cluster Ready → core up → проверка UI.
  • ScheduledBackup работает.

Ограничения и типичные ошибки

  1. Recovery ≠ in-place — только bootstrap нового Cluster из object store.
  2. Expected empty archive — после restore для spec.backup нужен отдельный serverName.
  3. Новые бэкапы = cnpg.backup, не bootstrap. Bootstrap Job только при первичном создании без данных.
  4. Cron ScheduledBackup — в формате CNPG (с секундами), время в UTC.
  5. Retention (retentionPolicy) ограничивает окно PITR (firstRecoverabilityPoint).
  6. PVC core и blob store этой инструкцией не восстанавливаются.
  7. При GitOps (Argo CD и аналоги) на время restore не включайте prune/Replace, пока Cluster/PVC в промежуточном состоянии; desired values должны соответствовать recovery, иначе sync откатит манифест.

Дополнительная информация