Привет, Хабр! Наверняка каждый разработчик или администратор сталкивался с ситуацией, когда для проверки гипотезы или нового функционала срочно нужна «чистая» база данных. Приходится либо искать свободный сервер, либо разворачивать всё локально, тратя время на установку и настройку. А если таких тестовых баз нужны десятки для команды или разных команд? У наших клиентов мы видели целый зоопарк из PostgreSQL разных версий и конфигураций, поддержка которых превращалась в головную боль. Именно эту проблему — создание «одноразовых» и легковесных баз по одному клику — мы и решили. Меня зовут Сергей Гонцов, я занимаюсь развитием СУБД, основанной на PostgreSQL, которая совсем недавно перешла «под крыло» Arenadata и называется теперь Arenadata Prosperity (ADP). В этой статье расскажу нашу историю, как мы готовили свой DBaaS-сервис.

Откуда пришла идея

Давно прошли уже те времена, когда считалось, что базу данных в контейнере запускать нельзя или это идейно неправильно. Уже есть и множество примеров, и большой наработанный опыт ИТ-гигантов («Амазон», «Гугл» и даже Microsoft). DBaaS и вариантами его реализации мы, конечно же, интересовались, но изначально планов его создания в рамках нашего продукта не было. При этом по пути развития продукта и его внедрения мы неоднократно видели самые разнообразные инфраструктуры баз данных и решения по масштабированию инстансов СУБД (не серверов СУБД, а инстансов или сервисов), причём часто это сопровождалось зоопарком разных версий СУБД на базе PostgreSQL, в том числе от одновременно от разных вендоров с разными настройками, разной совместимостью (от набора расширений до набора функций ввиду разных версий), — всё это требовало отдельных усилий как по обслуживанию,  администрированию, так и по согласованию настроек, функций, расширений.

В повседневной работе нередко возникает потребность быстро развернуть СУБД для проведения тестов — от проверки настроек до работы с готовыми наборами данных. При этом требования к производительности обычно минимальны, важна именно скорость и простота подготовки окружения.

Мы пришли к идее встроенного DBaaS-сервиса, работающего непосредственно внутри нашего продукта. Он должен позволять разворачивать экземпляры СУБД за считаные секунды, в том числе через API, использовать их для нужных задач, а затем также быстро удалять. При этом должна быть возможность быстро и легко выгрузить результаты работы — базу данных или резервную копию.

Выбор и внутренняя борьба за архитектуру

Выбор платформы для реализации DBaaS был очевиден: Kubernetes с использованием statefulSet-приложений и локального хранения данных в Persistent Volume. Такой подход позволяет сохранять результаты работы: базу данных, конфигурации, WAL-журналы и логи. На сегодняшний день существует достаточно публичного опыта эксплуатации PostgreSQL в Kubernetes, поэтому этот выбор не вызывал сомнений.

Основные вопросы лежали в инженерной плоскости: как оптимально организовать кластер Kubernetes и подключаемые Persistent Volume (PV) для хранения данных СУБД, конфигураций и логов; как построить масштабируемый кластер с возможностью подключения новых физических или виртуальных хостов (или реализовать масштабирование иным способом). Наш опыт подсказывал первый вариант, однако внедрять сложную архитектуру внутри продукта мы не хотели — важно было сохранить простоту решения. Дополнительную сложность создавали задачи автоматизации установки и настройки Kubernetes с подключением PV на заданное количество хостов под разные операционные системы при сохранении гибкости и удобства для конечного пользователя (в соответствии с нашей внутренней концепцией автоматизации установки всех компонентов и снижения порога входа в продукт). И так как кластер Kubernetes должен быть внутри нашего продукта, то все вопросы по развёртыванию кластера, автоматизации развёртывания мы должны решить сами. С другой стороны, тем, кому нужен «навороченный» и правильный кластер Kubernetes под какие бы то ни было задачи, развернут его сами. Дополнительно мы пришли к тому, что точно не хотели бы обременять пользователя тем, чтобы он озадачивался проблемами проектирования внешнего подключаемого хранилища для кластера Kubernetes и заполнял множество параметров при его развёртывании. 

