Практический взгляд на запуск PostgreSQL в Kubernetes с встроенной высокой доступностью, автоматическим failover, бэкапами и облачными операциями с базой данных.
Я попытался сделать некий кук-бук, если заметите ошибки или не точности, то велком в комментарии.
Вступление
Kubernetes изначально проектировался для stateless-нагрузок, поэтому stateful-приложения всегда были операционной головной болью. Управление базами данных требовало от инженеров жонглирования StatefulSets и ручных скриптов, что регулярно приводило к проблемам с хранилищем, низкой производительностью и риску потери данных. В результате многие организации продолжали держать stateful-приложения за пределами Kubernetes.
Да и сам принцип использования БД в Kubernetes не все готовы воспринимать как что-то надежное и применимое в прод.
Первые попытки запускать PostgreSQL в Kubernetes выглядели примерно так: StatefulSets с кривыми init-скриптами, ручное переключение primary при падении ноды, бэкапы через kubectl exec и вечные вопросы «а что произошло со storage при рестарте?». Многие команды в итоге выносили базы наружу (на отдельные VM или managed-сервисы ) и просто не возвращались.
Ситуацию начал менять паттерн Operator, предложенный CoreOS в 2016 году. Идея простая и красивая: берёшь экспертизу опытного DBA (как делать бэкапы, как переключать primary, как апгрейдить без даунтайма) и превращаешь её в код контроллера. В 2017-м в Kubernetes 1.7 появились Custom Resource Definitions — и у операторов появился язык для общения с кластером. EDB воспользовалась этим фундаментом и в 2020 году создала CloudNativePG. В январе 2025-го проект был принят в CNCF Sandbox.
Архитектура CNPG (как он работает изнутри)
Тут нам важно понять ключевое архитектурное решение CNPG: он не использует StatefulSets.
Вместо этого оператор реализует собственный кастомный Pod-контроллер и напрямую управляет Persistent Volume Claims (PVCs). Это даёт несколько преимуществ:
-
Ресайзинг PVC прямо из манифеста — StatefulSet этого не поддерживает
-
Разный порядок обновлений для primary и replica в зависимости от типа операции (например, понижение
max_connectionsтребует сначала обновить primary, а не реплики) -
Согласованное управление несколькими PVC на instance (PGDATA + отдельный WAL volume)
Компоненты
Operator — это Kubernetes-контроллер, который непрерывно сверяет фактическое состояние кластера с желаемым, определённым в манифесте, и автоматически обрабатывает failover, масштабирование и управление конфигурацией. Т.е. он в бесконечном цикле сверяет «как есть» и «как должно быть» и приводит одно к другому
Instance Manager — строенный процесс внутри каждого PostgreSQL Pod. Он управляет жизненным циклом инстанса и коммуницирует с Operator через Kubernetes API. Важно: это не sidecar-контейнер в классическом смысле, процесс встроен в сам Pod с PostgreSQL и именно он принимает решение о промоции реплики в primary при failover.
CRDs, которые CNPG добавляет в Kubernetes
|
CRD |
Назначение |
|---|---|
|
|
Основной ресурс: декларативно определяет и управляет кластером PostgreSQL |
|
|
Декларативно описывает базу данных внутри существующего кластера |
|
|
Управляет масштабируемым слоем доступа к БД через PgBouncer |
|
|
Описывает бэкап по требованию |
|
|
Периодический автоматический бэкап с cron-расписанием |
|
|
Отслеживает состояние кворума кластера при quorum-based failover |
Важно про
FailoverQuorum: Этот ресурс создаётся и управляется оператором автоматически, поэтому не изменяйте его вручную. В версии 1.28 quorum-based failover был переведён из экспериментального в стабильный API и теперь конфигурируется через полеspec.postgresql.synchronous.failoverQuorum.
На основе модели Operator Capability Levels, CNPG реализует подмножество возможностей уровня «Level V — Auto Pilot». На практике это означает: оператор берёт на себя весь операционный цикл — от первичного деплоя до самовосстановления.
Схема того что мы будем разворачивать:
Предварительные требования:
Шаг 1: Установка CNPG Operator (1.28.x)
Способ 1: деплой через манифест
kubectl apply --server-side -f \ https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.28/releases/cnpg-1.28.1.yaml
Проверяем деплой:
kubectl rollout status deployment -n cnpg-system cnpg-controller-manager
Способ 2: через Helm Chart
Если у Вас не установлен Helm Chart, то переходим в доку и ставим.
# Добавить репозиторий CNPGhelm repo add cnpg https://cloudnative-pg.io/charts/# Установить операторhelm install cnpg-operator cnpg/cloudnative-pg --version 0.27.1
Шаг 2: Создание кластера PostgreSQL (CNPG)
Разворачиваем кластер через CRD Cluster, который идёт в комплекте с оператором. Это декларативный способ описать желаемое состояние PostgreSQL-кластера.
# cluster.yamlapiVersion: postgresql.cnpg.io/v1kind: Clustermetadata: name: my-clusterspec: instances: 3 storage: size: 1Gi
Этот манифест создаёт кластер с дефолтным storage class: 1 primary + 2 hot standby реплики. Primary инстанс стримит Write-Ahead Logs (WAL) на standby через нативную потоковую репликацию PostgreSQL.
Режимы репликации
CNPG поддерживает два режима:
1. Асинхронный (по умолчанию) — не ждёт подтверждения от реплик о получении WAL. Минимальная latency записи, минимальный риск потери данных при failover (только транзакции, не успевшие улететь на реплику).
2. Синхронный (настраиваемый) — ждёт кворума реплик с подтверждением получения WAL перед тем, как вернуть COMMIT клиенту. Никакой потери данных при failover, ценой небольшого роста latency.
Если потеря даже одной транзакции недопустима, то нужна синхронная репликация:
# cluster-synchronous.yamlapiVersion: postgresql.cnpg.io/v1kind: Clustermetadata: name: my-synchronous-clusterspec: instances: 3 storage: size: 1Gi postgresql: synchronous: method: any # any = кворум, first = приоритет (required) number: 1 # кол-во standbys, обязанных подтвердить WAL (required)
-
method: any— кворум-based (рекомендуется для большинства HA-сценариев) -
method: first— priority-based (по порядку в списке реплик) -
number— минимальное количество реплик, которые должны ответить доCOMMIT
В версии 1.28 к этому добавился стабильный
spec.postgresql.synchronous.failoverQuorum: true— включает data-driven failover, который не допускает промоции реплики без подтверждения кворума.
Шаг 3: Настройка базы данных
По умолчанию CNPG инициализирует кластер с двумя базами:
-
app— рабочая база приложения. Принадлежит непривилегированному пользователюapp, credentials хранятся в Kubernetes Secret. -
postgres— системная административная база во всех PostgreSQL-инсталляциях, используется оператором для reconciliation. Не удаляйте её, т.к. это сломает оператор
Если стандартное имя app не устраивает, то переопределяется через bootstrap.initdb:
# initdb-secret.yamlapiVersion: v1kind: Secretmetadata: name: initdb-secrettype: kubernetes.io/basic-authdata: username: YWRtaW5fbXl1c2Vy # base64 password: YWRtaW5fcGFzc3dvcmQ= # base64
# initdb-cluster.yamlapiVersion: postgresql.cnpg.io/v1kind: Clustermetadata: name: initdb-clusterspec: instances: 3 bootstrap: initdb: database: custom # app (по умолчанию) owner: myuser # app (по умолчанию) secret: name: initdb-secret storage: size: 1Gi
Чтобы добавить дополнительную базу в уже существующий кластер через CRD Database:
# my-db.yamlapiVersion: postgresql.cnpg.io/v1kind: Databasemetadata: name: my-db # (обязательно)spec: name: my-db # (обязательно) owner: app # или ваш кастомный пользователь (обязательно) cluster: name: my-cluster # должно совпадать с именем Cluster (обязательно)
Шаг 4: Подключение к кластеру
После того как кластер поднялся, оператор создаёт в namespace набор ресурсов. Разберём главные.
Секреты
kubectl get secrets
NAME TYPE DATA AGEmy-cluster-app kubernetes.io/basic-auth 11 1d4hmy-cluster-ca Opaque 2 1d4hmy-cluster-replication kubernetes.io/tls 2 1d4hmy-cluster-server kubernetes.io/tls 2 1d4h
Все данные для подключения хранятся в секрете my-cluster-app. Достать их в читаемом виде можно следующим образом:
kubectl get secret my-cluster-app -o json \ | jq -r '.data | to_entries[] | "\(.key): \(.value | @base64d)"'
Три сервиса (три точки входа)
kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEmy-cluster-r ClusterIP 10.105.200.51 <none> 5432/TCP 1d4hmy-cluster-ro ClusterIP 10.97.105.14 <none> 5432/TCP 1d4hmy-cluster-rw ClusterIP 10.99.140.210 <none> 5432/TCP 1d4h
Важно то, что оператор создаёт три сервиса для каждого кластера, у каждого сервиса своя семантика, и правильный выбор напрямую влияет на поведение приложения при failover:
|
Сервис |
Куда смотрит |
Для чего |
|---|---|---|
|
|
Только primary |
Все операции записи. При failover оператор автоматически переключает его на нового primary , соответственно приложению не нужно ничего делать |
|
|
Только hot standby реплики |
Чтение с offloading нагрузки. Приложение должно уметь работать с небольшим replication lag |
|
|
Любой инстанс |
Максимальное распределение read-запросов, без гарантий актуальности данных |
Операции записи всегда идут через -rw. В этом фишка автоматического failover: сервис меняет endpoint незаметно для приложения, я бы сказал бесшовно.
Заходим в кластер
Через плагин kubectl cnpg (дока по установке):
kubectl cnpg psql -n default my-cluster
Через стандартный psql
psql -h <host> -p <port> -U <username> -d <dbname>
Креды подставляем из секрета my-cluster-app
Итог
За несколько манифестов мы получили то, на настройку чего раньше уходили дни: высокодоступный PostgreSQL-кластер в Kubernetes с потоковой репликацией, автоматическим failover и полностью декларативным управлением.
Что отличает CNPG от «просто PostgreSQL в StatefulSet»:
-
Прямое управление PVC без ограничений StatefulSet (включая ресайзинг на лету)
-
Автоматический failover с промоцией самой актуальной реплики
-
Три семантически разных сервиса для грамотной маршрутизации нагрузки
-
Декларативное управление базами, пользователями и расписаниями бэкапов
-
Высокий уровень автоматизации
ссылка на оригинал статьи https://habr.com/ru/articles/1039452/