Поднять CloudNativePG — легко: один Cluster CR, и у вас primary с репликами (об этом — вводная статья). Боль начинается на day-2. Автоматический failover, который реально срабатывает под нагрузкой; бэкапы, из которых получается восстановиться; мажорный апгрейд без часа простоя. Именно здесь команды спотыкаются — потому что «работает в демо» и «переживёт инцидент» это разные состояния. Разберём то, что отделяет одно от другого.
Содержание
Открыть содержание
- HA — это не «есть реплики», а «failover срабатывает»
- Бэкап, из которого нельзя восстановиться, — не бэкап
- Мониторинг: на что реально смотреть
- Апгрейды: minor — рутина, major — по плану
- Ещё две операции day-2: resize диска и rolling restart
- Сравнение: «просто работает» против «переживёт инцидент»
- Итог
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 есть 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 и сверять контрольные суммы. Тогда в день реального инцидента вы выполняете отрепетированную процедуру, а не читаете документацию под давлением.
Мониторинг: на что реально смотреть
CloudNativePG отдаёт метрики Prometheus из коробки — экспортер встроен в каждый инстанс на порту 9187. Включается это через PodMonitor (или enablePodMonitor: true в спеке кластера, если у вас установлен Prometheus Operator):
spec:
monitoring:
enablePodMonitor: true
Метрик много, но в проде реально смотрят на несколько сигналов:
cnpg_pg_replication_lag— лаг репликации в секундах. Растёт — реплики отстают, при failover потеряете больше данных (или запись встанет при синхронном режиме).cnpg_pg_wal/ архивация WAL — если WAL перестал уезжать в S3, ваш PITR-архив молча ломается, а вы узнаете об этом только при попытке восстановиться. Это алерт первого приоритета.cnpg_backends_totalи использование пула — приближение кmax_connectionsозначает, что пора смотреть на PgBouncer, а не поднимать лимит коннектов.cnpg_collector_last_failed_backup_timestamp— время последнего неудачного бэкапа; если оно свежее последнего успешного, защита деградировала.
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 тут не работает. Есть два подхода:
- Декларативный in-place через
pg_upgrade. Вы меняете мажорную версию образа, оператор останавливает кластер, прогоняетpg_upgradeна месте и поднимает уже на новой версии. Быстро (минуты, не зависит от объёма данных так, как дамп), но с коротким плановым даунтаймом на время апгрейда. - Blue/green через логическую репликацию. Поднимаете новый кластер на новой мажорной версии, настраиваете логическую репликацию со старого, ждёте синхронизации и переключаете трафик почти без даунтайма. Дороже по настройке, но окно записи — секунды переключения.
Выбор — по вашему 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 уже сделал за вас.