Было принято нестандартное решение: отказаться от многоузлового кластера k8s и упростить процессы установки, настройки и запуска. Кластер должен был устанавливаться максимально легко, насколько это возможно. Для этого было выбрано разворачивание заранее подготовленного k8s в docker-контейнерах, что позволило полностью пропустить этап ручного конфигурирования при запуске DBaaS-сервиса внутри продукта. Среди доступных решений остановились на kind. Масштабирование не исключалось — планировалось реализовать его другим способом.

Выбор легковесного kubernetes-дистрибутива оказался непростым

Не из-за отсутствия вариантов или их непригодности — наоборот, их слишком много (K3s, Minikube, MicroK8s, k0s, kind) и каждый способен справиться с нашей задачей. Чувствуешь себя ребёнком перед прилавком с конфетами — не знаешь, что взять. Вместо поисков «идеального» варианта пошли от обратного: начали отсекать то, что хуже подходит под наши требования. MicroK8s — привязка к snap-пакетам, сразу мимо. Minikube — потенциальные проблемы с драйверами, тоже мимо. K3s и k0s подойдут, но требуют лишних действий, чтобы корректно запускаться внутри Docker (что важно для наших тестов). В итоге остановились на kind, как наиболее удобном варианте.

В итоге мы получили такой уровень вложенности нашей СУБД, запускаемой в рамках DBaaS-сервиса: инстанс СУБД → pod → docker → host (гостевой хост), а дальше ещё бывает виртуализация и хост платформы виртуализации → физический хост. Оставалось только всё это реализовать... 

Видимые докер контейнеры

Забегая вперёд, если не знать, что на хосте (на DBaaS-хосте, который разворачивается «по одной кнопке») есть k8s, то мы видим всего лишь два докер контейнера:

Поскольку внутри этих контейнеров работает локальный кластер Kubernetes, можно увидеть следующие запущенные поды:

Два из которых — это наши «прикладные» инстансы СУБД (в нашей терминологии DBaaS-юниты), один под — агент телеграфа для сбора данных мониторинга / метрик, остальные поды (Pod)— системные от локального кластера Kubernetes.

Да, в рамках такой реализации DBaaS пока не поддерживается высокая доступность и отказоустойчивость, а также правильное и нативное масштабирование, но нам ничего не мешает поднять второй (и третий, и далее нужное количество) DBaaS-хост (установив его «по кнопке» за считаные минуты) масштабируя их таким образом, и далее при большом желании поэкспериментировать, можно настроить репликацию между двумя подами вручную размещаемыми на разных DBaaS-хостах. 

Для тех же случаев, когда нужна полноценная отказоустойчивость и высокая доступность, у нас есть варианты кластерного развёртывания ADP.

История реализации и подводные камни, которые мы собрали

С самого начала одной из идей при реализации DBaaS было предусмотреть возможность развёртывания СУБД (как DBaaS-сервиса) как через встроенный UI, так и через API, с которым могут взаимодействовать внешние инструменты.

Вторая идея — это получение максимально быстрого доступа к СУБД в DBaaS, т. е. ожидание доступности СУБД, с нашей точки зрения, должно составлять несколько секунд (фактически время развёртывания DBaaS-сервиса должно быть равно максимальному времени старта пода со всеми скриптами). Но как же выбор порта доступа или установка пароля пользователя postgres при развёртывании сервиса DBaaS? Мы решили максимально отказаться от всех возможных диалогов взаимодействия с пользователем и при старте DBaaS-сервиса самостоятельно генерить:

  • порт доступа к PostgreSQL. Одного 5432 на всех же не хватит, каждому инстансу нужен свой порт);

  • пароль для postgres (который пользователь при желании, конечно же, может сменить).
    Таким образом, мы свели время развёртывания DBaaS-сервиса от нажатия кнопки «Хочу» до времени старта заготовленного шаблонного подa к нескольким секундам.

