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

Kubernetes 1.5: StatefulSets из альфы в бету - базы данных в кластере без костылей

Kubernetes 1.5 перевёл StatefulSets из alpha в beta со стабильным API. Разбираемся чем StatefulSet отличается от Deployment и когда он реально нужен для баз данных в нашей практике.

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

Kubernetes 1.5 вышел в декабре 2016 со стабильными StatefulSets и PodDisruptionBudget - первый релиз где stateful-нагрузки считаются production-ready

В декабре вышел Kubernetes 1.5, и самое интересное там - StatefulSets переехали из альфы в бету со стабильным API (apps/v1beta1). Мы к этому моменту уже полтора года держим кластеры под managed-проекты и вопрос «а базу данных тоже в Kubernetes?» встречаем регулярно. Раньше отвечали честно: можно, но неудобно, API нестабильное, лучше держать снаружи. Теперь картина немного другая.

Почему раньше не шли в кластер с базами

С самого начала наши кластеры работали по простому принципу: stateless-сервисы внутри, stateful - снаружи. PostgreSQL, Redis, RabbitMQ жили на отдельных виртуальных машинах, поды обращались к ним по адресу. Это решение из нашего опыта с namespaces в 1.1 - тогда мы прямо написали что persistent storage не тащим в кластер, и с тех пор это не менялось.

Причина была не в страхе, а в том что инструмент не был готов. Deployment - основная рабочая лошадка Kubernetes - создаёт поды как взаимозаменяемые. Упал под с базой данных - scheduler запустил новый. Но новый под получит другое имя, другой hostname, возможно другой PersistentVolume. Для stateless-сервиса это нормально, для базы - катастрофа: реплика PostgreSQL должна знать что она реплика, к какому мастеру подключаться, и этот мастер должен её знать по стабильному идентификатору.

PetSets, которые потом переименовали в StatefulSets, появились как ответ на это. Но пока они были в бете - трогать их в продакшне было авантюрой с нестабильным API.

Что даёт StatefulSet

Три вещи которые делают StatefulSet другим по сравнению с Deployment:

  • Стабильные идентификаторы. Поды получают предсказуемые имена: postgres-0, postgres-1, postgres-2. При перезапуске под получает то же имя, тот же hostname, тот же PersistentVolume. Для кластерного ПО это фундамент - без этого невозможно описать топологию репликации.
  • Упорядоченный запуск и остановка. Поды стартуют строго по порядку: postgres-0 поднялся и готов - тогда стартует postgres-1. При масштабировании вниз обратный порядок. Кажется мелочью, но для кластеров где мастер должен подняться раньше реплик - это принципиально.
  • Headless Service со стабильными DNS-записями. Каждый под получает свою DNS-запись вида postgres-0.postgres.namespace.svc.cluster.local. Это позволяет подам обращаться друг к другу по предсказуемым адресам, что и нужно для репликации.

Для сравнения, если попытаться сделать то же на Deployment: поды получат случайные суффиксы вроде postgres-a3f7b, после перезапуска суффикс другой, DNS-запись другая, Volume может примонтироваться другой. Реплика теряет мастера, мастер теряет реплику, кластер рассыпается.

PodDisruptionBudget - второй важный кусок

Вместе со стабильными StatefulSets в 1.5 нормально заработал PodDisruptionBudget. Это ограничение на то сколько подов из группы может быть недоступно одновременно при запланированных операциях - дрейн ноды перед обслуживанием, обновление кластера.

Практический сценарий: трёхнодовый кластер Postgres, делаем обновление нод по очереди. Без PDB Kubernetes может начать дрейнить несколько нод подряд - и кластер потеряет кворум. С PDB указываем minAvailable: 2 и Kubernetes учитывает это при планировании: следующую ноду не тронет пока под с предыдущей не восстановился.

Это не теоретический сценарий - именно такая ситуация у нас случилась однажды во время ручного обновления ноды. Кворум не потеряли, но было напряжённо.

Что мы из этого делаем прямо сейчас

Пока не торопимся переезжать. StatefulSets вышли в бету декабрь 2016, у нас январь 2017 - это значит что реальных историй о том как это ведёт себя под нагрузкой в продакшне ещё мало. Не хочется быть первопроходцами в чужом минном поле.

Конкретный план:

  • Redis - кандидат первый. Stateless относительно данных (кеш), использование StatefulSet для Redis Sentinel выглядит разумным. Начнём с него на staging в ближайшие недели.
  • PostgreSQL - кандидат второй, но с осторожностью. Patroni поверх StatefulSets выглядит интересно, но там своя операционная сложность. Пока изучаем.
  • RabbitMQ - пока не трогаем. Есть нюансы с кластеризацией через Erlang cookie и hostname, которые теоретически решаются StatefulSets, но практики ещё не набрали.

Базы на отдельных ВМ никуда не денутся для проектов где они уже работают и работают нормально. Переезд ради переезда не делаем.

Зачем это вообще нужно если можно на ВМ

Резонный вопрос. Ответ не в том что ВМ плохи - они отлично работают. Ответ в операционной унификации: если всё описано как Kubernetes-манифесты, включая stateful-компоненты, то процедуры DR, масштабирование, обновление конфигурации становятся одинаковыми для всего стека. Не надо держать в голове разные инструменты для разных типов нагрузки.

Но это рассуждение работает только когда инструмент достаточно зрелый. Helm нам в этом помогает - charts для stateful-приложений уже появляются в репозиториях. Вопрос в том насколько эти charts проверены реальной нагрузкой.

Смотрим, тестируем, в прод не спешим.

Контакт

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

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