ADG Оставить заявку
Блог DevOps 5 мин чтения

Postgres Operator от Zalando: разворачиваем PostgresCluster вместо ручных реплик

Операторный паттерн в Kubernetes взрослеет: берём Zalando Postgres Operator, сравниваем с Crunchy Data и разбираем что CRD реально берёт на себя в продакшне.

Контекст момента

Операторный паттерн в Kubernetes зрелеет: крупные вендоры публикуют операторы для stateful-сервисов

С операторами в Kubernetes у нас были отношения по принципу «знаем, что существует, но в продакшн не трогаем». Паттерн красивый - берёшь CRD, описываешь желаемое состояние кластера баз данных как Kubernetes-ресурс, контроллер сам разруливает failover, реплики, бэкапы. На практике год назад всё это было в состоянии «работает, пока не надо ничего нестандартного». Но за последние месяцы несколько крупных вендоров выкатили достаточно зрелые операторы для stateful-сервисов, и мы решили посмотреть внимательнее - на конкретной задаче, а не в вакууме.

Задача: кластер PostgreSQL для одного из клиентов, который живёт в Kubernetes и до сих пор управляется руками. Три ноды, Patroni поверх etcd, всё поднято через Helm-чарт с горой кастомных values. Работает, но каждый раз когда надо добавить реплику или настроить бэкап - это ручная работа и знание о том как устроено хранится в головах, а не в Git.

Почему Zalando, а не что-то другое

Основных игроков сейчас два: Zalando Postgres Operator и Crunchy Data PGO (Postgres Operator). Есть ещё KubeDB, но это платный продукт с другой бизнес-моделью.

Zalando Operator - open source, активно поддерживается, написан на Go, CRD называется postgresql (в API-группе acid.zalan.do). Активно используется у самого Zalando в production с большим количеством кластеров.

Crunchy Data PGO - тоже open source, более enterprise-ориентированный, CRD pgclusters (группа crunchydata.com), v4 вышел недавно и изменил модель управления достаточно радикально по сравнению с v3. Crunchy делают certified PostgreSQL дистрибутив и в этом смысле экспертиза у них глубокая.

Выбрали Zalando для первого захода по нескольким причинам: документация лучше структурирована, примеры ближе к нашим сценариям, и оператор уже был знаком паре человек в команде.

Что CRD берёт на себя

Минимальный манифест кластера выглядит так:

apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: prod-db-cluster
  namespace: databases
spec:
  teamId: "adg"
  volume:
    size: 100Gi
    storageClass: ceph-block
  numberOfInstances: 3
  postgresql:
    version: "12"
  users:
    appuser:
      - superuser
      - createdb
  databases:
    appdb: appuser
  resources:
    requests:
      cpu: 500m
      memory: 2Gi
    limits:
      cpu: 2
      memory: 8Gi

Что происходит после kubectl apply:

  • Оператор разворачивает StatefulSet с нужным числом подов.
  • Внутри каждого пода - Patroni, настроенный автоматически, с etcd или с Kubernetes-ом как distributed configuration store (мы выбрали Kubernetes endpoints как более простой вариант без внешней зависимости).
  • Создаются Service-ы: один для primary (на который пишут), один для реплик (read-only), один headless для Patroni.
  • Пользователи и базы из spec создаются автоматически при первом старте.
  • Секреты с паролями генерируются и кладутся в Kubernetes Secrets.

Failover. При падении primary Patroni переключает роль на одну из реплик, оператор обновляет endpoint primary-сервиса. Приложение переподключается и продолжает работать. На тестах задержка переключения была в диапазоне 15-30 секунд - это Patroni-поведение, оператор тут не добавляет лишнего.

Масштабирование. Чтобы добавить реплику - меняешь numberOfInstances: 4 и делаешь apply. Оператор добавляет под в StatefulSet, дожидается когда Patroni введёт его в кластер как standby, всё. Чтобы убрать - уменьшаешь число. До этого добавление реплики вручную занимало добрые полчаса с pg_basebackup и настройкой recovery.conf.

Бэкап: Spilo + WAL-G

Образ, который Zalando Operator использует по умолчанию, - это Spilo, их собственный образ PostgreSQL со встроенным WAL-G для бэкапов. Настройка через аннотации на кластере:

annotations:
  wal-g.zalando.org/wal-s3-bucket: "s3://backup-bucket/postgres"
  wal-g.zalando.org/backup-schedule: "0 3 * * *"

WAL-G умеет в S3-совместимое хранилище, что в нашем случае - это Ceph Object Gateway. Полный бэкап раз в сутки, WAL-стриминг непрерывно, point-in-time recovery из коробки. Проверили восстановление на отдельном кластере - работает, время восстановления зависит от размера базы и объёма WAL за период.

Раньше бэкап был скриптом в cron на одной из нод. Если нода падала вместе со скриптом - бэкап молча не снимался. Теперь это забота оператора.

Что не так просто

Несколько мест где пришлось повозиться.

Connection pooling. Оператор умеет разворачивать pgBouncer рядом с кластером, но конфигурация через аннотации - не очень очевидная. Понадобилось время чтобы правильно настроить режим pooling и убедиться что приложение использует именно bouncer, а не напрямую primary.

Кастомные параметры PostgreSQL. Через spec.postgresql.parameters - работает, но изменение параметров требующих рестарта приводит до rolling restart кластера. Это правильно, но надо понимать заранее - в рабочее время лучше не трогать.

Мониторинг. Оператор сам по себе метрики в Prometheus не пушит - нужен postgres-exporter в качестве sidecar или отдельного деплоя. Документация это описывает, но подразумевает что вы сами это соберёте.

Crunchy Data PGO: пара слов для сравнения

Мы подняли тестовый кластер через PGO v4 параллельно. Основное отличие которое бросается в глаза: в Crunchy модель более декларативная и явная. Бэкап, мониторинг, пулер - всё описывается в одном CRD pgclusters с явными секциями, а не через аннотации. Это удобнее для ревью в Git - видно всё в одном месте.

С другой стороны, PGO v4 - достаточно свежий, и документация местами догоняет код. Мы несколько раз натыкались на поведение, которое не совпадало с тем что написано в docs. У Zalando Operator ощущение более обкатанного продукта - возможно потому что они его реально используют сами в нагруженном production.

Где сейчас

Клиентский кластер мигрирован на Zalando Operator в staging. Production пока стоит на старой схеме с Patroni вручную - ждём ещё пару недель наблюдений в staging перед переключением. Сама миграция потребовала pg_dump/restore, прямой in-place переход мы не стали делать - слишком много ручного состояния в старой конфигурации.

Общее впечатление: паттерн работает и для stateful-workload-ов. Главный выигрыш не в том что что-то стало быстрее, а в том что операционные процедуры - failover, добавление реплики, бэкап - теперь воспроизводимы и описаны в коде. Это как раз то что нам нужно в managed-сопровождении - не «у нас есть человек который это умеет», а «это работает так потому что так написано в манифесте».

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.