Под (Pod) с СУБД поднимается за считаные секунды, выдавая пользователю достаточный объём реквизитов, чтобы начать работать с СУБД прямо сейчас
Под (Pod) с СУБД поднимается за считаные секунды, выдавая пользователю достаточный объём реквизитов, чтобы начать работать с СУБД прямо сейчас

Реализация DBaaS у нас разделилась на три части:

  • разработка решения с локальным kubernetes-кластером (DBaaS-хост в нашей терминологии) в докер-контейнерах — т. е. платформу для DBaaS, и чтобы её также можно было удобно «по кнопке» разворачивать на любой пустой linux-хост; 

  • разработка движка для DBaaS (этих самых API, которые будут управлять процессами развёртывания);

  • проектирование UI, в котором будет несколько заветных кнопок: «разверни мне DBaaS-хост» (платформу DBaaS), «подними мне СУБД в Kubernetes» (DBaaS-юнит).

«Локальный кубер»

Для обеспечения функционирования DBaaS-хоста мы используем один контейнер, совмещающий control-plane и data-plane кубера. Несмотря на то что kind позволяет разворачивать дополнительные data node и подключать к кластеру, в нашем случае это не имеет смысла, так как развёртывание мультинодовой конфигурации на одном хосте нужно только для тестирования кубера, а это не являлось нашей целью.

Для обеспечения доступа к созданным экземплярам СУБД в Kubernetes нам необходимо было каким-то образом пробрасывать трафик снаружи к конкретным подам. Так как при развёртывании kind в control plane автоматически настраивается проброс диапазона портов с хоста в control plane, то нам не пришлось особо выбирать среди инструментов для реализации — использовали штатный iptables c DOCKER_USER chain для динамического маппинга внешних портов на порты k8s service network. Мы выделили диапазон из 3000 портов для работы NAT — этого гарантированно хватит для запуска нужно количества экземпляров СУБД на одном DBaaS-хосте.

Для реализации хранилища мы использовали штатный механизм hostpath (в одноузловом исполнении кубера под не сможет уехать за пределы данного узла). Минусом этого механизма является то, что данные остаются после удаления PV. Мы рассмотрели несколько различных вариантов, включающих чистку данных удалённых разделов по крону и удаление разделов как часть операций обслуживания сервером управления. В итоге было решено сделать свой небольшой оператор, реализующий finalizer для PersistentVolume (решили, что наиболее логичным и простым в эксплуатации будет использование штатных механизмов кубера).

Движок для управления DBaaS

Так как наш продукт уже включает движок, который через агента управления и ansible обеспечивает необходимые процессы автоматизации и управления, оставалось лишь добавить в него новые операции для работы с DBaaS: реализовать операции развёртывания и удаления для DBaaS-хоста (развёртывание платформы DBaaS) и DBaaS-юнита (экземпляр СУБД в поде), а также операции остановки/перезапуска для DBaaS-юнитов. 

Наличие k8s как основы под развёртывание DBaaS-юнитов позволило нам гибко управлять юнитами через API k8s. Мы можем полноценно отслеживать состояния подов вплоть до получения логов по контейнерам, упростить менеджмент ресурсов. В перспективе подобные сценарии могут быть применены и на внешних k8s-кластерах. 

UI для DBaaS

Новых элементов интерфейса было немного, поэтому их разработка не вызвала сложностей. Основное время заняли обсуждения — запрашивать пароль для пользователя postgres или генерировать его автоматически, определить пределы выбора ресурсов DBaaS-юнита, продумать способ реализации этого выбора и форму уведомления о том, что пароль будет показан только один раз. 

Подводные камни и проблемы 

