Привет! Я тимлид команды «Добро» в «Сравни.ру», мы занимаемся разработкой сервиса по подбору кредитных продуктов.
Сервис, над которым мы работаем, помогает нашим клиентам подобрать кредитные продукты с высокой степенью одобрения. Для этого мы придумали алгоритм, который аккумулирует необходимый объем данных, обрабатывает их и подбирает кредитные продукты от банков, которые с высокой долей вероятности одобрят заявку конкретного пользователя. Нетипичное название команды («Добро») произошло от слова «одобрение».
Сейчас все наши решения работают на базе микросервисной архитектуры, но так было не всегда. Сегодня я расскажу о том, как мы переезжали с монолита на микросервисы, какие при этом возникали сложности и как мы их решали.
С чего всё начиналось
На момент принятия решения о переезде проект «Сравни.ру» существовал уже около семи лет. Сайт был написан на .NET и представлял собой один большой монолит. Как можно догадаться, чтобы выкатить очередное обновление, приходилось немного пошаманить. У нас было несколько админок, в которых нужно было время от времени перезагружать данные. Все данные собирались из одной базы, выполнялся экспорт, и они обновлялись пачкой. Это занимало кучу времени. Вместо этого мы хотели получить реал-таймовые админки (когда поправил раздел — и он моментально обновился на сайте).
Еще одной проблемой было то, что проект получился большим, долго компилировался, на это могло уходить по несколько часов. У нас был релизный цикл. Например, через две недели мы выпускаем новую версию сайта — и туда войдут такие-то фичи по таким-то разделам.
Мы же хотели перейти к другой схеме: разбить всех на продуктовые команды, каждая из которых отвечает за свой раздел, отдельно релизит и может быстро проводить итерации. Даже если они выкатят код с багом, должна быть возможность это быстро исправить или откатить на предыдущую версию. И это никак не должно затрагивать другие разделы сайта, потому что процесс отката или фикса мог занимать несколько часов, а хотелось, чтобы это занимало максимум минуту.
В какой-то момент нам надоело тратить кучу времени, и мы захотели перейти к более гибкой разработке и коротким итерациям, быстрее релизить разные фичи, проводить A/B-тесты и так далее. К тому же сайт морально устарел с точки зрения пользовательского опыта — и много что нужно было переделывать. Тогда-то мы и решили, что раз уж мы всё переделываем, то надо распиливать наш монолит на отдельные сервисы и переходить на более гибкий стек, который позволяет делать итерации гораздо быстрее.
Из технологий выбрали Node.js для серверной части, React — для фронтенда и MongoDB — в качестве базы данных, а для хостинга — Azure и Яндекс.Облако. Перевести хотели в облако, чтобы всё крутилось в Kubernetes. Это открывало дополнительную универсальную схему: так и команды, и DevOps-инженеры могли легко это всё поддерживать.
Как мы переезжали
Проект начали делить по разделам. Логически хотелось вынести и фронтенд, и бэкенд в ведение команд, чтобы у них была полная свобода действий: как и что релизить, что использовать и так далее. Но очень скоро эти разделы стали разрастаться и угрожали превратиться в новые монолиты.
Некоторые команды могли отвечать за несколько разделов. В рамках одного репозитория они начали работать над разделами, за которые отвечают. Стало понятно, что такая схема не будет работать, так как сегодня одна команда отвечает за один раздел, а завтра это может быть передано другой команде. Мы быстро вмешались и начали четко делить всё на небольшие логические сервисы, которые обычно шли в группах. В итоге пришли к схеме, когда есть фронтенд, бэкенд и админка в виде отдельных репозиториев.
Начали использовать MongoDB в качестве системы управления базами данных, но возникли проблемы. В части сервисов нужно было производить сложные выборки и вычисления. По философии и некоторым технических ограничениям MongoDB нам не очень подходила, так как приходилось делать «хитрые» агрегации на большом объеме данных. Например, считать процентные ставки. Это всё работало медленно, и мы поняли, что инструмент не подходит для таких задач. Сейчас MongoDB мы также используем, но PostgreSQL лучше подходит под наши задачи с точки зрения логики. Чтобы не грузить базу, часть расчетов мы вынесли либо на клиентскую, либо на серверную часть.
Что касается деплоя, то, как я сказал раньше, мы сделали несколько репозиториев под разные проекты. Например, репозитории под наш сервис по подбору кредитов выглядели так: credit_selection_frontend, credit_selection_service, credit_selection_backoffice.
Реализовали всё это через TeamCity. Заходя в TeamCity, любой разработчик или тестировщик может выкатить один сервис или сразу несколько. Выкатывается всё либо в тестовый, либо в продакшен Kubernetes. Это дает возможность изолированно тестировать функционал сразу в нескольких микросервисах. Добавляем в несколько микросервисов и смотрим, как это вместе работает и как будет выглядеть для пользователя. Как минимум это посмотрят тестировщики, а в некоторых случаях — продакт-оунеры. Грубо говоря, тестовые стенды создаются двумя кликами мыши через TeamCity.
В конце концов мы перенесли всё в Kubernetes, это позволило легко создавать и деплоить новые сервисы. Сейчас у нас есть несколько нод Kubernetes для разных задач.
Проблемы и их решение
При переезде на микросервисную архитектуру мы поняли, что нельзя целиком переписывать код, так как это огромный проект, у которого могут быть свои баги и недоработки. Поэтому пришлось выкатывать всё по частям и переписывать код по разделам.
В процессе переезда запросы шли в монолит. В монолите был код, который эти запросы роутил либо в свои обработчики, либо на микросервисы, если запрашиваемый раздел был уже на новой архитектуре. Проблема была в том, что, например, когда мы выкатывали новый микросервис, могла произойти ошибка в таблице маршрутизации урлов. Это приводило к тому, что мы выкатывали сервис, а он не работал.
Из-за того что проксирование шло через монолит, это возвращало нас к необходимости снова пересобирать проект несколько часов. Пользователи не могли нормально использовать сайт в течение этого времени, что для нас было критично.
В итоге решили разруливать запросы с помощью nginX, который служил единой точкой входа, а затем перекидывал запросы на монолит и микросервисы. Если раньше на этот процесс уходило от одного до нескольких часов, то теперь он стал занимать всего несколько минут. Такое решение помогло переезжать в более спокойном режиме.
Немного про тестирование и деплой
В Kubernetes у нас есть стейджинг, куда мы деплоим наши задачи для тестирования, причем все эти задачи создаются в отдельной ветке. Тестировщики заходят в тестовый стенд, разворачивают сервисы и проверяют, как они работают.
Разворачивание происходит по нажатию на одну кнопку в соответствующей ветке. По завершении тестирования мы мерджим ветку в мастер, и после этого она автоматом выкатывается на основной тестовый стенд, чтобы продакты и вообще все желающие могли посмотреть на новую фичу перед релизом.
По нажатию еще одной кнопки происходит мердж в мастер и выкатка на продакшен. Мы специально не делали этот процесс полностью автоматизированным, чтобы можно было в ручном режиме подтвердить деплой в нужное время.
Для непрерывной интеграции и деплоя мы долгое время использовали TeamCity, сейчас потихоньку переезжаем на GitHub Actions.
Что в итоге?
В результате переезда на микросервисы мы ушли от крайне неприятной ситуации, когда одна команда что-то меняет в коде, а у другой в это время что-то отваливается. Теперь все сервисы изолированы, что позволяет каждой команде быстрее выкатывать (и, что немаловажно, откатывать) фичи оперативно, и это никак не влияет на работоспособность системы. В итоге наша текущая инфраструктура достаточно стабильна.
Сейчас внедряем ML и тоже деплоим в Kubernetes. Но об этом расскажем в следующий раз. Буду рад ответить на комментарии и вопросы!