За микросервисы и улицу — как мы внедряли новую инфраструктуру и почему она классная
С чего все начиналось
Когда мы запускали Клауд три года назад, у нас уже был продукт — VDS. Его инфраструктура была написана на Python, а бизнес-логика — на Erlang. Здесь же содержались виртуальный хостинг и панель веб-мастеров — разработка другой команды.
Первоначально стояла задача развивать Клауд как продукт со своей отдельной разработкой, для этого нужно было собрать команду. Erlang — редкий и специфичный язык, на рынке довольно мало специалистов, которые им владеют, к тому же они очень дорогие. По этой причине проще и логичнее было набрать команду для работы на более популярном стеке.
Нам хотелось собрать команду из fullstack-разработчиков, потому что у продукта достаточно сложная предметная область: нужно было понимать, как работает продукт на разных уровнях. Так, им было бы проще запускать новые фичи. Для того, чтобы реализовать задумку, сначала нужно было понять, как развивать и запускать новые сервисы и что делать со старыми.
Кроме желания оптимизировать команду, у нас была необходимость улучшить технические характеристики:
- Во-первых, в старой версии сервиса мы не могли увеличить время ответов API, а это напрямую влияло на скорость работы панели управления и на восприятие сервиса в целом.
- Во-вторых, из-за того, что Erlang непопулярный язык, на него было мало готовых решений и нам пришлось бы писать большую часть шаблонных решений самостоятельно, а не пользоваться тем, что уже использует комьюнити.
Выбор языка и подхода
Изменения мы решили начать с выбора нового языка: остановились на TypeScript и фреймворке Nestjs по двум причинам:
- У команды уже был опыт работы с этим языком.
- У нас было понимание, как искать ребят на рынке.
Далее нам нужно было выбрать стратегию развития продукта. Технически мы рассматривали два разных подхода к созданию Клауда на базе VDS:
Первый подход — монолитное приложение. Такую инфраструктуру несложно разрабатывать — она реализует все необходимые функции и работать так особенно удобно в момент, когда проект только запускается: команда пока небольшая и она не тратит время на дополнительные настройки развертывания, интерфейсов и взаимодействий между сервисами. Буквально всё находится в одном проекте, вы вносите обновления и дополнения в монолит — и это работает.
Второй подход — микросервисы. В этом варианте под каждую бизнес-сущность запускается отдельный сервис, который инкапсулирует в себя всю логику работы с конкретной функцией. Так, например, в Клауде для кластеров Kubernetes и балансировщика баз данных написаны свои микросервисы: каждый сервис инкапсулирует маленький кусочек продукта.
В разных компаниях могут быть разные принципы деления: где-то любят распределять ресурсы почти по атомарным сервисам, где-то наоборот, микросервисы достаточно большие, а внутри содержится много бизнес-логики.
Мы в Клауде делаем что-то среднее: инкапсулируем только то, что касается большого бизнес-блока.
Причины, по которым мы начали запускать микросервисы
- Мы понимали, что команда будет со временем увеличиваться и overhead на этапе внедрения микросервисов после завершения выльется в ускорение. За счет того, что все сервисы были отделены друг от друга, мы могли независимо доставлять и развивать их. Разные разработчики, когда реализуют какой-то функционал в разных частях систем, друг с другом не пересекаются. Такой подход стоит больше на этапе внедрения, но дешевле потом, когда ты с этим работаешь и есть хорошая компетентная команда.
- Нам не хотелось переписывать то, что было раньше: решили рядом создавать новый продукт для того, чтобы сэкономить ресурсы. Так, старые элементы кода мы заменяли новыми написанными частями системы и переключали, соответственно, работу панели и API на новые сервисы. Этот процесс продолжается до сих пор и будет какое-то время продолжаться: работы нам хватит еще на полгода точно.
Микросервисы намного сложнее, чем монолитные приложения с точки зрения их доставки. Здесь требуется квалифицированная команда DevOps, которая сможет правильно настроить все этапы доставки изменений.
Запуск первого микросервиса
Мы решили запустить первый сервис в тестовом режиме и посмотреть, насколько это удобно и какие проблемы решает. Одним из таких сервисов были наши стандартные облачные серверы. В системе, еще до обновления, запросы для получения списка серверов выполнялись достаточно долго, особенно это касалось крупных аккаунтов — запрос там висел по несколько секунд. Была задача это оптимизировать.
В результате внедрения даже первого микросервиса мы получили ускорение в десятки раз: для крупных аккаунтов, в которых, допустим, несколько сотен VDS, запрос раньше грузился 7-10 секунд. После внедрения нового сервиса, нам удалось сократить время загрузки до нескольких десятков миллисекунд.
Такой подход решил задачу бизнеса, когда все работает быстро и слаженно. Одна часть запросов теперь уходила в микросервисы, другая — в монолитное приложение. У нас тогда еще не было API Gateway: еще одну авторизацию для нового сервиса мы реализовывали просто рядом.
Далее команда занялась полноценным проектированием. Описали, как будет выглядеть продукт через год или полтора. В результате получили полноценную карту развития микросервисов, по которой и стали двигаться. Все новые сервисы мы сразу запускали на ноде, использовали тот же подход, что и вначале: понемногу выносили старые и запускали рядом новые.
Первые трудности
Кроме запуска микросервисов нам требовалось наладить сразу несколько взаимодействий: между самими сервисами и между сервисами и панелью управления. Для этого нужно было приложить много усилий, в итоге у нас появилась схема:
Для всей автоматизации мы используем GitLab CI/CD: отдельная команда девопс занималась и занимается тем, что помогает нам настраивать автоматизацию и доставлять изменения в сервисах на любое из окружений. Все работает в пару кликов из интерфейса GitLab и настроено очень удобно.
Нам требуется проверять, что клиент действительно имеет право выполнять тот или иной запрос: нужен механизм авторизации и аутентификации, при том, что сервис не один. Система должна понимать, в какой из микросервисов следует отправить клиентский запрос — этим занимается API Gateway. Снаружи система для клиентов выглядит так, будто это единый рабочий интерфейс, внутри же он идет в определенный микросервис и там получает нужные данные.
Что нам дало внедрение микросервисов
Мы постоянно собираем фидбэк с наших клиентов: видим, насколько сильно он улучшился. Но было сложно объективно оценивать пользовательскую реакцию в контексте только микросервисов: мы делали сразу много обновлений, в том числе, новый дизайн панели.
Тем не менее, клиенты отмечают, что продукт стал работать стабильнее — это положительно повлияло на уровень удовлетворенности.
К тому же внедрение микросервисов позволило сократить тайм-ту-маркет: доставка обновлений стала дешевле и почти в четыре раза быстрее.
Вообще все наши ожидания оправдались. Мы ставили перед собой несколько целей:
- Технически ускорить работу сервисов.
- Обеспечить высокую скорость релизов — это было требованием бизнеса.
Мы должны оперативно вносить любые изменения: нам стало намного проще разрабатывать и оптимизировать методы, писать их так, чтобы решения работали без перебоев.
Конечно, были технические сложности: нам пришлось настраивать сложную систему авторизации для того, чтобы совместить старое монолитное приложение с новыми микросервисами, но реальных бэдкейсов не было.
Сейчас мы пришли к схеме, где старый монолит — просто один из микросервисов в новой системе: мы особо не отличаем его от любых других наших сервисов.
Как поменялось отношение команды к работе
Мы смогли получить контроль за продуктом, потому что первый монолит поддерживался другой командой. Новые сервисы мы пишем сами и любые изменения вносим самостоятельно. Поскольку релизить обновления теперь можно оперативнее, к нам чаще приходят новые классные задачки — в принципе работать стало интереснее.
Мы не засиживаемся с одной задачей надолго, быстро делаем и запускаем обновления, дальше — идем к новому. Это драйвит команду: ей самой интереснее развиваться.