Почему стоит запускать PostgreSQL на Kubernetes
Идея проста: управлять базой данных так же, как и всем остальным.
GitOps для баз данных. Кластер PostgreSQL описывается в YAML, хранится в системе контроля версий и проходит ревью через pull request. Изменение числа реплик в одну строку запускает автоматическое масштабирование. Никаких SSH-сессий, никаких runbook’ов, никакого «единственного человека, который умеет настраивать репликацию».
Паритет сред разработки и эксплуатации. Каждый разработчик поднимает идентичный кластер PostgreSQL командой kubectl apply. Больше никаких ситуаций «у меня работает» из-за того, что в dev используется SQLite, а в prod — PostgreSQL 16 с кастомными расширениями.
Автоматический failover. Операторы обнаруживают отказ первичного узла и повышают реплику за секунды. Операторы на базе Patroni (Zalando) обеспечивают failover менее чем за 10 секунд. CloudNativePG достигает того же с помощью нативных примитивов Kubernetes. Сравните это с ручным повышением standby в 3 часа ночи.
Масштабирование. Добавить реплику для чтения — значит изменить одно поле. Операторы автоматически выполняют базовый бэкап, настройку репликации и конфигурацию эндпоинтов.
Единообразие операций. Бэкапы, обновления минорных версий, ротация сертификатов и пул соединений описываются в том же манифесте, что и сам кластер. Один инструмент, один рабочий процесс.
Почему НЕ стоит запускать PostgreSQL на Kubernetes
Скажу прямо. Kubernetes привносит реальную сложность, и далеко не каждая команда выигрывает от этого.
Хранилище — главная проблема. Базы данных требуют надёжного, высокопроизводительного хранилища. Kubernetes изначально проектировался для stateless-нагрузок. PersistentVolumeClaim’ы, StorageClass’ы, CSI-драйверы и ограничения топологии томов добавляют слои абстракции между базой данных и диском. Некорректно настроенный StorageClass может незаметно подсунуть сетевое хранилище с задержкой в 10 раз выше, чем у локального NVMe.
Противоречие «питомцев и скота». Kubernetes относится к подам как к расходному «скоту». Первичная база данных — это «питомец»: у неё уникальное состояние, и потеря без корректной передачи управления означает простой или потерю данных. Операторы закрывают этот разрыв, но привносят собственную сложность.
Накладные расходы на сеть. Service mesh’и, сетевые политики и CNI-плагины создают дополнительную задержку и режимы отказа, которых нет на bare metal. Сетевой хоп в 0,5 мс на каждый запрос ощутимо сказывается при 10 000 запросов в секунду.
Порог операционной сложности. Когда что-то идёт не так — завис failover, PVC не биндится, сегмент WAL не заархивировался — нужно одновременно разбираться и в Kubernetes, и в PostgreSQL. Это требует двух разных компетенций.
Небольшие команды должны подумать дважды. Если в команде меньше пяти инженеров и один экземпляр PostgreSQL, управляемая база данных (RDS, Cloud SQL, Supabase) подойдёт лучше. Вы потратите чуть больше денег в месяц и сэкономите сотни часов в год.
Честный ответ: запускайте PostgreSQL на Kubernetes, если команда уже уверенно работает с Kubernetes и вам нужно несколько кластеров PostgreSQL. В противном случае начните с управляемого сервиса и мигрируйте позже, когда упрётесь в его ограничения.
Три основных оператора
Оператор Kubernetes — это контроллер, который знает, как запускать конкретное приложение. Для PostgreSQL операторы управляют репликацией, failover’ом, бэкапами и пулом соединений. В экосистеме доминируют три оператора.
CloudNativePG
CloudNativePG — проект CNCF Sandbox и наиболее активно развиваемый оператор PostgreSQL. Он придерживается Kubernetes-native подхода: никаких внешних зависимостей для обеспечения высокой доступности (HA), никакого Patroni, никакого etcd. Failover реализован непосредственно через примитивы Kubernetes (pod readiness, leader election).
Ключевые преимущества:
-
Самая чистая интеграция с Kubernetes — ощущается как нативный ресурс
-
Встроенное архивирование WAL и бэкапы в S3/GCS/Azure (через Barman Cloud)
-
Декларативная поддержка tablespace’ов
-
Архитектура плагинов для расширений
-
Нет нужды в sidecar-контейнерах для HA
Минимальное описание кластера:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-app-db
spec:
instances: 3
storage:
size: 50Gi
storageClass: fast-ssd
postgresql:
parameters:
shared_buffers: "256MB"
effective_cache_size: "768MB"
work_mem: "8MB"
max_connections: "200"
bootstrap:
initdb:
database: myapp
owner: myapp
backup:
barmanObjectStore:
destinationPath: "s3://my-backups/cnpg/"
s3Credentials:
accessKeyId:
name: aws-creds
key: ACCESS_KEY_ID
secretAccessKey:
name: aws-creds
key: SECRET_ACCESS_KEY
wal:
compression: gzip
retentionPolicy: "14d"
Это трёхузловой кластер PostgreSQL со стриминговой репликацией, автоматическим failover’ом, непрерывным архивированием WAL в S3 и 14-дневным хранением бэкапов — примерно в 30 строках YAML.
Установка:
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.25/releases/cnpg-1.25.0.yaml
Залando Postgres Operator
Прошёл боевую проверку в Zalando, где управляет 4000+ кластерами PostgreSQL на одной из крупнейших e-commerce платформ Европы. Использует Patroni для обеспечения высокой доступности — тот же инструмент, который обеспечивает HA PostgreSQL в GitLab, Zalando и десятках других организаций за пределами Kubernetes.
Ключевые преимущества:
-
Patroni HA — самый проверенный инструмент failover для PostgreSQL
-
Образ контейнера Spilo включает PostgreSQL + Patroni + WAL-G + PgBouncer
-
Встроенный пул соединений
-
Контроль доступа на основе команд (маппинг неймспейсов Kubernetes на роли PostgreSQL)
-
Клонирование из бэкапа S3 или существующего кластера
Описание кластера:
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
name: my-app-db
spec:
teamId: "myteam"
numberOfInstances: 3
volume:
size: 50Gi
storageClass: fast-ssd
postgresql:
version: "16"
parameters:
shared_buffers: "256MB"
effective_cache_size: "768MB"
work_mem: "8MB"
users:
myapp:
- superuser
- createdb
databases:
myapp: myapp
patroni:
ttl: 30
loop_wait: 10
retry_timeout: 10
synchronous_mode: false
resources:
requests:
cpu: "1"
memory: 2Gi
limits:
cpu: "2"
memory: 4Gi
Установка:
kubectl create namespace postgres-operator
helm install postgres-operator \
oci://ghcr.io/zalando/postgres-operator-charts/postgres-operator \
-n postgres-operator
Crunchy PGO (v5)
Оператор от CrunchyData с коммерческой поддержкой. Глубоко интегрирован с pgBackRest для бэкапов и pgMonitor для мониторинга на базе Prometheus.
Ключевые преимущества:
-
Интеграция с pgBackRest — наиболее зрелая: дифференциальные бэкапы, несколько репозиториев, верификация бэкапов
-
Встроенный экспортёр Prometheus (pgMonitor/postgres_exporter)
-
PgBouncer как sidecar, управляемый декларативно
-
Опция корпоративной поддержки
-
Обширная документация
Описание кластера:
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PostgresCluster
metadata:
name: my-app-db
spec:
postgresVersion: 16
instances:
- name: pgha
replicas: 3
dataVolumeClaimSpec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 50Gi
resources:
requests:
cpu: "1"
memory: 2Gi
backups:
pgbackrest:
repos:
- name: repo1
s3:
bucket: my-backups
endpoint: s3.amazonaws.com
region: us-east-1
schedules:
full: "0 1 * * 0" # Полный бэкап еженедельно
differential: "0 1 * * 1-6" # Дифференциальный бэкап ежедневно
proxy:
pgBouncer:
replicas: 2
config:
global:
pool_mode: transaction
default_pool_size: "20"
Установка:
kubectl apply --server-side -k \
github.com/CrunchyData/postgres-operator-examples/install/default
Хранилище: решение, от которого зависит всё
Хранилище — это то, на чём PostgreSQL на Kubernetes либо преуспевает, либо проваливается. Ошибитесь здесь — никакая тонкая настройка оператора не поможет.
Выбор StorageClass
StorageClass определяет реальный тип диска под PersistentVolumeClaim’ами. На большинстве облачных провайдеров по умолчанию используются сетевые диски (EBS gp3, GCE pd-ssd). Они работают, но важно понимать компромиссы:
| Тип хранилища | Задержка | Пропускная способность | Переживает отказ узла | Стоимость |
|---|---|---|---|---|
Локальный NVMe |
~0,1 мс |
3+ ГБ/с |
Нет |
Минимальная |
EBS io2 |
~0,5 мс |
4 ГБ/с |
Да |
Высокая |
EBS gp3 |
~1 мс |
1 ГБ/с |
Да |
Средняя |
GCE pd-ssd |
~1 мс |
1,2 ГБ/с |
Да |
Средняя |
Локальный NVMe обеспечивает лучшую производительность, но не переживает отказ узла. Используйте его только если оператор умеет автоматически пересоздавать узел из бэкапа (CloudNativePG и Crunchy PGO это поддерживают).
Сетевой SSD (gp3, pd-ssd) — безопасный вариант по умолчанию. Накладные расходы на задержку реальны, но приемлемы для большинства нагрузок. Установите volumeBindingMode: WaitForFirstConsumer в StorageClass, чтобы тома создавались в той же зоне доступности, что и под.
WAL на отдельном томе
Записи журнала предзаписи (WAL, write-ahead log) последовательны и чувствительны к задержке. Записи данных случайны. Если смешать их на одном томе, они будут конкурировать за IOPS. CloudNativePG поддерживает отдельный том для WAL:
spec:
storage:
size: 50Gi
storageClass: fast-ssd
walStorage:
size: 10Gi
storageClass: fast-ssd
Это не обязательно, но для нагрузок с интенсивной записью позволяет снизить задержку фиксации транзакций на 30–50%.
Проведите нагрузочное тестирование до запуска в production
Запустите pgbench на реальном StorageClass перед тем, как переходить в production:
# Инициализация pgbench
kubectl exec -it my-app-db-1 -- pgbench -i -s 100 myapp
# Смешанный нагрузочный тест чтения и записи
kubectl exec -it my-app-db-1 -- pgbench -c 20 -j 4 -T 300 myapp
Сравните показатели транзакций в секунду и задержки с требованиями вашего приложения. Если p99-задержка запроса составляет 50 мс, а хранилище добавляет 5 мс — это приемлемо. Если p99 равен 5 мс, а хранилище добавляет те же 5 мс — это проблема.
Резервное копирование и восстановление
Потеря базы данных на Kubernetes ничем не отличается от потери в любой другой среде — это катастрофа. Все операторы поддерживают автоматическое резервное копирование, но реализации различаются.
pgBackRest (Crunchy PGO) — наиболее функциональный инструмент: полные, дифференциальные и инкрементальные бэкапы, параллельное резервное копирование и восстановление, верификация бэкапов, поддержка нескольких репозиториев. Может одновременно копировать в S3, GCS, Azure или локальный том.
Barman Cloud (CloudNativePG) обеспечивает непрерывное архивирование WAL и базовые бэкапы в объектное хранилище. Проще, чем pgBackRest, но покрывает критический путь: восстановление на произвольный момент времени (point-in-time recovery) в пределах окна хранения.
WAL-G (Zalando) встроен в образ Spilo. Обрабатывает архивирование WAL и базовые бэкапы в S3/GCS. Проверен на масштабах Zalando.
Независимо от используемого оператора убедитесь, что всё нижеперечисленное работает:
-
Архивирование WAL непрерывно. Проверяйте наличие пробелов. Один пропущенный сегмент WAL ломает point-in-time recovery.
-
Регулярно тестируйте восстановление. Бэкап, из которого вы ни разу не восстанавливались, — не бэкап. Планируйте ежемесячные тесты.
-
Следите за возрастом бэкапа. Если последний успешный бэкап старше вашего целевого RPO — у вас уже есть проблема.
# CloudNativePG: проверка статуса бэкапов
kubectl get backups -n my-namespace
# Zalando: список бэкапов WAL-G
kubectl exec my-app-db-0 -- envdir /run/etc/wal-e.d/env wal-g backup-list
# Crunchy PGO: информация о pgBackRest
kubectl exec my-app-db-pgha-0 -- pgbackrest info
Пул соединений
Kubernetes усугубляет проблему соединений. Перезапуски подов, rolling deployment’ы и горизонтальное масштабирование постоянно открывают и закрывают соединения. Между подами приложения и PostgreSQL необходим пулер.
Crunchy PGO управляет PgBouncer как первоклассным ресурсом. Объявите его в спецификации PostgresCluster, и оператор сам займётся деплоем, конфигурацией и TLS-сертификатами. Это самая чистая интеграция.
Оператор Zalando включает PgBouncer в образ Spilo и может разворачивать отдельный ресурс connection pooler. Настройка выполняется через спецификацию connectionPooler.
CloudNativePG поддерживает PgBouncer через ресурс Pooler:
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: my-app-db-pooler-rw
spec:
cluster:
name: my-app-db
instances: 2
type: rw
pgbouncer:
poolMode: transaction
parameters:
default_pool_size: "25"
max_client_conn: "200"
Режим transaction pooling — правильный выбор по умолчанию для большинства приложений. Он мультиплексирует множество клиентских соединений через меньшее число backend-соединений PostgreSQL, переназначая бэкенды между транзакциями. Режим session pooling нужен только если приложение использует prepared statement’ы, advisory lock’и на уровне сессии или временные таблицы вне явных транзакций.
Мониторинг PostgreSQL на Kubernetes
Kubernetes добавляет ещё один уровень сложности к мониторингу. Нужно следить как за экземпляром PostgreSQL, так и за ресурсами Kubernetes — и с точки зрения приложения режимы отказа каждого из них могут выглядеть одинаково: медленный запрос может быть следствием отсутствующего индекса или под, упирающийся в лимит CPU.
Стек мониторинга Kubernetes (Prometheus, Grafana, Datadog) обрабатывает здоровье подов, утилизацию ресурсов, ёмкость PVC и нагрузку на узлы. Но у этих инструментов есть слепое пятно: они видят PostgreSQL как чёрный ящик. Они могут сказать, что под использует 90% своего CPU-лимита, но не объяснят, вызвано ли это последовательными сканированиями таблицы без индекса.
Инструменты вроде myDBA.dev сосредоточены на уровне PostgreSQL: проверки работоспособности, выявляющие дрейф конфигурации и антипаттерны; анализ производительности запросов; рекомендации по индексам; отслеживание планов EXPLAIN и обнаружение регрессий; мониторинг репликации. Такой уровень аналитики нужен независимо от того, где запущен PostgreSQL — на Kubernetes, bare metal или в управляемом сервисе.
На практике работает многоуровневый подход:
-
Уровень Kubernetes: Prometheus + node-exporter + kube-state-metrics для метрик подов, узлов и PVC
-
Метрики PostgreSQL: postgres_exporter (включён в Crunchy PGO) для pg_stat_statements, лага репликации, числа соединений
-
Аналитика PostgreSQL: myDBA.dev для проверок работоспособности, советника по индексам, анализа планов EXPLAIN и отслеживания производительности запросов
Каждый оператор также предоставляет собственные метрики. CloudNativePG имеет встроенный эндпоинт Prometheus. Оператор Zalando выставляет метрики состояния кластера и событий failover. Настройте алерты для:
-
Лаг репликации превышает 30 секунд
-
Ошибка архивирования WAL (бэкап сломан)
-
Утилизация соединений выше 80%
-
Использование PVC выше 85%
-
Рост счётчика перезапусков пода (crash loop)
Сравнение операторов
| Характеристика | CloudNativePG | Zalando | Crunchy PGO |
|---|---|---|---|
Механизм HA |
Нативные примитивы K8s |
Patroni + DCS |
На основе Patroni |
Инструмент бэкапа |
Barman Cloud |
WAL-G |
pgBackRest |
Пулер соединений |
PgBouncer (Pooler CRD) |
PgBouncer (встроен) |
PgBouncer (управляемый) |
Версии PG |
12–17 |
12–16 |
13–16 |
Статус CNCF |
Sandbox-проект |
Community |
Community |
Коммерческая поддержка |
EDB |
Zalando (внутренняя) |
CrunchyData |
Мониторинг |
Эндпоинт Prometheus |
Эндпоинт Prometheus |
pgMonitor встроен |
Декларативные пользователи |
Да (через secrets) |
Да (на основе команд) |
Да (через secrets) |
Поддержка tablespace |
Да |
Нет |
Да |
Том для WAL |
Отдельный том |
Общий том |
Отдельный том |
Подход к sidecar |
Минимальный (без HA-sidecar) |
Patroni + WAL-G |
Patroni + pgBackRest |
Порог вхождения |
Умеренный |
Умеренный |
Выше среднего |
Рекомендация: для новых развёртываний выбирайте CloudNativePG. У него самая активная разработка, самая чистая интеграция с Kubernetes, а статус проекта CNCF внушает уверенность в долгосрочной жизнеспособности. Оператор Zalando — правильный выбор, если вы уже вложились в Patroni или вам нужна его модель доступа на основе команд. Crunchy PGO выигрывает, если продвинутые функции бэкапа pgBackRest критичны или нужна коммерческая поддержка.
Быстрый старт: минимальная конфигурация CloudNativePG
Самый быстрый путь от нуля до работающего кластера PostgreSQL на Kubernetes:
# 1. Установить оператор
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.25/releases/cnpg-1.25.0.yaml
# 2. Дождаться готовности оператора
kubectl wait --for=condition=Available deployment/cnpg-controller-manager \
-n cnpg-system --timeout=120s
# 3. Сохранить как cluster.yaml и применить
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: my-first-cluster
spec:
instances: 3
storage:
size: 10Gi
bootstrap:
initdb:
database: app
owner: app
postgresql:
parameters:
shared_buffers: "128MB"
log_statement: "ddl"
log_min_duration_statement: "1000"
# 4. Применить и дождаться готовности
kubectl apply -f cluster.yaml
kubectl wait --for=condition=Ready cluster/my-first-cluster --timeout=300s
# 5. Подключиться
kubectl exec -it my-first-cluster-1 -- psql -U app -d app
Результат — трёхузловой кластер PostgreSQL со стриминговой репликацией и автоматическим failover’ом. Без Helm-чартов, без сложной конфигурации.
Чтобы открыть доступ к кластеру для подов приложения:
# Оператор автоматически создаёт следующие сервисы:
# my-first-cluster-rw -> всегда указывает на первичный узел
# my-first-cluster-ro -> балансирует нагрузку по репликам
# my-first-cluster-r -> балансирует нагрузку по всем узлам
# В деплойменте приложения:
env:
- name: DATABASE_URL
value: "postgresql://app:$(PASSWORD)@my-first-cluster-rw:5432/app"
Чеклист для production
Перед запуском PostgreSQL на Kubernetes в production проверьте каждый пункт.
Хранилище
-
❏ StorageClass использует SSD-тома (не HDD по умолчанию)
-
❏ Установлен
volumeBindingMode: WaitForFirstConsumer -
❏ Размер PVC предусматривает двукратный запас от ожидаемого объёма данных (WAL, временные файлы, bloat)
-
❏ WAL вынесен на отдельный том для нагрузок с интенсивной записью
-
❏ Проведено нагрузочное тестирование pgbench на реальном StorageClass
Ресурсы
-
❏ Заданы requests (не только limits) для CPU и памяти
-
❏ Лимит памяти не менее
shared_buffers + work_mem * max_connections + 512MB -
❏ CPU requests соответствуют реальному потреблению в штатном режиме
Высокая доступность
-
❏ Не менее 3 экземпляров (первичный + 2 реплики)
-
❏ PodDisruptionBudget допускает не более 1 недоступного пода
-
❏ Pod anti-affinity распределяет экземпляры по разным узлам
-
❏ Протестирован failover путём удаления первичного пода
# Пример PodDisruptionBudget (CloudNativePG создаёт автоматически)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-db
spec:
maxUnavailable: 1
selector:
matchLabels:
cnpg.io/cluster: my-app-db
Резервное копирование
-
❏ Архивирование WAL в объектное хранилище (S3/GCS) активно
-
❏ Расписание базовых бэкапов настроено (минимум ежедневно)
-
❏ Срок хранения бэкапов соответствует требованиям комплаенса
-
❏ Протестировано восстановление на произвольный момент времени из бэкапа
-
❏ Настроены алерты мониторинга бэкапов
Безопасность
-
❏ TLS включён для клиентских соединений
-
❏ Учётные данные базы данных хранятся в Kubernetes Secrets (или внешнем менеджере секретов)
-
❏ Сетевые политики ограничивают доступ к порту PostgreSQL на уровне неймспейсов приложений
-
❏
pg_hba.confограничивает методы аутентификации
Мониторинг
-
❏ Метрики PostgreSQL экспортируются в Prometheus
-
❏ Настроены алерты для лага репликации, насыщения соединений, использования PVC
-
❏ Настроен мониторинг на уровне PostgreSQL: производительность запросов, проверки работоспособности, рекомендации по индексам (myDBA.dev работает с любым развёртыванием PostgreSQL, включая Kubernetes)
-
❏ Настроена агрегация логов из подов PostgreSQL
Пул соединений
-
❏ PgBouncer развёрнут (управляется оператором или как sidecar)
-
❏ Режим пула установлен в
transaction(если не требуются функции сессионного уровня) -
❏ Приложение подключается через пулер, а не напрямую к PostgreSQL
-
❏ Размер пула подобран:
default_pool_size * pooler_replicas < max_connections
Заключение
Запуск PostgreSQL на Kubernetes не лучше и не хуже, чем на виртуальных машинах или в управляемом сервисе. Это компромисс: вы получаете согласованность и автоматизацию, платя за это сложностью работы с хранилищем и более запутанной отладкой.
Если команда уже работает с Kubernetes и управляет несколькими экземплярами PostgreSQL, оператор снизит операционную нагрузку. Если вы небольшая команда с одной базой данных — используйте RDS или Cloud SQL и тратьте инженерное время на продукт.
Для тех, кто всё же выбирает путь Kubernetes: начните с CloudNativePG. Он проще всего настраивается, активнее всего поддерживается и не тащит за собой груз зависимостей полноценного стека Patroni. Правильно настройте хранилище, протестируйте бэкапы и выстройте многоуровневый мониторинг, чтобы при возникновении проблем видеть и Kubernetes-, и PostgreSQL-перспективу одновременно.
Операторы достигли такого уровня зрелости, что вопрос «можно ли запустить PostgreSQL на Kubernetes?» уже не актуален. Правильный вопрос звучит иначе: «хватит ли у команды экспертизы в Kubernetes, чтобы разобраться с завязшим PVC в 3 часа ночи?» Если да — добро пожаловать.