Как стать автором
Обновить
0
True Engineering
Лаборатория технологических инноваций

Как перейти на микросервисы и не разломать production

Время на прочтение 9 мин
Количество просмотров 16K
Сегодня расскажем, как переводили на микросервисы монолитное решение. Через наше приложение круглосуточно проходит от 20 до 120 тысяч транзакций в сутки. Пользователи работают в 12 часовых поясах. В то же время функционал добавлялся много и часто, что довольно сложно делать на монолите. Вот почему системе требовались устойчивая работа в режиме 24/7, то есть HighLoad, High Availability и Fault Tolerance.

Мы развиваем этот продукт по модели MVP. Архитектура менялась в несколько этапов вслед за требованиями бизнеса. Первоначально не было возможности сделать всё и сразу, потому что никто не знал, как должно выглядеть решение. Мы двигались по модели Agile, итерациями добавляя и расширяя функциональность.



Первоначально архитектура выглядела следующим образом: у нас был MySql c единственным war, Tomcat и Nginx для проксирования запросов от пользователей.



Окружения (& минимальный CI/CD):

  • Dev — деплой по Push в develop,
  • QA — раз в сутки с develop,
  • Prod — по кнопке с master,
  • Запуск интеграционных тестов вручную,
  • Всё работает на Jenkins.

Разработка была построена на основе пользовательских сценариев. Уже на старте проекта большая часть сценариев укладывалась в некий workflow. Но все же не все, и это обстоятельство усложняло нам разработку и не позволяло провести «глубокое проектирование».



В 2015 году наше приложение увидело продакшн. Промышленная эксплуатация показала, что нам не хватает гибкости в работе приложения, его разработке и в отправке изменений на prod-сервера. Мы хотели добиться High Availability (HA), Continuous Delivery (CD) и Continuous Integration (CI).

Вот проблемы, которые необходимо было решить для того, чтобы прийти к HI, CD, CI:

  • простой при выкатывании новых версий — cлишком долго происходил deploy приложения,
  • проблема с меняющимися требованиями к продукту и новые user cases — слишком много времени уходило на тестирование и проверки даже при небольших фиксах,
  • проблема с восстановлением сессий у Tomcat: session management для системы бронирования и сторонних сервисов, при перезапуске приложения сессия не восстанавливалась силами Tomcat,
  • проблемы с высвобождением ресурсов: приходилось рано или поздно перезагружать Tomcat, происходили memory leak.

Мы стали решать все эти проблемы поочередно. И первое, за что взялись — это меняющиеся требования к продукту.

Первый микросервис


Вызов: Изменяющиеся требования к продукту и новые use cases.
Технологический ответ: Появился первый микросервис — вынесли часть бизнес-логики в отдельный war-файл и положили в Tomcat.



К нам пришла очередная задача вида: до конца недели обновить бизнес-логику в сервисе и мы приняли решение вынести эту часть в отдельный war-файл и положить в тот же Tomcat. Мы использовали Spring Boot для скорости конфигурирования и разработки.

Мы сделали небольшую бизнес-функцию, которая решала проблему с периодически изменяющимися параметрами пользователей. В случае изменения бизнес-логики нам бы не пришлось перезапускать весь Tomcat, терять наших пользователей на полчаса и перезагрузить только небольшую его часть.

После удачного вынесения логики по такому же принципу мы продолжили вносить изменения в приложение. И с того момента, когда к нам приходили задачи, которые кардинально меняли что-то внутри системы, мы выносили эти части отдельно. Таким образом, у нас постоянно копились новые микросервисы.
Основной подход, по которому мы начали выделять микросервисы — это выделение бизнес-функции или бизнес-услуга целиком.
Так у нас быстро отделились сервисы, интегрированные со сторонними системами, такими как 1C.

Первая проблема — типизация


Вызов: Микросервисов уже 15. Проблема типизации.
Технический ответ: Spring Cloud Feign.

Проблемы не решились сами собой лишь потому, что мы начали разрезать наши решения на микросервисы. Более того, стали возникать новые проблемы:

  • проблема типизации и версионирования в Dto между модулями,
  • как задеплоить не один war-файл в Tomcat, а множество.

Новые проблемы увеличили время перезапуска всего Tomcat при технических работах. Получается, мы усложнили себе работу.

Проблема с типизацией, конечно, возникла не сама собой. Скорее всего, в течение нескольких релизов мы её просто игнорировали, потому что находили эти ошибки ещё на этапах тестирования или во время разработки и успевали что-то предпринять. Но когда несколько ошибок были обнаружены уже совсем на полпути в продакшн и потребовали срочного исправления, мы ввели регламенты или начали использовать инструменты, которые решают эту проблему. Мы обратили внимание на Spring Cloud Feign — это клиентская библиотека для http-запросов.
github.com/OpenFeign/feign
cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html
Мы выбрали его, поскольку

— мало накладных расходов на внедрение в проект,
— сам генерировал клиента,
— можно использовать один интерфейс и на сервере, и на клиенте.

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

