Управляем миром без боли и консоли: как мы создали софт для работы с распределенными ЦОДами

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Всем привет! Я Максим, бэкенд разработчик и тимлид команды DCImanager. Я работаю в компании ISPsystem уже почти пять лет и считаю себя очень везучим человеком, ведь за эти годы я прошел путь от обычного тестировщика до лида команды, по моему мнению, именно за счет своей удачи =).

Сегодня хочу рассказать вам о новом продукте DCImanager 6, его фичах, возможностях … Нет. Я не хочу этого, я же не маркетолог. Да и это никому здесь не интересно. Давайте ещё раз.

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

Микросервисы, но не микросервисы

Поговорим немного об архитектуре. Предыдущая версия продукта, DCImanager 5, был большим и сложным монолитом, написанным на C++. Но в какой-то момент мы достигли потолка: большое количество легаси-кода, о котором никто не знает всего, ограничения архитектуры, ограничения в используемых языках и библиотеках и даже элементарное ограничение версии языка C++03 — всё это не давало панели нормально развиваться. И не только ей — разработчики не развивались, добавление чего-то нового было болью, приходилось смотреть много мест, которые могут быть затронуты при добавлении новой фичи или исправлении ошибки.

В итоге в 2018 году было принято волевое решение переписать панель. Не распилить, а именно переписать. Забыть о наследии предков… Оглядываясь назад, я понимаю, что это был настоящий вызов и чертовски правильное решение. Вся команда была очень рада, ну ещё бы! Это новый виток развития продукта. Прощай, легаси, прощайте, ограничения для операционных систем, прощай, C++03. Наконец-то мы сможем развивать продукт под нужды пользователей. Также это новые вызовы для разработчиков. Новый стандарт C++17, новые языки, новый микросервисный подход к разработке. Больше не нужно пилить свои альтернативы для сбора статистики или работе с SNMP — достаточно найти сервис или библиотеку, которая сделает это за нас. До сих пор помню, как было интересно продумывать архитектуру для взаимодействия с локациями и коммутаторами, обсуждать это с командой, переделывать несколько раз.

Новая версия платформы теперь упакована в Docker-контейнеры и разбита на множество различных сервисов. Множество… Сколько это? На текущий момент мы имеем почти четыре десятка контейнеров. Четыре десятка, Карл! И это только контейнеры. Если бы мы придерживались правил построения архитектуры на контейнерах один сервис — один контейнер, их было бы 60, или 70, или ещё больше. Не рискну посчитать =). Свои сервисы мы частично разделили по задачам: сервис работы с оборудованием, сервис проксирования BMC, сервис интеграции с LDAP и другие. Но мы решили не разбивать нашу платформу на микросервисы до конца. У нас всё ещё остаётся мини-монолит с основной логикой приложения.

Упаковка сервисов в Docker-контейнеры позволила нам забыть об ограничениях операционных систем — если на ней есть Docker, значит мы можем запуститься. Естественно, на текущий момент мы ограничили количество поддерживаемых систем самыми актуальными: CentOS 7 и Ubuntu 20.04. Но это ограничение искусственное. При необходимости, например подходе EOL (привет, CentOS), мы с небольшими доработками сможем расширить этот список.

Ещё Docker-контейнеры привнесли в нашу жизнь горизонтальную масштабируемость. Одним из слабых мест DCImanager 5 был периодический опрос оборудования. Он выполнялся прямо в платформе и мог сильно нагружать систему. Теперь же мы вынесли весь этот механизм в отдельный контейнер, таких при желании можно запустить несколько на разных машинах. Но на текущий момент мы используем docker-compose для запуска нашего стека, и поэтому нет никакой горизонтальной масштабируемости. Но при необходимости можно будет довольно просто перейти на Docker Swarm или Kubernetes.

Дата-центров много, а DCImanager один

Перейдём к самому интересному — к локациям. Что это? DCImanager 5 изначально разрабатывался с подходом один дата-центр — один DCImanager. Все функции для работы находились в самом монолите, конфигурирование сервисов для установки операционных систем на серверы, работа с коммутаторами, ПДУ, BMC — всё это работало только при прямой видимости оборудования. Клиентам, у которых есть несколько дата-центров, приходилось покупать несколько лицензий, чтобы управлять ими. Ни о какой централизованной системе для управления всей инфраструктурой не могло быть и речи. Когда мы это поняли, решили добавить механизм локаций. Локация — это отдельный дата-центр, в котором выделяется сервер для управления инфраструктурой. DCImanager подключается к серверу локации через SSH и настраивает необходимые сервисы. На сервере локации стоит свой демон — DCImini, который умеет работать с BMC и передавать необходимые данные в сам DCImanager при выполнении диагностики серверов.

