PostgreSQL на Kubernetes: операторы, хранилище и production

Почему стоит запускать 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 часа ночи?» Если да — добро пожаловать.

Логотип сообщества DEV
© 2026 meganuke