Проблемы тестирования: внутренний процесс юнит-тестирования завязан на контейнеры, чтобы гарантировать воспроизводимость конфигураций. Вводить отдельную схему с виртуальными машинами только ради одного модуля DBaaS не хотелось: это ломало уже отлаженный процесс. При этом требовалась полная изоляция тестируемых процессов внутри тест-контейнера. Возникла проблема: Docker внутри контейнера не работал с overlayFS. Решением стало добавление волюмов, которые монтировались в нужные пути, чтобы Docker корректно запускался внутри тестового окружения. Такой подход оказался гораздо легче, чем поднятие новых виртуалок на каждое изменение, хоть и привёл к своеобразной конструкции kubernetes-in-docker-in-docker.

Второе, с чем пришлось столкнуться, — овербукинг ресурсов в кластере. Проблема рабочая и типовая, поэтому сначала был рассчитан допустимый уровень переподписки по CPU, затем добавлено правило проверки доступности ресурсов, запускаемое после нажатия условной кнопки «Развернуть DBaaS-юнит». Кроме того, для того чтобы у пользователя не было соблазна аллоцировать сразу же все доступные ресурсы на DBaaS-хосте при создании первого же DBaaS-юнита, мы реализовали механизм выбора объёма вычислительных мощностей с заранее определёнными ограничениями:

Также мы столкнулись с проблемами с резервными копиями, созданными через наш штатный UI. Как оказалось, сделать бэкап совсем несложно, основная трудность заключается в том, чтобы удобно забрать его из PV. Тут на помощь опять пришёл wal-g с его поддержкой S3.

Что в итоге мы имеем

Фактически был реализован продукт в продукте, позволяющий за считаные секунды поднимать инстансы СУБД без необходимости привлечения администраторов или специалистов по Kubernetes. Итого мы имеем:

  1. Возможность установки DBaaS-платформы (DBaaS-хоста) фактически на любую Linux ОС, т. к. всё запускается в контейнерах.

  2. Избавление администраторов/пользователей от необходимости скачивать специальный софт и настраивать его — весь софт и все конфигурации поставляются в составе дистрибутива. DBaaS-платформа (DBaaS-хост) устанавливаются по «одной кнопке» из UI, причём, если мало одного DBaaS-хоста, можно по этой же «одной кнопке» доустановить/развернуть нужное количество DBaaS-хостов.  

  3. Отсутствие необходимости подключаться к чужим облакам — фактически это своё «микрооблачко», которое в «тучки» пока не объединяется и живёт автономно, но при этом его можно масштабировать.

  4. Время подготовки «облачка» (развёртывания DBaaS-хоста) составляет в среднем 5–7 минут с момента нажатия кнопки «Развернуть». Дополнительное время на запуск экземпляра СУБД в формате DBaaS-сервиса (СУБД в поде) — от 2 до 5 секунд, что можно считать практически мгновенным развёртыванием.

  5. Минимизация операций или задач, в рамках которых должен быть привлечён администратор для обслуживания «микрооблачка»/DBaaS-сервиса.

  6. Квотирование ресурсов и встроенная защита от овербукинга ресурсов в кластере (с учётом допустимой переподписки). 

  7. Простые и интуитивно понятные для пользователя инструменты получения DBaaS-сервиса. 

  8. Производительность инстанса PostgreSQL, не отличающуюся существенным образом в худшую сторону от производительности этого же инстанса, запущенного на выделенных фиксированных ресурсах. При этом есть и определённые бонусы в «бесплатной» лучшей производительности при недозагруженности DBaaS-хоста. 

С точки зрения производительности и накладных расходов мы понимали, что архитектура «инстанс СУБД → pod → docker (kind) → виртуализация → хост» может вызывать вопросы о производительности. Наши внутренние тесты с помощью pgbench показали, что падение производительности при сравнении аналогичных по ресурсам инстансов PostgreSQL (один в DBaaS с ресурсами 2 ядра и 3 Гб памяти — максимум, который можно выделить для DBaaS-юнита, другой на отдельном хосте в виде виртуальной машины с ресурсами 2 ядра и 3 Гб памяти) составляет в среднем не более 5–7% в классическом тесте tpcb-like. Но это только в том случае, если DBaaS хост нашпигован инстансами с PostgreSQL (DBaaS-юнитами) под завязку, при этом они утилизируют все запрошенные CPU. Мы сознательно не используем лимиты по CPU, поэтому полная утилизация может быть в двух случаях: когда все инстансы равномерно генерят планируемую нагрузку или когда один инстанс генерит нагрузку, значительно превышающую запрошенные значения.

