Перейти к содержимому
Hogin Hogin
Назад

CloudNativePG в проде: HA, бэкапы и мажорные апгрейды без даунтайма

7 мин чтения

Поднять CloudNativePG — легко: один Cluster CR, и у вас primary с репликами (об этом — вводная статья). Боль начинается на day-2. Автоматический failover, который реально срабатывает под нагрузкой; бэкапы, из которых получается восстановиться; мажорный апгрейд без часа простоя. Именно здесь команды спотыкаются — потому что «работает в демо» и «переживёт инцидент» это разные состояния. Разберём то, что отделяет одно от другого.

Содержание

Открыть содержание

HA — это не «есть реплики», а «failover срабатывает»

Наличие двух реплик само по себе не даёт высокой доступности. HA — это когда при падении primary кластер сам, без человека, выбирает нового мастера и переключает на него трафик за секунды, и при этом не оказывается в состоянии split-brain, где два пода считают себя primary одновременно.

CloudNativePG решает это через оператор как единую точку принятия решений. Мастер ровно один, и знает об этом только оператор — поды не «договариваются» между собой (нет распределённого консенсуса, который сам по себе источник split-brain). При потере primary оператор фенсит старый инстанс (перестаёт направлять на него трафик и не даёт ему вернуться в роли мастера), промоутит наиболее свежую реплику и переключает сервис -rw на неё. Приложение, которое ходит на <cluster>-rw, переживает это как короткий разрыв коннекта.

Здесь критична синхронная репликация. При асинхронной реплике на момент падения мастера часть последних транзакций могла не доехать — и при промоуте они теряются. Синхронный режим требует подтверждения хотя бы от одной реплики до того, как коммит считается успешным:

spec:
  instances: 3
  postgresql:
    synchronous:
      method: any
      number: 1              # ≥1 реплика подтверждает каждый коммит
      dataDurability: required

Компромисс честный: dataDurability: required означает, что при потере кворума синхронных реплик запись встанет — база выбирает консистентность над доступностью. Если для вашего сценария важнее принимать запись даже в одиночку, ставится preferred, и тогда primary продолжит работать асинхронно, приняв риск потерять последние транзакции при аварии. Это ваш выбор, но его надо сделать осознанно, а не по умолчанию.

Failover: оператор фенсит старый primary и промоутит реплику; switchover — плановая версия того же

Отдельно от аварийного failover есть switchover — плановая смена мастера (например, перед выводом ноды на обслуживание). Это то же переключение, но контролируемое: оператор дожидается, пока реплика догонит primary, и только потом меняет роли — с минимальным окном записи. Failover реагирует на аварию, switchover её предотвращает.

Бэкап, из которого нельзя восстановиться, — не бэкап

Главная ошибка day-2 — считать, что настроенный бэкап равен защите. Реальная защита — это регулярно проверенный restore. Бэкап, который никто не разворачивал, — это гипотеза, а не гарантия.

CloudNativePG через Barman Cloud Plugin даёт два уровня: периодический base backup и непрерывный архив WAL. Вместе они дают PITR — восстановление на любую секунду в пределах окна хранения, а не только на момент ночного снапшота. Расписание бэкапов описывается декларативно:

apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
  name: pg-nightly
  namespace: prod
spec:
  schedule: "0 0 2 * * *"       # каждую ночь в 02:00 (cron с секундами)
  backupOwnerReference: self
  cluster:
    name: pg
  method: plugin
  pluginConfiguration:
    name: barman-cloud.cloudnative-pg.io

Ключевой навык — не настроить это, а восстановиться из этого. PITR в CNPG — это новый кластер, который бутстрапится из объектного хранилища на заданную точку во времени. Старый кластер не трогается — это и есть безопасный способ проверить restore или отыграть последствия ошибочного DELETE:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pg-restore
  namespace: prod
spec:
  instances: 3
  storage:
    size: 50Gi
    storageClass: fast-ssd
  bootstrap:
    recovery:
      source: origin
      recoveryTarget:
        targetTime: "2026-07-06T01:45:00Z"   # за 15 минут до инцидента
  externalClusters:
    - name: origin
      plugin:
        name: barman-cloud.cloudnative-pg.io
        parameters:
          barmanObjectName: pg-backups
          serverName: pg                      # имя исходного кластера