И все? Проблема решена? Как бы не так. Помните про легаси? Да… Этот монолит очень сложно было переделать для корректной работы с локациями. Мы сумели вынести часть функций, например конфигурирование DHCP, TFTP, NFS и других сервисов для установки ОС, управление BMC. Но вынести работу с коммутаторами, ПДУ, маршрутизаторами — это оказалось практически невыполнимой задачей. Чтобы управлять коммутаторами из другого дата-центра, клиентам приходилось поднимать SSH/VPN-туннели до сервера локации или навешивать белые адреса на оборудование (что, ясно дело, небезопасно).

Ещё одна проблема этой схемы — DCImini должен был каким-то образом отправлять данные в сам DCImanager, чтобы тот мог доверять источнику. Как это сделать? Передавать данные для авторизации на сервер локации и хранить там? Небезопасно. И что если данные изменятся? Генерировать ключ? А когда и как перегенерировать его, если он будет скомпрометирован? И ещё нужен доступ не только от DCImanager до DCImini, но и в обратную сторону.

Эти ограничения были одними из основных факторов для написания новой системы, поистине единой точке входа для администраторов. И её написание началось с разработки архитектуры взаимодействия с локацией.

«Нерушимые» табу

Прежде всего мы решили — доступ должен быть только односторонний. DCImanager знает о локациях, локации ничего не знают о DCImanager и друг о друге. Так будет лучше для всех. Меньше знаешь — крепче взаимоотношения ;). Хотя какая здесь взаимность, абьюз, да и только =). Но для подобной системы это лучший выход. Не нужно думать об авторизации, но усложняется способ доставки информации от диагностики до платформы.

Второе табу — DCImanager работает с локацией исключительно через SSH. Никаких открытий «левых» портов. Хочешь сконфигурировать сервис — иди по SSH, хочешь работать с коммутатором — иди по SSH, хочешь пойти по SSH — иди по SSH. Конечно, поначалу было сложно с этим свыкнуться и даже просто заставить всё хорошо работать. Приходилось-таки открывать порты для проксирования BMC, порт для Redis (о нём чуть позже). Но со временем мы смогли преодолеть и эту проблему, слава SSH-туннелю.

И самое главное — мы разделили понятия DCImanager и локации. DCImanager 5 сам являлся первой локацией. Из-за этого в код приходилось добавлять костыли для работы с первой и остальными локациями. Теперь платформа и локация логически разделены. При установке DCImanager платформа предлагает настроить первую локацию. Да, это может быть тот же сервер с DCImanager. Но табу не нарушаем — идём на неё по SSH. И теперь мы можем спокойно поднимать платформу на виртуальной машине, например на новой версии VMmanager. А если ещё настроить High Availability, то мы получим платформу, доступную в любое время.

Из-за предыдущих табу мы столкнулись с одной сложностью — как передать данные с локации в DCImanager? При выполнении диагностики сервера мы настраиваем необходимые для этого сервисы: DHCP, TFTP, HTTP и другие. Как узнать, что диагностика или установка ОС завершилась? Периодически опрашивать? Тоже выход. Но не такой элегантный. А ещё ведь есть поиск серверов, который включается на долгое время, и о найденных серверах хочется узнать чуть ли не мгновенно. В итоге мы остановились на механизме уведомлений посредством очереди сообщений. Подключаемся к очереди и ждём новых сообщений. Хорошо, решение есть, осталось определиться с очередью. Есть множество вариантов очередей сообщений, Kafka, RabbitMQ и другие. Как же выбрать? Redis — мы остановили свой выбор на нём. Но почему, ведь Redis — совсем не очередь? Да просто потому, что нам ещё нужна была небольшая база данных на сервере-локации, и здесь мы решили выбрать два в одном: key-value хранилище в качестве базы данных и Redis Streams в качестве очереди сообщений. К тому же, он достаточно легковесный и простой в использовании.

Реализация прослушивания Redis претерпела несколько изменений. Поначалу мы открывали порт наружу. Естественно, на сервис мы повесили пароль. Ещё поначалу мы использовали механизм Pub/Sub. Но в дальнейшем он оказался неудобным, ведь если подключение прервется, то и сообщения мы потеряем. Также мы подключались к Redis-у из C++, для этого написали свою обёртку над библиотекой bredis, так как мы активно используем boost. Но подключение было нестабильным и попросту неудобным.