Простои. Бой первый. Работоспособность


Вызов бизнеса: 18 микросервисов, теперь простои в работе системы недопустимы.
Технический ответ: изменение архитектуры, увеличение серверов.

У нас осталась проблема с простоем в работе и выкатыванием новых версий, осталась проблема с восстановлением сессии Tomcat и с освобождением ресурсов. Количество микросервисов же продолжало расти.

Процесс деплоя всех микросервисов занимал около часа. Периодически приходилось перезагружать приложение из-за проблемы с высвобождением ресурсов у tomcat. Не было простых способов делать это более оперативно.

Мы стали продумывать, как изменить архитектуру. Вместе с отделом инфраструктурных решений мы построили новое решение на основе того, что у нас уже было.



Архитектура изменила свой вид следующим образом:

  • горизонтально разделили наше приложение на несколько data-центров,
  • добавили Filebeat на каждый сервер,
  • добавили отдельный сервер для ELK, поскольку росло количество транзакций и логов,
  • несколько серверов haproxy + Tomcat + Nginx + MySQL (так мы обеспечивали High Availability).

Используемые технологии были такими:

  • Haproxy занимается роутингом и балансировкой между серверами,
  • Nginx отвечает за раздачу статики, tomcat был сервером приложений,
  • Особенностью решения стало то, что MySQL на каждом из серверов не знает о существовании своих других MySQL’ей,
  • Из-за проблемы latency между датацентрами репликация на уровне MySQL была невозможна. Поэтому мы решили реализовать шардинг на уровне микросервисов.

Соответственно, когда приходил запрос от пользователя до сервисов в Tomcat, они просто запрашивали данные у MySQL. Те данные, которые требовали целостности, собирались со всех серверов и склеивались (все запросы были через API).

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

  • Даже если один из серверов падал, у нас оставалось ещё 3-4, которые, поддерживали работоспособность всей системы.
  • Мы хранили бекапы не на серверах в том же дата-центре, в котором они делались, а в соседних. Это помогало нам с disaster recovery.
  • Fault tolerance решалась также за счет нескольких серверов.

Так были решены крупные проблемы. Ушёл простой в работе пользователей. Теперь они не чувствовали, когда мы накатывали обновление.

Простои. Бой второй. Полноценность


Вызов бизнеса: 23 микросервиса. Проблемы с консистентностью данных.
Техническое решение: запуск сервисов отдельно друг от друга. Улучшение мониторинга. Zuul и Eureka. Упростили разработку отдельных сервисов и их поставку.

Проблемы продолжали появляться. Так выглядел наш редеплой:

  • У нас не было консистентности данных при редиплое, поэтому часть функционала (не самого важного) отходила на второй план. К примеру, при накатывании нового приложения статистика работала неполноценно.
  • Нам приходилось выгонять пользователей с одного сервера на другой, чтобы перезапустить приложение. Это тоже занимало порядка 15-20 минут. Вдобавок ко всему, пользователям приходилось перелогиниваться при переходе с сервера на сервер.
  • Также мы всё чаще перезапускали Tomcat из-за роста количества сервисов. И теперь приходилось следить за большим количеством новых микросервисов.
  • Время редиплоя выросло пропорционально количеству сервисов и серверов.

Подумав, мы решили, что нашу проблему решит запуск сервисов отдельно друг от друга — если мы будем запускать сервисы не в одном Tomcat, а каждый в своём на одном сервере.



Но появились другие вопросы: как сервисам теперь общаться между собой, какие порты должны быть открыты наружу?

Мы выбрали ряд портов и раздали их нашим модулям. Чтобы не было необходимости держать всю эту информацию о портах где-то в pom-файле или общей конфигурации, мы выбрали для решения этих задач Zuul и Eureka.
Eureka — service discovery
Zuul — proxy (для сохранения контекстных урлов, что были в Tomcat)
Eureka также улучшила наши показатели в High Availability /Fault Tolerance, поскольку теперь стало возможным общение между сервисами. Мы настроили так, что если в текущем дата-центре нет нужного сервиса, идти в другой.

Для улучшения мониторинга добавили из имеющегося стека Spring Boot Admin для понимания того, что на каком сервисе происходит.

Также мы начали переводить наши выделенные сервисы к stateless-архитектуре, чтобы избавиться от проблем деплоя нескольких одинаковых сервисов на одном сервере. Это дало нам горизонтальное масштабирование в рамках одного data-центра. Внутри одного сервера мы запускали разные версии одного приложения при обновлении, чтобы даже на нём не было никакого простоя.

Получилось, что мы приблизились к Continuous Delivery / Continuous Integration тем, что упростили разработку отдельных сервисов и их поставку. Теперь не нужно было опасаться, что поставка одного сервиса вызовет утечку ресурсов и придётся перезапускать весь сервис целиком.