Оператор возьмёт последний base backup до targetTime, накатит WAL до нужной секунды и поднимет свежий кластер в этом состоянии. Сделайте такой прогон частью регламента — например, раз в месяц восстанавливать прод в отдельный namespace и сверять контрольные суммы. Тогда в день реального инцидента вы выполняете отрепетированную процедуру, а не читаете документацию под давлением.

PITR: base backup + архив WAL из S3 → новый кластер, восстановленный на точку во времени

Мониторинг: на что реально смотреть

CloudNativePG отдаёт метрики Prometheus из коробки — экспортер встроен в каждый инстанс на порту 9187. Включается это через PodMonitor (или enablePodMonitor: true в спеке кластера, если у вас установлен Prometheus Operator):

spec:
  monitoring:
    enablePodMonitor: true

Метрик много, но в проде реально смотрят на несколько сигналов:

PgBouncer из Pooler отдаёт свои метрики отдельно (порт 9127, префикс cnpg_pgbouncer_) — оттуда важны насыщение пула и число ожидающих клиентов.

Апгрейды: minor — рутина, major — по плану

Minor-обновления (17.2 → 17.4) в CNPG — рутина без даунтайма для приложения. Вы меняете imageName, оператор делает rolling-обновление: сначала по очереди перезапускает реплики, затем делает switchover на уже обновлённую реплику и обновляет бывший primary. Единственное окно — секунды switchover, и то только на запись.

spec:
  imageName: ghcr.io/cloudnative-pg/postgresql:17.4   # было 17.2
  primaryUpdateStrategy: unsupervised                  # switchover сам

С primaryUpdateStrategy: supervised оператор обновит реплики, но переключение primary отдаст вам — вы триггерите switchover в удобное окно командой kubectl cnpg promote.

Мажорный апгрейд (16 → 17) — другая история: меняется формат данных на диске, rolling тут не работает. Есть два подхода:

Выбор — по вашему SLA: если несколько минут планового простоя ночью допустимы, pg_upgrade проще; если нет — blue/green. И то, и другое обязательно репетируется на копии, восстановленной по PITR, — тот самый restore, который вы и так должны проверять.

Ещё две операции day-2: resize диска и rolling restart

Диск под базой рано или поздно кончается. Если StorageClass поддерживает расширение (allowVolumeExpansion: true), увеличение — это правка одного поля; оператор расширит PVC без пересоздания подов:

spec:
  storage:
    size: 100Gi        # было 50Gi — просто увеличиваем

Rolling restart (например, чтобы применить изменение параметра Postgres, требующего рестарта) — тоже операция оператора: он перезапускает инстансы по одному, начиная с реплик, с switchover в конце. Никакого ручного «убить под и молиться».

Сравнение: «просто работает» против «переживёт инцидент»

АспектДемо-кластерПрод day-2
Репликацияасинхронная по умолчаниюсинхронная, осознанный dataDurability
Failover«наверное, сработает»проверен убийством primary под нагрузкой
Бэкапнастроенрегулярно проверяется restore по PITR
WAL-архив«вроде идёт»алерт на остановку архивации
Мониторингдефолтный дашбордалерты на lag, архив, насыщение пула
Мажорный апгрейд«потом разберёмся»отрепетирован на копии, выбран путь
Пул соединенийприложение напрямуюPooler (PgBouncer) перед -rw

Итог

Stateful в Kubernetes — это про day-2, и CloudNativePG закрывает его честно: HA с автоматическим failover и защитой от split-brain, декларативные бэкапы с PITR, встроенные метрики, minor- и мажорные апгрейды без долгого даунтайма. Но оператор даёт механизмы, а не гарантии. Гарантию даёт дисциплина: синхронная репликация выбрана осознанно, алерт на остановку WAL-архива настроен, а restore по PITR проверяется по расписанию, а не в день инцидента. Прод-Postgres в k8s держится не на настроенном backup, а на регулярно проверенном restore — всё остальное CNPG уже сделал за вас.


Поделиться:

Следующая статья
CloudNativePG: prod-ready Postgres в Kubernetes без шаманства