Оригинал: CNPG Recipe 17 — PostgreSQL In-Place Major Upgrades — 3 апреля 2025.
В CloudNativePG 1.26 появилась декларативная поддержка in-place major-апгрейдов PostgreSQL через pg_upgrade. Major-апгрейд теперь делается ровно так же, как minor: меняешь imageName в манифесте кластера — оператор делает остальное. Требуется короткий downtime, но операционно это сильно проще, чем blue/green и логическая репликация.
Существующие подходы
CloudNativePG предлагает три декларативных способа major-апгрейдов. Два из них требуют новый кластер и blue/green:
- Импорт через
pg_dump/pg_restore— практично для маленьких БД и тестирования версий, требует downtime на финальный cutover. - Логическая репликация — нулевой downtime независимо от размера БД; правильный выбор для критичных БД и миграций из RDS / других сред (см. Рецепт 5 и Рецепт 15).
- In-place через
pg_upgrade— offline-подход с минимальными операционными хлопотами. Об этом — этот рецепт.
Когда in-place оправдан
Главная мотивация — убрать операционную разницу между minor- и major-апгрейдами для GitOps. Чтобы major выкатить, надо просто отредактировать spec кластера — обновить образ для primary и standby. Особенно ценно, когда у вас «десятки или сотни PostgreSQL-кластеров в одном Kubernetes».
Перед началом
Пробовать in-place можно на любом Kubernetes — включая локальный kind (см. Рецепт 1).
Как это работает
Operand-образ PostgreSQL задаётся через:
.spec.imageName,- либо
ImageCatalog/ClusterImageCatalog.
В рецепте — через imageName.
Стартовая конфигурация (PG 13)
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:13.21-minimal-bullseye
instances: 3
storage:
size: 1Gi
walStorage:
size: 1GiШаги апгрейда
- Корректное выключение. Кластер останавливается, чтобы данные были консистентны. Это и есть downtime.
- Запись статуса. CloudNativePG фиксирует в статусе
Clusterтекущий образ — это необходимо для отката при провале. - Подготовка данных. Запускается Job, который:
- временно копирует бинарники старого PostgreSQL для нужд
pg_upgrade; - инициализирует новый
PGDATAчерезinitdbдля целевой версии; - проверяет, что апгрейд действительно нужен (сравнивает on-disk версии);
- аккуратно ремаппит volume для WAL и tablespaces.
- временно копирует бинарники старого PostgreSQL для нужд
- Запуск
pg_upgrade --link. Используются hard-links — это резко ускоряет миграцию данных и почти не требует доп. места и I/O. - Завершение или откат:
- Успех — оператор заменяет старые data-директории новыми, удаляет PVC реплик, перезапускает кластер.
- Провал — придётся откатить вручную: вернуть старый манифест и удалить upgrade-job.
Пример апгрейда
Проверяем стартовую версию:
kubectl cnpg status pg
kubectl cnpg psql pg -- -qAt -c 'SELECT version()'PostgreSQL 13.21 (Debian 13.21-1.pgdg110+1) on x86_64-pc-linux-gnu, ...Меняем imageName на PostgreSQL 17:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:17.5-minimal-bullseye
instances: 3
storage:
size: 1Gi
walStorage:
size: 1Gikubectl apply -f pg.yamlПроверяем версию:
PostgreSQL 17.5 (Debian 17.5-1.pgdg110+1) on x86_64-pc-linux-gnu, ...Поды после апгрейда:
NAME READY STATUS RESTARTS AGE
pg-1 1/1 Running 0 62s
pg-4 1/1 Running 0 36s
pg-5 1/1 Running 0 15sРеплики
pg-2/pg-3пересоздаются под новыми именамиpg-4/pg-5— оператор пересобирает их черезpg_basebackup.
Ограничения и нюансы
Пересоздание реплик
Реплики восстанавливаются через pg_basebackup. Пока новые реплики не готовы, если упадёт primary — придётся восстанавливаться из бэкапа. А этот бэкап может быть от предыдущей версии PostgreSQL — то есть major-апгрейд придётся повторять. Ситуация маловероятная, но кластер какое-то время в degraded-состоянии по HA.
Митигация: сразу после апгрейда снять физический бэкап.
Совместимость расширений
Все используемые расширения и их версии должны быть в operand-образе целевой версии PostgreSQL. Отсутствие расширения — провал апгрейда. Перед прод-апгрейдом обязательно проверьте совместимость.
Тест на большой БД (2.2 ТБ)
Автор протестировал апгрейд PostgreSQL 16 на тестовой БД из pgbench со scale 150 000 — это 2.2 ТБ.
Стартовый статус:
Cluster Summary
Name default/pg
PostgreSQL Image: ghcr.io/cloudnative-pg/postgresql:16
Primary instance: pg-1
Status: Cluster in healthy state
Instances: 1
Ready instances: 1
Size: 2.2T
Current Write LSN: 1D0/8000000 (Timeline: 1 - WAL File: 00000001000001D000000001)Апгрейд на PostgreSQL 17 завершился за 33 секунды, кластер восстановил полную работоспособность меньше чем за минуту.
После:
Cluster Summary
Name default/pg
PostgreSQL Image: ghcr.io/cloudnative-pg/postgresql:17
Status: Cluster in healthy state
Size: 2.2T
Current Write LSN: 1D0/F404F9E0 (Timeline: 1 - WAL File: 00000001000001D00000003D)Главный вывод: так как используется pg_upgrade --link (hard-links), время апгрейда зависит в основном от количества таблиц, а не от объёма БД.
Выводы
In-place major-апгрейды через pg_upgrade приносят классический PostgreSQL-апгрейд в Kubernetes — декларативно и с минимальными операционными накладными. Downtime есть, но он невелик; зато не нужен blue/green-кластер. Особенно хорошо подходит для «парков» из небольших и средних PostgreSQL.
- Успешный сценарий — кластер работает так же, как если бы
pg_upgradeзапустили на VM. - Откат — поправить манифест и удалить upgrade-job. Continuous backup даёт дополнительную страховку.
- Лучшие use cases — команды, для которых операционная простота важнее zero-downtime. Для критичных систем по-прежнему предпочтительнее логическая репликация (Рецепт 15).