Затем, осознав все прелести Python, мы вынесли механизм отслеживания в отдельный микросервис (вот они — микросервисы!) и закрыли порт. Теперь для подключения мы поднимаем SSH-туннель. Стало намного проще жить, тикеты по проблемам с подключением начали сходить на нет. Одна из прелестей микросервистной архитектуры — можно хорошо написать необходимый сервис и забыть о нём. Он спокойно будет работать, выполнять свои задачи, и никто его не будет трогать. Сервис уже полгода не менялся, это ли не признак стабильной работы =).

Можжевельник и другое оборудование

Ещё один прекрасный механизм платформы — работа с оборудованием, коммутаторами, ПДУ, ИБП. Платформа сразу была рассчитана на работу с локациями, в отличие от предыдущей версии. Поэтому нужно было сделать сервис, который будет работать с оборудованием через SSH, а не на прямую. Поначалу мы были сильно зациклены на C++, ну ещё бы, все разработчики являлись выходцами из DCImanager 5, мы писали эту платформу годами. Ни о каких других языках не могло быть и речи. «Мы хранители традиций, сидения, стояния и кувыркания».

Поэтому начали мы писать обработчики на плюсах в нашем любимом мини-монолите. Поначалу всё шло неплохо, написали несколько обработчиков по примеру DCImanager 5. И они неплохо работали. Однако приходилось устанавливать на локацию дополнительные утилиты для работы с SNMP и писать свои обёртки, которые идут по SSH и вызывают сторонние программы. Но долго так продолжаться не могло и мы это понимали. И вот однажды мы собрались на мозговой штурм и решили — нам нужен отдельный сервис, чтобы добавление новых обработчиков перестало быть таким сложным. И на Python, чтобы при необходимости можно было воспользоваться уже готовой библиотекой, а не придумывать велосипеды. Ведь именно для этого создан Python =). Результатом мозгового штурма явились два новых контейнера:

  • equip_service — базовый сервис, который знает об обработчиках всё: какие они, что они могут, а также занимается периодическим опросом оборудования;

  • equip_handler — сервис, в котором сосредоточена вся логика обработчиков. Последний находится на сервере-локации и использует нужные нам библиотеки питона для работы с оборудованием. При желании туда можно добавить даже Ansible, для работы с коммутатором. 

Позже часть логики из equip_service перетекла в новый контейнер consumer, который работает с очередью сообщений и может безболезненно масштабироваться для более эффективного периодического опроса оборудования.

В итоге теперь мы пишем обработчики на Python. Для некоторых из них мы уже воспользовались библиотеками, которые облегчают жизнь, например работа с API Cisco Nexus, Arista, Mikrotik и другие. Несмотря на то, что пишем мы на Python, стараемся везде добавить типизацию и проверяем наши сервисы при помощи mypy. Типизацию… В Python… Да, мы настолько заморочились, чтобы поставлять качественный продукт. И к тому же, мы подумали и о пользовательских обработчиках заранее. Некоторые наши клиенты писали свои обработчики в DCImanager 5 для работы с оборудованием. Писать свои обработчики в DCImanager 5 было довольно сложно, вот пример доки. Все на XML-ках, и только попробуй отдать не те параметры, что нужны… Теперь же пользовательские обработчики добавлять очень просто, особенно если заранее натравить на них mypy и pylint. Наследуешь обработчик от базового класса, пишешь реализацию для абстрактных методов, в которые приходит определенный объект и возвращается также вполне конкретный объект. Это достаточно просто и, в качестве примера, один из обработчиков был написан нашим DevOps-инженером. Но не будем углубляться, это уже тема для отдельной статьи =).

BMC втёмную (серую)

Последняя, но не по значимости, тема, которую я хочу затронуть, это проксирование BMC. Это фича, одна из самых интересных, которые были сделаны в DCImanager 5, позволяет получить доступ к BMC, которые находятся в серых сетях. Она была реализована в виде двух модулей: прокирование IPMI через IHTTPD и проксирование IPMI через дополнительный сервер. Почему целых два модуля для одной фичи? Дело в том, что проксирование через IHTTPD работало отнюдь не со всеми производителями, только с Supermicro и Hewlett-Packard. Нам приходилось залазить прямо в ответ BMC и подменять хедеры и данные. Но зато работало оно красиво — можно было прямо в браузере, без лишних окон, открыть Web-интерфейс BMC и выполнить свою работу. Затем мы поняли, что не для всех серверов можно это сделать — под каждого производителя придётся вносить исправления в код, а хотелось иметь всё и сразу. Поэтому появился новый модуль. Он устанавливал на выбранный сервер дополнительное ПО, и мы открывали браузер прямо на этом сервере, а отображали его при помощи NoVNC и Websockify. Но у него был существенный минус. Думаю вы уже догадались о нём — локации. Настраивался только один сервер, который обязательно должен был быть под управлением CentOS 7, и, как с коммутаторами, доступ до BMC должен был быть прямой от этого сервера. Снова нужны были белые адреса на BMC, но какой тогда толк от проксирования? Или вручную настроенные туннели, тоже не предел  мечтаний… А ещё приходилось открывать несколько десятков портов наружу, ну так, чтобы с запасом хватило на все подключения.

