Kubernetes Operators: полугодовой итог - Zalando Postgres Operator в production
Подводим итог работы с Kubernetes Operators за полугодие: Zalando Postgres Operator упрощает управление кластерами СУБД, но требует понимания CRD и lifecycle-хуков.
Зрелость паттерна Kubernetes Operators - Etcd, PostgreSQL, Prometheus операторы распространяются в production-кластерах
В марте мы разбирали паттерн Kubernetes Operators как концепцию, в июне раскатали Prometheus Operator на production. Сейчас конец июня, и если оглянуться на последние полгода - операторы из «интересного эксперимента CoreOS» превратились в реальный инструмент с которым мы работаем каждую неделю. Время сделать честный срез: что прижилось, что требует осторожности, и почему Zalando Postgres Operator оказался и неожиданно удобным, и неожиданно требовательным.
Три оператора, три разных опыта
Etcd Operator у нас так и живёт в тестовом кластере. Не потому что плохой - он работает корректно, правильно обрабатывает quorum при выводе члена из строя, делает rolling upgrade без потери данных. Проблема в другом: production-etcd это сам etcd Kubernetes, и запускать ещё один etcd-кластер для приложений через оператор на тех же нодах - это двойная ответственность за один и тот же тип данных в разных контекстах. Пока не нашли клиентский сценарий где это необходимо именно так.
Prometheus Operator уже три недели на production-кластере, и это самый чистый опыт из трёх. ServiceMonitor CRD прижился, команды разработки создают мониторинг самостоятельно, тикетов «добавьте наш сервис в Prometheus» не было ни одного за этот период. Паттерн простой и понятный: инфраструктура управляет самим Prometheus, разработчики управляют тем что мониторить. Работает.
Zalando Postgres Operator - это отдельная история, и ради неё написан этот пост.
Zalando Postgres Operator: откуда он взялся
Задача появилась месяц назад: один из клиентов хочет несколько PostgreSQL-кластеров в Kubernetes с failover, point-in-time recovery и возможностью масштабировать реплики без ручного вмешательства. StatefulSet с PostgreSQL в одном Pod они уже пробовали - при падении пода потеряли несколько минут транзакций и долго разбирались почему. Логично было посмотреть на операторы.
Вариантов на рынке сейчас несколько: Crunchy Data PostgreSQL Operator, KubeDB, и Zalando Postgres Operator. Мы выбрали Zalando по нескольким причинам: они используют его в собственной production-инфраструктуре, код открытый на GitHub, и он построен поверх Patroni - инструмента для HA PostgreSQL с которым мы уже работали не в контексте Kubernetes.
CRD оператора называется postgresql (в API группе acid.zalan.do). Манифест выглядит примерно так:
apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
name: acid-postgres-cluster
namespace: default
spec:
teamId: "acid"
volume:
size: 10Gi
numberOfInstances: 3
users:
appuser:
- superuser
- createdb
databases:
myapp: appuser
postgresql:
version: "10"
Создаёшь этот объект - оператор поднимает кластер из трёх нод, настраивает Patroni для управления leader election, создаёт пользователей и базы. Если нода падает - Patroni переключает лидера, оператор следит за тем чтобы общее количество нод восстановилось.
Где оказалось нетривиально
Первый сюрприз пришёл со стороны lifecycle-хуков. Postgres Operator не просто запускает Pod и ждёт Ready - он управляет жизненным циклом через preStop-хуки и собственные health-проверки, которые нужно понять прежде чем трогать что-то в манифестах Pod-шаблона. Мы один раз поменяли resource limits напрямую в StatefulSet который создал оператор - при следующей синхронизации оператор вернул их в исходное состояние. Это правильное поведение - оператор владеет этими объектами, а не мы. Но пока не понимаешь что управление идёт через CRD, а не через прямую правку ресурсов - теряешь время.
Второй момент: хранилище credentials. Оператор создаёт Kubernetes Secret для каждого пользователя автоматически. Неплохо. Но схема именования секретов фиксирована ({username}.{cluster-name}.credentials.postgresql.acid.zalan.do), и если у вас уже есть соглашение по именованию секретов - придётся либо адаптироваться, либо настраивать. Для клиентских managed-кластеров мы написали небольшую документацию по этому поводу, потому что иначе разработчики не могут найти свои credentials.
Третий момент - backup. Оператор поддерживает интеграцию с WAL-G и Spilo (Docker-образ от Zalando) для WAL-архивации и PITR. Это хорошо. Но настройка требует S3-совместимого хранилища для WAL-сегментов и понимания как Spilo управляет базовыми бэкапами. Мы потратили примерно день на то чтобы отладить цепочку: Spilo пишет WAL в MinIO, проверить что восстановление с конкретной точки действительно работает, убедиться что оператор корректно передаёт параметры подключения к хранилищу через переменные окружения.
Что с CRD и API-стабильностью
Это общая проблема всех операторов сейчас. Zalando Postgres Operator использует v1 в имени API-группы, но это не означает что API стабильный в смысле «никогда не меняется». Это просто имя группы. Мы нарвались на это при обновлении оператора - несколько полей в spec переименовались, существующий объект postgresql не удалился и не обновился автоматически, пришлось мигрировать руками.
Это не катастрофа, но требует того чего у многих нет в начале: нормального процесса обновления оператора с проверкой совместимости CRD.
Etcd Operator, к слову, в этом же месте тоже вызывал вопросы - там API v1beta2 что хотя бы честно говорит «мы ещё итерируем».
Где мы сейчас
Postgres Operator работает на одном production-кластере третью неделю. Под ним кластер из трёх нод PostgreSQL 10, WAL-архивация настроена, одно переключение лидера мы намеренно проверили в тестовом окне - Patroni переключился за несколько секунд, приложение пережило это с минимальным количеством ошибок. Клиент доволен.
Паттерн Operators в целом за полгода перешёл для нас из категории «надо разобраться» в категорию «используем на реальных кластерах». Prometheus Operator - рабочий инструмент. Postgres Operator - тоже рабочий, но требует больше вложений на старте: нужно понять Patroni, lifecycle-хуки, схему именования объектов и выстроить процесс обновления самого оператора.
Если кто-то ждёт «просто поставь и забудь» - пока так не работает. Оператор убирает рутину, но не убирает необходимость понимать что происходит внутри.