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 проверены реальной нагрузкой.
Смотрим, тестируем, в прод не спешим.
- Kubernetes 1.1: namespaces на практике и HPA который не дал завалить соседей · 10 ноября 2015
- Helm: менеджер пакетов для Kubernetes от Deis · 1 декабря 2015