Первый вариант при наличии десятков инстансов возможен конечно, но все же маловероятен, именно поэтому мы используем «переподписку» по CPU — наш модуль управления позволяет создавать ресурсы с суммарным запросом по CPU, превышающим возможности сервера в соответствии с заданным параметром. Но в большинстве случаев для профиля использования DBaaS — нагрузка идёт только от используемых в данный момент экземпляров. Они будут использовать все возможные CPU, что даёт лучшую производительность в 2–3–4 раза и выше. И тут как раз видны все плюсы использования инстансов PostgreSQL (DBaaS-юнитов), запускаемых в Kubernetes, даже с учётом такого количества «прослоек»: никакая «простаивающая» виртуалка c выделенными CPU на «простаивающей» платформе виртуализации не выдаст в 3–4 раза лучшую производительность и сами DBaaS-юниты не всегда загружены на все 100% (в нашей концепции). Во втором варианте — чрезмерная утилизация одним инстансом легко отслеживается мониторингом, на основании данных которого можно принять решение: погасить проблемный инстанс или порадоваться, что тяжёлая и важная задача в СУБД может быть выполнена быстрей, чем могла быть выполнена на обычном железе с конфигурацией, соответствующей конфигурации юнита.

Мы сочли, что указанная выше разница в производительности является вполне приемлемой платой за гибкость и скорость развёртывания, а также за «бесплатную» лучшую производительность в те моменты, когда DBaaS-хост недозагружен или другие DBaaS-юниты простаивают, тем более что сервис не рассчитан на high-load-задачи. 

По накладным расходам на память софт DBaaS-хоста (kind, docker) в простое после развёртывания потребляет около 650–750 Мб оперативной памяти, только что установленный DBaaS-юнит (инстанс PostgreSQL) потребляет 110 Мб.

Планы на будущее

Поставленные цели на текущий момент в целом достигнуты. В дальнейшем планируется развитие DBaaS в направлении повышения отказоустойчивости и расширения его функциональности за счёт интеграции различных прикладных сценариев и сервисов:

  • автоматизированная миграция небольших баз в DBaaS с апгрейдом на лету;

  • создание копии СУБД (из какой-то уже управляемой из Prosperity Advanced СУБД) с автоматическим развёртыванием её в DBaaS с одновременной обработкой «на лету» (усечением данных, деперсонализация данных);

  • расширенное отображение утилизированных и доступных ресурсов DBaaS-хоста;

  • возможность интеграции и подключения внешнего k8s-кластера для развёртывания экземпляров СУБД.

Заключение

Наша изначальная задача была не в создании облака, а в решении конкретной боли клиентов: ручного развёртывания множества экземпляров PostgreSQL на одном хосте с последующим трудоёмким администрированием, борьбой за изоляцию и удалением лишних данных и инстансов. Мы хотели, чтобы этот процесс был автоматическим, быстрым, основанным на понятных шаблонах, с полной изоляцией экземпляров и возможностью их мгновенного удаления.

Перепробовав разные подходы — от простого скриптования с разделением прав доступа до полной виртуализации, — мы пришли к варианту с контейнерной виртуализацией и реализовали её на Kubernetes. В итоге «облако» стало естественной частью нашего продукта. Такой формат не заменяет крупные публичные DBaaS-платформы, но отлично подходит для развёртывания лёгких СУБД с периодическим бэкапом, а также для прототипирования, CI/CD и быстрых экспериментов, где ценится скорость запуска и простота управления.

Комментарии (0)