Простой при выкатывании новых версий всё ещё остался, но не целиком. Когда мы обновляли поочерёдно несколько jar на сервере, это происходило быстро. И на сервере не возникало никаких проблем при обновлении большого количества модулей. Но перезапуск всех 25 микросервисов во время обновления занимал очень много времени. Хоть и быстрее, чем внутри Tomcat, который делает это последовательно.

Проблему с освобождением ресурсов мы решили также тем, что запускали всё с jar, а утечками или проблемами занимался системный Out of memory killer.

Бой третий, управление информацией


Вызов бизнеса: 28 микросервисов. Очень много информации, которой нужно управлять.
Техническое решение: Hazelcast.

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



Мы, наконец, избавились от проблемы с consistency наших данных. Теперь мы могли сохранять любые данные во все базы одновременно, не делая никаких лишних действий. Мы сказали Hazelcast, в какие базы данных он должен сохранять приходящую информацию. Он делал это на каждом сервере, что упростило нашу работу и позволило избавиться от шардинга. И тем самым мы перешли к репликации на уровне приложения.

Также теперь мы стали хранить сессию в Hazelcast и использовали его для авторизации. Это позволило переливать пользователей между серверами незаметно для них.

От микросервисов к CI/CD


Вызов бизнеса: нужно ускорить выход обновлений в продакшн.
Техническое решение: конвейер развертывания нашего приложения, GitFlow для работы с кодом.

Вместе с количеством микросервисов развивалась и внутренняя инфраструктура. Мы хотели ускорить поставку наших сервисов до продакшна. Для этого мы внедрили новый конвейер развертывания нашего приложения и перешли к GitFlow для работы с кодом. СI начал собирать и прогонять тесты по каждому комиту, прогонять unit-тесты, интеграционные, складывать артефакты с поставкой приложения.



Чтобы делать это быстро и динамически, мы развернули несколько GitLab-раннеров, которые запускали все эти задачи по пушу разработчиков. Благодаря подходу GitLab Flow, у нас появилось несколько серверов: Develop, QA, Release-candidate и Production.

Разработка происходит следующим образом. Разработчик добавляет новый функционал в отдельной ветке (feature branch). После того, как разработчик закончил, он создает запрос на слияние его ветки с магистральной веткой разработки (Merge Request to Develop branch). Запрос на слияние смотрят другие разработчики и принимают его или не принимают, после чего происходит исправление замечаний. После слияния в магистральную ветку разворачивается специальное окружение, на котором выполняются тесты на поднятие окружения.

Когда все эти этапы закончены, QA инженер забирает изменения к себе в ветку “QA” и проводит тестирование по ранее написанным тест-кейсам на фичу и исследовательское тестирование.

Если QA инженер одобряет проделанную работу, тогда изменения переходят в ветку Release-Candidate и разворачиваются на окружении, которое доступно для внешних пользователей. На этом окружении заказчик производит приемку и сверку наших технологий. Затем мы перегоняем всё это в Production.

Если на каком-то этапе находятся баги, то именно в этих ветках мы решаем эти проблемы и их мёрджим в Develop. Также сделали небольшой плагин, чтобы Redmine мог сообщать нам, на каком этапе находится фича.



Это помогает тестировщикам смотреть, на каком этапе нужно подключаться к задаче, а разработчикам — править баги, потому что они видят, на каком этапе произошла ошибка, могут пойти в определенную ветку и воспроизвести её там.

Дальнейшее развитие


Вызов бизнеса: переключение между серверами без простоя.
Техническое решение: Упаковка в Kubernetes.



Сейчас по окончанию деплоймента технические специалисты докладывают jer-ки на PROD-сервера и перезапускают их. Это не очень удобно. Мы хотим автоматизировать работу системы и дальше, внедрив Kubernetes и связав его с data-центром, обновляя их и разом накатывая.

Чтобы перейти к этой модели, нам необходимо закончить следующие работы.

  • Привести наши текущие решения к stateless-архитектуре, чтобы пользователь мог отправлять запросы на все сервера без разбора. Некоторые из наших сервисов ещё поддерживают какие-то сессионные данные. Эта работа касается и репликации данных базы данных.
  • Также мы должны распилить последний маленький монолит, который содержит в себе несколько бизнес-процессов. Это и приведёт нас к последнему главному шагу — Continuous Delivery.

P.S. Что изменилось с переходом на микросервисы


  • Мы избавились от проблемы меняющихся требований.
  • Избавились от проблемы восстановления сессий у Tomcat тем, что перенесли их в Hazelcast.
  • При перебрасывании пользователей с одного сервера на другой им не приходится перелогиниваться.
  • Решили все проблемы с высвобождением ресурсов, переложив их на плечи операционной системы.
  • Проблемы типизации и версионирования решились благодаря Feign.
  • Уверенно движемся в сторону Continuous Delivery c помощью Gitlab Pipelines.
Теги:
Хабы:
+10
Комментарии 9
Комментарии Комментарии 9

Публикации

Информация

Сайт
www.trueengineering.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия

Истории