МегаполОС или как мы были вынуждены переизобрести DevOps
Вступление
Это история о том как небольшая группа IT-шников хотела автоматизировать свою работу, а в результате решила переизобрести DevOps-заново, и (с точки зрения этой группы) добилась прогресса.
Кто мы
Если кратко, то мы небольшая группа хорошо знакомых друг с другом людей, которые последние несколько лет занимались разными проектами. Туда входило и создание культурных центров в Москве, и чтение лекций, и IT-разработка. Сейчас мы называемся «Цифровая Протопия». В 2021 году большинство проектов у нас завершилось (удачно или не удачно), а в 2022 году мы решили запустить новые – в основном open source проекты для автоматизации горизонтальных команды и сообществ. Но сначала было нужно разобраться с наследием прошлого.
В истории, про которую я пишу в этой статье, занимались два человека из этой команды – я (Дмитрий Лейкин) и Федор Моросеев. Мы занимались и занимаемся описываемым в статье проектом в свободное от основной работы время.
Наследие
От предыдущего этапа нашей деятельности у нас осталось множество IT-разработок. Какие-то остались недоделанными, какие-то были выпущены в продакшен, какие-то были опубликованы как open source, какие-то остались закрытыми, какие-то нужно было архивировать, а какие-то опубликовать как демо-версии. Всё было свалено в кучу на нескольких VPS-ках. И чтобы продолжать деятельность, стало важно привести все это во вменяемый вид.
Надо все контейнеризировать
Одна из проблем, с которой мы столкнулись на предыдущем этапе – это сложность разработки и поддержки большого числа проектов одновременно. Мы поняли, что нам не хватало автоматизации нашей деятельности – разработки, тестирования, деплоя и т. д. Короче говоря, мы поняли, что нам нужен DevOps. Необходимо контейнеризировать все проекты, автоматизировать работу с ними и дальше вести разработку нормально.
DevOps-инженер со стороны
Мы начали решать задачу с того, что попросили знакомого DevOps-инженера настроить нам среду для работы. Он попросил доступы, попробовал развернуть DevOps-инструменты (kubernetes, runner и gitlab на своем железе), помог составить список текущих проектов и дальше дело встало в тупик. В течении двух месяцев он настраивал и правил среду развертывания, но так и не смог развернуть на ней наши проекты. Поэтому мы решили искать другой путь.
Самостоятельная попытка DevOps и что нас не устроило.
К этому моменту Федор сам глубже погрузился в DevOps и предложил мне настроить все самому по его инструкциям. Он предложил установить связку Gitlab/Ansible/Docker Swarm, а дальше постепенно переходить на Кубернетес.
Установить первую связку оказалось несложно, и я не очень понимал, что именно заняло столько времени у нашего знакомого.
Но установив все необходимое, я сразу увидел, в чем проблема – ну написал я playbook, который собирает образ, выкатывает на сервер, настраивает nginx и certbot. А если я хочу изменить доменное имя? А если я хочу удалить одну из инсталляций? Что, каждый раз придется писать скрипты для более-менее стандартных задач?
Тогда было принято решение попытаться перейти на Кубернетес. По опыту работы Федора, он знал что в кубернетесе рутинные задачи (работу с доменами, сертификатами, репозиториями и прочим) можно автоматизировать.
И тут у меня начались проблемы. Во-первых, было непонятно, как установить Кубернетес. В интернете было огромное число инструкций, и все предлагали сначала написать кучу конфигов, и только после них что-то ставить. Попытки понять какие конфиги необходимы, и понять почему кубернетес не может, как докер, работать из коробки, ничего не дали.
Тогда было предложено установить надстройку над кубером – систему k3s. Она в отличии от голого кубера установилась сразу. Но далее надо было прикрутить к куберу необходимый для наших задач функционал и интегрировать его с gitlab. Судя по описанию, это все требовало написания множества yml-конфигов, на порядок более сложных, чем docker-compose.
Еще упоминались системы типа helm и werf, которые умели шаблонизировать конфиги. Они были установлены, но, как будет видно далее, не помогли решить задачу.
Была попытка настроить кубернетес, чтобы хотя бы установить backend и frontend для одного из наших проектов. То есть решить ту же задачу, которую до этого решали через ansible + docker swarm. Конфиги были написаны, но по нужному адресу проекты так и не открывались. После нескольких часов вопросов и ответов было выяснено в чатах, что в k3s работает два балансировщика одновременно и один из них надо было отключить. После этого проект как-то открылся.
Но далее я попытался привязать кубернетес к docker registry из гитлаба. Чтение мануала заставило меня подумать, что это шутка. Там буквально было написано: «Возьмите логин и пароля от реестра, склейте их в строчку, зашифруйте в base64, вставьте в большой yml файл, зашифруйте его в base64, вставьте в еще больший yml-файл и загрузите в кубер».
Как ни странно, выполнение этой безумной инструкции помогло, и кубер начал обращаться к реестру. Правда, через раз. Почему – разбираться мне уже не захотелось.
Что не так с существующим DevOps
И вот здесь я и Федор задались очевидным вопросом. Почему все так сложно? Почему для таких стандартных задач как развертывание контейнеров, управление ими, подключение к ним базы данных, доменного имени, ssl-сертификита, бэкапов и других абсолютно стандартных вещей, нужно так много усилий? Почему концепция «инфраструктуры как код», которая выглядит очень разумной, на практике достаточно неудобна? Почему нужно так много инструментов для решения одной и понятной задачи?
Тут же всплыл пример обратной ситуации. Вы знаете что такое «виртуальный хостинг»? Это когда за минимальные деньги вы получаете небольшой кусочек сервера с предустановленным, например, вордпрессом, куда уже подключены доменное имя и база данных MySQL. Да-да, то что называется LAMP-стэком. При этом управлять этим может даже непрограммист, через удобную панель управления хостингом. Почему для контейнеризированных проектов нельзя сделать то же самое?
Начались попытки проектирования и изучения существующих альтернатив. В процессе выяснились удивительные вещи. Например то, что кубернетес создавался как эксперимент с единственной целью – проверки гипотезы о том, что докер-контйнеры можно запускать на кластере, и больше ни для чего. Или то, что докер и кубер создавались для работы исключительно со stateless-приложения, и как следствие, к докеру даже volume нельзя добавить, не уничтожив контейнер и не создав его заново. И многие другие абсурдные вещи.
В итоге стало ясно, что существующая экосистема devops похожа на лоскутное одеяло, где есть множество разных инструментов, которые приходится интегрировать, где нужно писать много конфигов, где все равно надо писать скрипты даже для стандартных задач, и где для мало-мальски крупных проектов нужно нанимать команду devops-инженеров, которые будут всё настраивать.
При этом было ощущение, что все эти проблемы в истории IT-индустрии уже когда-то возникали. И что они уже были когда-то решены.
Операционная система второго порядка
И тут, в процессе исследования всего этого, меня и Федора осенило.
Мы поняли на что похожи задачи, которые пытаются решить существующие инструменты.
Это задачи которые давным-давно умеет решать такой класс IT-приложений как... операционные системы.
Что делают операционные системы? Они являются прослойками между отдельными программами и железом, на котором они запущены. Они позволяют делать стандартные операции одинаково – устанавливать и удалять программы, давать им через API доступ к друг другу и к оборудованию, абстрагируют разнообразие «железа» под единым интерфейсом HAL (Hardware abstraction layer), позволяют запускать и закрывать программы, следят за ресурсами процессора и памяти которые они используют... Разве не то же самое мы хотим для серверных контейнеризированных приложений?
И стало понятно что нам нужно. Нам нужна операционная система второго порядка!
Что значит второго порядка?
Это значит, что эта операционная система работает поверх обычной операционной системы (или нескольких – если на кластере). Программами для ОС второго порядка являются контейнерами. А «железом» (и это самое удивительное!) – приложения операционной системы первого порядка!
Что имеется в виду?
На серверах существует ПО, которое не рекомендуется класть в контейнеры. Такое как СУБД или nginx. Потому что оно обычно работает постоянно, в ограниченном числе экземпляров и по сути предоставляет общий ресурс для любого числа контейнеров.
Это ПО с точки зрения ОС второго порядка – то же самое, что железо для ОС первого порядка. И, как и для обычного железа, для этих программ нужны «драйвера» – специальные программы, которые позволят ядру нашей ОС единообразно общаться с любым «устройством типа БД» или «устройством типа веб-сервер».
И тогда получается, что стандартные операции типа «собрать образ», «развернуть контейнер», «свернуть контейнер», «подключить к контейнеру БД», «сделать бэкап», «восстановиться из бэкапа», «подключить доменное имя», «получить SSL-сертификат» становятся стандартными и могут быть автоматически выполненными в нужном порядке.
При этом, поскольку ядро нашей ОС работает постоянно, мы всегда можем узнать, что в каком состоянии, что к чему подключено и поменять любой параметр.
Так мы поняли, что нам нужно написать такую ОС. Мы ее назвали - «МегаполОС».
Как устроен МегаполОС
Разработка МегаполОСа ведется уже более 9 месяцев. Проектировали его я и Федор, до публикации в open source (в мае 2023 года) я его писал в одиночку. Сейчас к его разработке подключились и другие программисты, но этого недостаточно для скорого выхода стабильного релиза. Сейчас проект находится в альфа-версии.
Мегаполос объединяет множество машин (серверов) в единый кластер. Каждый сервер является узлом (нодой) этого кластера.
МегаполОС на данный момент состоит из ядра, драйверов и веб-GUI.
Ядро – это приложение, которое запускается на каждой ноде, включает ее в кластер и берет его под свой контроль. Оно управляет серверной инфраструктурой и контейнерами, развернутыми в кластере.
Драйвера – это самая интересная часть. Я ранее описывал, что мы относимся к серверной инфраструктуре (причем в первую очередь к программной) как к «устройствам» в операционной системе. А для управления устройствами нужен слой абстрагирования устройств (HAL) и драйвера.
Например: PostgreSQL – это «устройство» типа СУБД. Почему это устройство? Потому что оно предоставляет свои ресурсы (базы данных) произвольному числу контейнеров, и задача ОС - регулировать доступ контейнерных приложений к этому ресурсу.
С точки зрения ядра устройство типа СУБД – это стандартизированная сущность, у которой есть функции «создать БД», «удалить БД», «создать бэкап», «восстановить бэкап» и другие. Как конкретно СУБД это делает – решает драйвер. Драйвер – это контейнерное приложение с дополнительными привилегиями, которое занимается управлением своим устройством. На данный момент мы написали несколько драйверов для популярного серверного ПО, вроде mongodb, nginx, gitlab, certbot и других.
При этом, что является особо интересным – драйвер может «установить устройство», то есть поставить соответствующую программу на ноду или в контейнер. Последнее мы называем «виртуальным устройством».
Веб-GUI – это, естественно, интерфейс для управления МегаполОСом. Мы планируем сделать его приближенным к GUI операционной системы – то есть к простому способу управления сложной системой. Чтобы управлять кластером было не сложнее управления LAMP-хостингом.
Другая фишка, которая мне очень нравится – это то как megapolos управляет volume-ми. Как было указано выше, чтобы подключить или отключить volume в docker, надо уничтожить и заново создать контейнер. Это выглядит как бред – зачем полностью перезагружать приложение для такой простой операции?
Подобно хранилищам в обычных ОС, в МегаполОСе все volume управляются ядром. Пользователь может через API или GUI управлять ими. И в нем есть такое понятие как динамические volume. К каждому контейнеру монтируется volume «/megapolos» и система в любой момент может примонтировать в него директории с хоста как поддиректории. То есть мы имеем простое объектное хранилище, встроенное в МегаполОС.
В частности, можно указать, какое volume использовать для бэкапов СУБД, и МегаполОС сам будет его подключать и отключать в нужный момент.
Еще раз подчеркну то, что МегаполОС работает постоянно. Он постоянно отслеживает ситуацию и дает команды для ее изменения. Это в частности означает то, что связь между ядром и приложениями – двусторонняя. У контейнерных приложений есть доступ к API ядра, а у ядра есть доступ к приложениям. В частности, драйвера могут просить ядро выполнить shell-команды на нужной ноде для управления устройствами.
Теперь о технической части.
На данный момент МегаполОС находится в стадии альфа-версии. Уже написаны, как было указано выше, ядро, драйвера и GUI. Основные архитектурные элементы были реализованы, но пока что не была реализована поддержка множества нод. Также сам код еще будет рефакториться и отлаживаться.
Стэк МегаполОСа – это rqlite + Docker + node.js + GraphQL + React.
rqlite – это распредленный sqlite. Мы специально искали распредленную реляционную СУБД, поскольку у нас очень строгие взаимоотношения между сущностями. Планируется переход на более полноценную распределенную реляционную СУБД. Вы можете посмотритеть структуру БД здесь: https://gitlab.com/megapolos/megapolos-core/-/blob/main/install/newrqlite.sql
Docker – управление контейнерами. В будущем планируем перейти на containerd.
node.js – на нем написаны ядро и драйвера. Для не очень часто происходящих действий этого может быть достаточно. Высокопроизводительные части мы планируем переписывать на go. Мы выбрали этот стэк, потому что у нас происходит много параллельных и асинхронных процессов.
GraphQL – это язык API, через который происходит взаимодействие с ядром и драйверами. Мы выбрали это API, потому что оно типизировано
React – на нем написан GUI
Как пользоваться Мегаполосом
Для этого я захожу в GUI, который мне показывает список приложений (Applications). Приложение - это набор логически связанных образов, из которых можно создавать контейнеры.
Когда я хочу работать с приложеним, я создаю его экземпляр (Instance), состаящий из контейнеров, связанных виртуальной локальной сетью. Каждому контейнеру автоматически присваивается порт в виртуальной сети докера (кстати, одна из вещей которые меня удивили в Кубернетесе – там можно двумя контейнерам присвоить один и тот же порт в одной и той же сети, и меня никак не предупредят, о возникновении конфликта).
Я могу подключить к контейнеру устройство типа builder - которое занимается сборкой контейнеров. Этим устройством может быть gitlab ci, локального докера или что-нибудь еще. Вне зависимости от реализации, контейнер будет собран из образа. Я также могу указать специальные env-параметры и volume для этого контейнера.
При помощи драйверов я также могу подключать к контейнерам базы данных, доменные имена и SSL-сертификаты. Я в любой момент могу подключить или отключить что-либо из них или поменять их параметры (например, сменить доменное имя). При этом МегаполОС произведет все необходимые для этого действия – поменяет конфиги, пересоберет контейнеры, пропишет ENV-параметры, получит сертификаты, проследив, чтобы не возникало конфликтов. Например, чтобы разным экземплярам приложения не было присвоено одно доменное имя.
Когда я хочу добавить в систему драйвер, я добавлю его по сути как обычное приложение, но через другой раздел GUI - диспетчер устройств. У драйвера есть манифест, который помогает МегаполОСу определить его тип и параметры. Мы планируем также добавить поддержку манифестов для любых приложений, чтобы упростить их настройку.
Также есть раздел управления volume-ми, где я могу создать как статический volume, строго привязанный к каталогу на ноде, так и автоматический, расположение которого на ноде автоматически создается МегаполОСом.
На данный момент мне успешно удалось поставить МегаполОС на тестовой сервер, и благодаря ему развернуть на сервере несколько контейнерных приложений, подключить к ним БД, домены и SSL-сертификаты. Конечно, продукт еще сыроват, но реализуемый им способ управления инфраструктурой кластера выглядит проще и нагляднее, чем настройка ansible или kubernetes.
Текущий интерфейс Мегаполоса. Он очень сырой и будет полностью переработан, но дает представление о сущностях и операциях, с которыми он работает:
Итоги
Поскольку основной функционал МегаполОСа уже работает, мы решили опубликовать его в Open Source под лицензией Apache 2.0 и вести дальнейшую разработку открыто: https://gitlab.com/megapolos
Если вас заинтересовал этот проект и вы хотите с этим помочь – присоединяйтесь к нашему сообществу: https://t.me/megapolos