Естественно, ни нас, ни клиентов такой подход не устраивал. В новой версии нужно было его изменить. И вот новый мозговой штурм. Однозначно нужен новый сервис, иначе зачем мы переходили на новую архитектуру =). За основу мы взяли вторую версию проксирования, через дополнительный сервер с помощью NoVNC. Как минимум, потому, что IHTTPD больше нет в шестерке. Придуманный сервис состоял из двух контейнеров — клиентская часть, которая организует подключения и управляет временем их жизни, и серверная часть, которая ставится на каждую локацию и запускает браузер, NoVNC и прочие. Наконец-то! На каждую локацию. Больше не нужен дополнительный сервер, ведь в контейнере мы можем его поднять прямо на сервере-локации, не обращая внимания на то, какая там стоит операционная система.

Как и всегда, не только у нас, первая реализация оказалась с проблемами. Сервис съедал много памяти (питоновый websockify — прожорливая вещь, запускает четыре процесса на одно подключение) и долго поднимался, подключения к локациям не всегда корректно закрывались (опять Python, чтобы я ещё хоть раз решил написать на нём реверс прокси), клиенты писали тикеты. Сервис требовал срочных доработок. Но каких? Что нам поможет решить все эти проблемы?

Решение пришло из соседней команды, VMmanager. Ребята столкнулись с похожими проблемами и опыта работы с websockify у них было значительно больше. И вот она — серебряная пуля. Golang. Чтоооо??? В смысле, Go? Откуда он вообще взялся в компании? С этой идеей и языком было сложно свыкнуться. Поначалу мы его отвергали. Но теперь это в прошлом, три прокси на Golang спустя я понял — есть у этого языка своя ниша. И, конечно, в комментариях я увижу, что Go — классный язык, у него есть куча применений и вобще плюсы давно мертвы, а я ламер. Не буду спорить =). Но для меня Golang встал на первое место для написания различных проксей, он практически идеален для этого. Вернёмся к решению. В первую очередь мы сменили питоновый websockify на go-websockify. И сразу жить cтало веселее, меньше памяти расходуется на подключения. Дальше мы выбросили питоновую прокси и написали свою на Go, переделали механизм подключения, начали использовать SSH-туннель и теперь напрямую подключаемся к контейнеру-серверу на локации. Лишние порты на локации мы закрыли — всё по канону. И вновь сервис работает уже год без изменений и тикеты по нему писать перестали. Конечно в нём есть ещё проблемы — достаточно переподключиться в определённый промежуток времени, длиною примерно в пару секунд и можно словить ошибку. Но это время ещё нужно подгадать. Если хочется что-нибудь сломать, то всегда найдётся способ =).

А что же дальше?

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

Интерфейс DCImanager 6: можно управлять всеми локациями и видеть оборудование в них
Интерфейс DCImanager 6: можно управлять всеми локациями и видеть оборудование в них

Благодарю за то, что дочитали эту статью до конца и буду рад ответить на комментарии =)

Источник: https://habr.com/ru/company/ispsystem/blog/595907/


Интересные статьи

Интересные статьи

Flutter из нишевой технологии стал мощным игроком, который теснит не только React Native, но и нативную разработку. Быть Flutter-разработчиком — это востребовано. А если ...
Привет! Меня зовут Анастасия, психолог и много консультирую IT-специалистов. И я вижу большое сходство в работе тимлида и психолога. В чём оно? Работа с людьми, безусловно. А если конкрет...
SWAP (своп) — это механизм виртуальной памяти, при котором часть данных из оперативной памяти (ОЗУ) перемещается на хранение на HDD (жёсткий диск), SSD (твёрдотельный накоп...
Собственный софт-процессор на ПЛИС с компилятором языка высокого уровня или Песнь о МышЕ — опыт адаптации компилятора языка высокого уровня к стековому процессорному ядру. Распространенной про...
На третьей встрече из серии Backend United мы с коллегами из Booking, Dodo Pizza и Авито обменялись опытом работы с микросервисной архитектурой. Говорили о распилах, монолитах и всём, что за этим...