Резервное копирование и восстановление 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 чарт создаёт:
Cluster.spec.backup.barmanObjectStore— continuous WAL + base backups.ScheduledBackup(по умолчанию{cluster}-daily, здесьfoo-db-daily).- Secret credentials (если
s3secret.create: true). - 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.
Типовой порядок:
- Остановить запись со стороны Registry (scale down core).
- Удалить Cluster и PVC инстансов PostgreSQL.
- Создать Cluster с
bootstrap.recovery+externalClustersи сразу сspec.backup(другойserverNameдля новых WAL). - Дождаться 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.backup → spec.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):
HA (StatefulSet, registryCore.clustered: true):
Дождитесь завершения 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
Затем:
Перед 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.
Восстановление на новый стенд¶
- Убедитесь, что в object store есть бэкапы по нужному
serverName/ prefix. - Установите release чартом (Secrets, RBAC, ingress, core/UI, CNPG).
- Не оставляйте Cluster на пустом
initdbс записью core, если цель — разлить бэкап:- scale down core;
- удалите Cluster + PVC PostgreSQL;
- выставьте
cnpg.bootstrap.mode: recoveryиcnpg.backup.serverName: foo-db-restored→helm upgrade.
- Ready → scale up core → smoke-test.
- Проверьте 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 работает.
Ограничения и типичные ошибки¶
- Recovery ≠ in-place — только bootstrap нового Cluster из object store.
Expected empty archive— после restore дляspec.backupнужен отдельныйserverName.- Новые бэкапы =
cnpg.backup, неbootstrap. Bootstrap Job только при первичном создании без данных. - Cron ScheduledBackup — в формате CNPG (с секундами), время в UTC.
- Retention (
retentionPolicy) ограничивает окно PITR (firstRecoverabilityPoint). - PVC core и blob store этой инструкцией не восстанавливаются.
- При GitOps (Argo CD и аналоги) на время restore не включайте prune/Replace, пока Cluster/PVC в промежуточном состоянии; desired values должны соответствовать recovery, иначе sync откатит манифест.