Для одних микросервисы — это возможность переделать и отрефакторить приложение под условно современный стиль. Другим это архитектурное решение не подходит из-за особенности взаимодействия различных частей приложения. В любом случае, выбирая архитектуру, полезно изучить чужой опыт перехода от монолита к набору сервисов.
Мы попросили поделиться своим кейсом разработки и доставки микросервисов Алексея Баитова, ведущего инженера 2ГИС. Поговорим на тему архитектурных решений, деплоя и возможности масштабирования. Расспросим про трендовые и просто удобные инструменты для работы.
— Алексей, расскажи, пожалуйста, немного о себе и о твоей команде в 2ГИС. Над чем сейчас работаете?
В IT я пришел в далеком 2003 году как системный администратор, в разработку погрузился в 2011 году. За это время поработал на PHP, JavaScript, реализовал серию RESTful сервисов и Python-драйвер для Git. В 2ГИС работаю с 2015 года.
Поучаствовал в разработке двух микросервисных архитектур. Первая состояла из одного сервиса. Это был асинхронный реверсивный прокси с кэшем. По сути он занимался досылкой сообщений. Проработкой требований, разработкой и выстраиванием DevOps занимался я один, но мне помогали эксперты из нашей компании 2ГИС.
Сервис был написан на Go. Быстрая компиляция позволила не ждать, и я смог сконцентрироваться и на Continuous Deployment. Тогда мы только начинали использовать GitLab CI, Prometheus, Grafana и Deis (open source аналог Heroku). У нас есть команда Infrastructure and Operations, которая как раз к моменту моей разработки вывела все эти инфраструктурные решения на production ready уровень. Я решил попробовать все это и успешно реализовал независимый микросервис.
Два года назад я перешел в другую команду на новый проект, где стал заниматься функциональным программированием на Scala. Наша команда с нуля разработала микросервисную архитектуру для хранилища рекламных материалов 2ГИС на Scala, C# и JavaScript. В основу всех сервисов я заложил инструментарий и полученный опыт для выстраивания практик DevOps (Continuous Deployment и сопровождение). Архитектура прошла путь от прототипа до промышленной эксплуатации. Она поглотила два монолита, теперь состоит из 15 сервисов и постоянно расширяется.
— Ты согласен, что микросервисы по сути являются набором независимо развернутых сервисов, имеющих общие характеристики, то есть именно набор определенных черт придает им вид микросервиса? Данное определение нужно расширять? Или по факту компании по-разному понимают микросервисную архитектуру?
Мне нравится следующее определение. Микросервисная архитектура — это архитектурный стиль, который структурирует приложение как коллекцию слабо связанных сервисов, которые реализуют определенную бизнес-логику. Сервисы в микросервисной архитектуре могут не обладать общими характеристиками, но объединяются в рамках общей бизнес-логики.
Считается правильным разрабатывать сервис таким образом, чтобы вся его предметная область могла уместиться в голове одного разработчика. При этом в разработке этого сервиса могут участвовать несколько человек. Это поможет избежать bus-фактора, когда разработчик ушел в отпуск или заболел. Корректное разбиение на сервисы позволяет новому человеку быстро войти в контекст.
«Микросервисная архитектура» говорит нам о том, что в ее состав часто входят несколько сервисов. Таким образом, одним разработчиком уже не обойтись. Микросервисная архитектура отталкивается от продуктовой модели (или общей бизнес-логики). Разработчики подбираются так, чтобы получилось эту модель реализовать и при этом сфокусироваться на клиенте.
Фокусировка на клиенте организуется за счет прямого контакта разработчика с клиентом. Разработчикам надо видеть, как используется их продукт. Отсюда уже вытекают пожелания к знаниям в технологической области, умению как можно быстрее доставить продукт клиенту и сопровождать продукт.
Про численность команды однозначно сказать сложно. Всем уже, наверно, известно высказывание Джефа Безоса, основателя Amazon, что размер команды при сервис-ориентированной разработке должен быть достаточно мал, чтобы всех можно было накормить двумя пиццами. В комментариях на Хабре была дискуссия на эту тему, и там писали, что одному человеку может быть мало одной пиццы и поэтому команда должна состоять из одного-двух человек. Мартин Фаулер же, цитируя высказывание про две пиццы, сказал, что речь идет про большие американские пиццы, после чего уточнил, что в команде должно быть не 50, а в районе 12 человек. Я считаю, что все зависит от продуктовой модели. Но уточнение Фаулера про «не более 12 человек» на моей практике пока выигрывало. Я заметил, что внутри команды желательно разделиться по технологическим интересам, найти единомышленников.
Необязательно, чтобы все в команде хорошо знали все используемые в работе технологические области, но суммарные знания команды должны быть равномерно глубокими. Например, два человека занимаются первоначальным выстраиванием деплоя и в будущем, скорее всего, они также значительно его усовершенствуют. Но при этом вся команда должна хорошо понимать процесс деплоя. Это позволит ей высказывать пожелания и вносить изменения. Почему два человека? Потому что порой один человек может впасть в творческий ступор. А в дискуссии рождается истина.
Мы естественным образом построились по такому принципу, объединились по технологическим интересам. При этом разработчик может также заниматься налаживанием DevOps-практик, а QA-инженер — разрабатывать вспомогательный, не-продакшн сервис (например, прогревалку кэша или сервис поиска аномалий в данных на разных окружениях).
— Почти каждый доклад о микросервисах начинается с рассказа о том, что «вот у нас был „айсберг“ и мы его пилили, пилили, пилили… Новые части приложения делали на основе микросервисов, а затем уже начали отделять „кусочки“ от основной махины…»
Скажи, ты сторонник разработки с нуля или могут быть ситуации, когда стоит делать постепенный вывод из монолита? Как верно определить стратегию «выхода»?
Я сторонник разработки с нуля. Но это работает только в случае не слишком сложного набора функций. Обычно делается небольшой MVP-монолит. И иногда приходится несколько раз в корне менять его внутреннюю реализацию. Это может быть вызвано как сменой технического задания, так и тем, что приходит понимание реализации — появляются высокоуровневые абстракции на уровне бизнес-модели. После этого можно переходить к микросервисной архитектуре.
Но если хорошо проработать эти абстракции в самом начале и прорисовать их в разных нотациях (UML, BPMN, IDEF), чтобы все участники процесса понимали то, с чем они работают, то вполне возможно реализовать микросервисную архитектуру сразу.
Наш путь к микросервисной архитектуре был не прямой. Сначала был монолит. Он обрабатывал текстовые рекламные материалы. Три с половиной года назад нам потребовалось работать с графическими рекламными материалами (изображениями, логотипами). Было желание внести в бизнес-логику то, чего не хватало при работе с текстовыми рекламными материалами.
Реализация второго монолита была сложно расширяемой на первый. Поэтому мы решили не вести разработку сразу в двух монолитах, а объединить их в рамках третьей архитектуры по той самой новой бизнес-модели. Была создана команда в составе семи разработчиков, одного QA-инженера и двух аналитиков. Два разработчика из этой команды ранее создавали и поддерживали первый монолит, а еще один — второй монолит. То есть наша команда уже на входе хорошо знала подводные камни предыдущих монолитов.
Первый монолит был написан на C#. Второй — на PHP. Нам не хотелось терять отлаженные объемные куски кода из первого монолита и при этом требовались многопоточность, безопасный код и строгая типизация. PHP-код под эти требования частично не попадал. Поэтому C# остался как основа и реализовал то, что он хорошо делал в рамках первого монолита — работу с контентом рекламных материалов — но уже на базе другого хранилища: S3-совместимого хранилища и Kafka.
Для работы с той самой новой бизнес-моделью на этот раз была выбрана Scala и база данных PostgreSQL. Scala удовлетворяла нашим техническим требованиям. К тому же, Scala-разработчики располагались на том же этаже, что и C# разработчики. Это сокращало время на межкомандные коммуникации. Сработал закон Конвея — структура компании продиктовала структуру приложения. PHP-разработчик перепрофилировался в Scala-разработчика. Я же как раз закончил работу над независимым микросервисом на Golang с полным CI/CD циклом, после чего пришел в команду и тоже стал Scala-разработчиком.
Интересно, что именно я предложил использовать для работы с бизнес-логикой Scala, а не C#. Дело в том, что у нас не было достаточного количества C#-разработчиков. PHP-шник и я хотели переквалифицироваться на Scala. Плюс у нас была возможность привлечь опытного Scala-разработчика. Еще момент: если бы мы реализовывали все на C#, то на выходе могли бы получить либо микросервисную архитектуру, либо очередной монолит. Разделение же на Scala и C#, различные по потребностям хранилища и наличие опытных разработчиков в каждой из требуемых областей - все это прямо указывало нам на микросервисную архитектуру. И мы от этого только выиграли. Год назад микросервисная архитектура по работе с графическими и текстовыми материалами вышла в промышленную эксплуатацию и успешно работает до сих пор.
К вопросу о том, можно ли создать микросервисную архитектуру с нуля. Полтора года назад в процессе работы над микросервисной архитектурой к нам пришло требование поддержать новое направление в наших продуктах — видеорекламные материалы. Необходимо было в короткие сроки опробовать новую модель продаж. Наша архитектура была в начальной стадии разработки. Работа с видеоматериалами охватывала новую область технологий. Вектор развития мы решили не менять и реализовали MVP по видеоматериалам как отдельно стоящую микросервисную архитектуру на C# и доверенном видеохостинге. Это успешный опыт, и у нас есть доклад на эту тему. Таким образом, у нас появилось две параллельно существующих микросервисных архитектуры. MVP сильно не развивался, по нему также накопились хотелки и в скором времени мы объединим все в рамках одной микросервисной архитектуры — получится единое хранилище рекламных текстовых, графических и видеоматериалов.
— Сходу можно назвать два важных фактора, которые говорят в пользу микросервисов. Первый — возможность вывода отдельных частей в облако и, как следствие, колоссальная масштабируемость. Второй — возможность создания отдельного сервиса на другом языке. Какие еще ты видишь плюсы перехода на микросервисы? Ну и о минусах, конечно, тоже хочется услышать.
Если говорить про технологическую составляющую, то к плюсам, помимо перечисленного, можно отнести возможность использования другого стека технологий. А если он нас не устроит, выберем еще один. Новые технологии обходят проблемы старых решений. Микросервисная архитектура также дает устойчивость и независимость: деградация одного сервиса не должна приводить к полной деградации всей системы. Компонуемость сервисов позволяет переиспользовать функционал сервиса в других микросервисных архитектурах. С точки зрения организационной составляющей разбиение на сервисы позволяет разделить разработку в пределах распределенных команд или одной большой команды.
Вообще в свободном доступе можно найти много информации и материалов о том, как с этим работать. Но по факту все зависит от задачи. На моей практике плюсы всегда оказывались весомее минусов.
Еще стоит вспомнить слова Сэма Ньюмена: чем меньше сервис, тем сильнее проявляются все преимущества и недостатки микросервисной архитектуры.
— У тебя есть несколько интересных докладов про микросервисы. Деплой микросервисов и Подход к Continuous Deployment в микросервисной архитектуре. На одном из слайдов первого есть «шаблоны» деплоя, а в докладе ты говоришь о том, что для вас трендовым подходом является распространение через контейнеры. За это время ничего не изменилось? Связка Docker + Kubernetes не утратила актуальность?
Мы переводим на эту связку все больше и больше сервисов. Я думаю, что мы выбрали правильное направление и пока планируем его придерживаться. Если что-то поменяется, я обязательно расскажу об этом в новом докладе или интервью.
— Насколько беспроблемным является непрерывный деплой и передача микросервисов на прод?
Все зависит от того, как выстроить процесс. Вначале кажется, что все просто. Сервисы независимы, деплоятся порознь. Слабая связанность обеспечивается за счет разных подходов к работе с эволюцией контракта. И тут уже приходится выбирать. Например, можно внедрить версионирование контракта или добавить сервис для разъединения контракта (contract decoupling).
Если в микросервисной архитектуре в активной разработке находятся сразу 10 или больше сервисов и у каждого свое версионирование, то возникает проблема согласованности версий. Надо стараться не запутаться в совместимости сервисов разных версий.
Непрерывный деплой подразумевает, что мы можем доставить приложение в любое время на любое окружение.
Непрерывный деплой подразумевает возможность в любое время откатиться на любую версию. Соответственно, может быть так, что потребуется откатить сразу несколько сервисов и нужно будет соблюсти правильный обратный порядок деплоя сервисов.
В одном из своих докладов я рассказываю о нашем подходе к эволюции контрактов, о том, как решили проблему согласованности версий и как выстроили процесс деплоя в микровервисной архитектуре из более чем десяти сервисов. Непрерывный деплой не может быть беспроблемным, но все решаемо.
— Какой может быть начальный набор инструментов для непрерывного деплоя микросервисов? Какие варианты ты бы посоветовал использовать для работы с микросервисами?
Непрерывный деплой — это последовательность этапов (pipelines), включающих непрерывную интеграцию, интеграционные тесты и доставку сервиса на среду оркестрации. Наиболее популярными инструментами для последовательного выполнения этапов являются Jenkins 2 Pipelines, TeamCity Build Chains, Bitbucket Pipelines и GitLab CI Pipelines. Вначале нужно автоматизировать непрерывную интеграцию (Continuous Integration, CI). Нужен удаленный CI сервер, который бы занимался сборкой и прогоном тестов на этой сборке.
Перечисленные инструменты предлагают свои решения. Мы используем GitLab CI, и в качестве таких серверов выступают GitLab Runner’ы. Артефактом сборки является Docker-образ. В рамках интеграционных тестов можно провести нагрузочные и емкостные тесты с помощью Gatling, в частности, чтобы определить требуемые ему ресурсы (процессор и память) для функционирования на среде оркестрации (например, Kubernetes). Для деплоя широко применяется Helm, он позволяет описать микросервисную архитектуру для разных окружений. В нашей компании мы не используем Helm. У нас — собственный инструмент деплоя, который был создан, когда Helm был в версии Classic и не поддерживал разные окружения. Оба эти инструмента обладают схожими полезными качествами, но реализация и дистрибуция различаются. А собственный инструмент позволяет вносить в деплой улучшения и адаптировать все под нашу инфраструктуру.
— Какие технологии оптимальны для небольших и средних компаний, которые хотят внедрить у себя микросервисы? Не слишком ли это дорогое удовольствие для них?
Все чаще встречаю подтверждения, что небольшим и средним компаниям нецелесообразно поднимать собственную среду оркестрации (например, Kubernetes, Docker Swarm или Apache Mesos). Ведь для этого им нужно содержать свое железо и одну или несколько команд для его настройки и поддержки. Проще использовать популярные сторонние облачные сервисы (на базе Google Cloud Platform, Amazon Web Services, Microsoft Azure или Oracle Cloud) и правильно выбрать тариф.
Мы используем бесплатную версию GitLab и GitLab CI. Этот инструмент позволяет хранить код и видеть всю информацию о его деплое в одном месте. GitLab активными темпами интегрируется с Helm и разными облачными сервисами. Я часто говорю Helm и не привожу аналогов. У Helm есть свои недостатки, но эти недостатки для всех разные и зависят от масштаба разработки, поэтому можно с него начать, а потом обязательно найдутся необходимые инструменты в дополнение.
Наш инструмент конфигурации и деплоя, о котором я говорил ранее, я бы тоже порекомендовал, но он пока недоступен как open source, хотя работы в этом направлении активно ведутся.
Интересно также взглянуть на Spinnaker и Heroku — это разные, но зарекомендовавшие себя решения, которые позволяют быстро задеплоиться на облачные сервисы.
— С одной стороны, для обеспечения контроля связей и надежности на уровне, достаточном для промышленной эксплуатации, нужна версионность. При этом при большом количестве сервисов соблюдение версионности может серьезно замедлить развитие инфраструктуры. Как вы решили эту проблему?
Разрабатывая микросервисную архитектуру из нескольких сервисов, мы прошли путь от прототипа до промышленной эксплуатации. С версионированием мы впервые столкнулись, когда нам потребовалось задеплоить единое тестовое окружение из всех сервисов, а потом в ходе изменения каждого сервиса поддерживать стабильность имеющегося функционала.
Мы не хотели плодить версии (а их на этапе прототипа порождается очень много) или внедрять разные сервисы для разъединения контракта. Микросервисная архитектура позволяла заменять один сервис другим с другой реализацией или на другом стеке технологий.
При добавлении или удалении сервисов версионирование не помогает. Мы предложили свой подход к эволюции контракта. Наиболее часто меняющиеся сервисы мы объединили в единый слепок версий, он же пакет (package). Далее реализовали простой пакетный менеджер и стали использовать этот слепок для деплоя коллекции часто меняющихся сервисов.
Версиями сервисов (не контрактов) являются тэги Docker-образов, которые в свою очередь равны хэш коммитам или тэгам в Git. Нам пришлось перестроить непрерывный деплой, но от этого стало только легче. Я подробно рассказывал об этом в докладе, а динамику деплоя можно посмотреть на слайдах. В общем, мы смогли на этапе прототипа отказаться от привычного версионирования контракта и значительно ускорили разработку.
Версионирование контракта мы добавили в сервисы за месяц до выхода в промышленную эксплуатацию, когда нам потребовалось интегрироваться с внешними клиентами. И то: мы добавили его только в публичный API. И вот мы уже год находимся в промышленной эксплуатации и за это время подняли всего три минорные версии. При этом мы продолжаем заниматься активной разработкой: добавляем и удаляем функции. У нас есть клиентские сервисы, которые используют наш публичный API. Мы знаем, какие методы API используют внешние клиенты, а какие — мы. Если изменения касаются только наших методов, то в большинстве случаев мы не поднимаем версию API, изменяем только слепок версий и не делаем необратимых изменений на коротких промежутках времени.
Таким образом мы смогли отказаться от множества версий контракта. Если бы мы их оставили, нам пришлось бы покрывать тестами каждую версию; просить внешних клиентов переходить на новую версию публичного API, хотя большинство изменений их не касаются; следить за тем, не используется ли старая версия, и удалять ее из кода.
Знаю, что вы используете подход, при котором часть микросервисов выделяете в отдельные группы. Можно ли сказать, что они становятся аналогом ядра приложения?
Ядро — это что-то внутреннее. Если смотреть на это как на многослойную микросервисную архитектуру, где есть слои с сервисами для высокоуровневого API, интеграционные сервисы и небольшие атомарные сервисы, то ядром в этом случае являются как раз эти низкоуровневые атомарные сервисы. У нас другое разделение. В отдельную группу выделяем часто меняющиеся сервисы на разных слоях микросервисной архитектуры.
Сервисы, которые меняются редко, имеют собственный независимый процесс непрерывного деплоя. Для деплоя отдельной группы создали отдельный репозиторий, где хранятся конфигурации деплоя, имеется свой менеджер пакетов для слепка версий и выстроен непрерывный деплой на разные окружения. При этом непрерывная интеграция, которая является базовой частью непрерывного деплоя, распределена по репозиториям часто меняющихся сервисов.
— Всегда интересно узнать не только про подходы и технологии, но и про личный опыт компаний. Давай возьмем 2ГИС. Скажи, процесс перехода на микросервисы занял много времени? Что доделывается сейчас?
Переход начался более трех лет назад. Вначале мы провели исследование. Попробовали Mesosphere, OpenShift и другие доступные на тот момент PaaS и IaaS решения. Выбор пал на Deis и у нас есть доклад на эту тему, а также доклад о поддержке Deis в течение полутора лет. Это open source аналог Heroku, он позволял доставлять свой сервис как Heroku Buildpack’ом, Dockerfile’ом или подготовленным Docker-образом. У него была хорошая документация и грамотная архитектура. Мы написали семейство make файлов как универсальный набор команд поверх командной консоли для деплоя в Deis. Это была первая реализация для непрерывного деплоя.
Далее компания перешла на Deis2. У него была обратная совместимость с Deis, но в качестве среды оркестрации уже использовался Kubernetes. Команда Infrastructure & Operations, которая занималась поднятием этих платформ и их дальнейшим обслуживанием, разработала свой инструмент по деплою в Kubernetes и Deis2. Те, кто переходили на микросервисные архитектуры, в большинстве случаев выбирали Deis2, но были и те, кто выбирал низкоуровневый путь — прямую работу с Kubernetes. Вторые оказались правы. Deis2 обладал рядом недостатков: не было возможности зайти в запущенный контейнер, pod мог состоять только из одного контейнера и для каждого pod’а создавался свой namespace. Это не давало гибкости и осложняло работу команде Infrastructure & Operations. Kubernetes был лишен этих недостатков. В итоге приняли решение перенести все сервисы из Deis2 в Kubernetes. Этим летом запланирован полный отказ от Deis2 в пользу Kubernetes.
— Скажи, если бы ты стал сейчас переписывать все под микросервисную архитектуру, уже имея такой опыт за плечами, что бы ты изменил в реализации? Какие интересные идеи уже невозможно применить?
Я бы сразу почитал про Helm. У него очень большая документация, и ее тяжело прочитать за короткий промежуток времени. Но Helm хорошо выстраивает понимание практики деплоя микросервисной архитектуры.
Разработанный в 2ГИС инструмент деплоя позволяет объединять деплой сразу нескольких сервисов, но отлично подходит для коллекции из не более 10 сервисов. У нас было больше десяти сервисов (на тестовых окружениях для каждой задачи мы поднимали дополнительно свои backing сервисы: Postgres, Kafka, Zookeeper, Ceph). В какой-то момент я понял, что yaml-программирование не раскрывает всего потенциала, с ним неудобно работать в IDE, алиасы и якоря не покрывают всей вложенности. Я понял, что нужен язык программирования. Выбрал Python, потому что на нем был написан инструмент деплоя и Python точно есть в контейнере с этим инструментом. Описал абстрактную фабрику, которая бы порождала семейство описаний сервисов. Это позволило добавить описания для абстракций, которые мы не деплоим. Например, параметры доступа к третьим сервисам. Они были все так же представлены классами, как и описания наших сервисов, и все так же порождались абстрактной фабрикой. Появилась возможность вкладывать одни описания в другие и переиспользовать описания. Можно наследовать окружения, например, нагрузочное от стейджинга, так как хотелось иметь актуальное одинаковое описание ресурсов (процессор и память) для обоих окружений. Я понял, что я ушел дальше, чем Helm, и мне интересны решения по описанию ресурсов Kubernetes на каком-либо языке программирования, а не на yaml.
Еще бы сделал публичный API раздробленным на несколько сервисов и внедрил бы API Gateway. Но текущая реализация вполне справляется с поставленной задачей, а лучшее — враг хорошего.
Но что бы я точно не стал менять, так это результат выбора: монолит или микросервисная архитектура. Микросервисная архитектура принесла нам гораздо больше плюсов, чем сложностей, которые мы успешно смогли победить.
Мы попросили поделиться своим кейсом разработки и доставки микросервисов Алексея Баитова, ведущего инженера 2ГИС. Поговорим на тему архитектурных решений, деплоя и возможности масштабирования. Расспросим про трендовые и просто удобные инструменты для работы.
— Алексей, расскажи, пожалуйста, немного о себе и о твоей команде в 2ГИС. Над чем сейчас работаете?
В IT я пришел в далеком 2003 году как системный администратор, в разработку погрузился в 2011 году. За это время поработал на PHP, JavaScript, реализовал серию RESTful сервисов и Python-драйвер для Git. В 2ГИС работаю с 2015 года.
Поучаствовал в разработке двух микросервисных архитектур. Первая состояла из одного сервиса. Это был асинхронный реверсивный прокси с кэшем. По сути он занимался досылкой сообщений. Проработкой требований, разработкой и выстраиванием DevOps занимался я один, но мне помогали эксперты из нашей компании 2ГИС.
Сервис был написан на Go. Быстрая компиляция позволила не ждать, и я смог сконцентрироваться и на Continuous Deployment. Тогда мы только начинали использовать GitLab CI, Prometheus, Grafana и Deis (open source аналог Heroku). У нас есть команда Infrastructure and Operations, которая как раз к моменту моей разработки вывела все эти инфраструктурные решения на production ready уровень. Я решил попробовать все это и успешно реализовал независимый микросервис.
Два года назад я перешел в другую команду на новый проект, где стал заниматься функциональным программированием на Scala. Наша команда с нуля разработала микросервисную архитектуру для хранилища рекламных материалов 2ГИС на Scala, C# и JavaScript. В основу всех сервисов я заложил инструментарий и полученный опыт для выстраивания практик DevOps (Continuous Deployment и сопровождение). Архитектура прошла путь от прототипа до промышленной эксплуатации. Она поглотила два монолита, теперь состоит из 15 сервисов и постоянно расширяется.
— Ты согласен, что микросервисы по сути являются набором независимо развернутых сервисов, имеющих общие характеристики, то есть именно набор определенных черт придает им вид микросервиса? Данное определение нужно расширять? Или по факту компании по-разному понимают микросервисную архитектуру?
Мне нравится следующее определение. Микросервисная архитектура — это архитектурный стиль, который структурирует приложение как коллекцию слабо связанных сервисов, которые реализуют определенную бизнес-логику. Сервисы в микросервисной архитектуре могут не обладать общими характеристиками, но объединяются в рамках общей бизнес-логики.
Конечно, у каждой компании будет «свой» микросервис. Это ведь набор практик: распределенная архитектура, непрерывная интеграция и доставка и так далее. Если расширить понятие практики до используемых инструментов, варианты реализации микросервисов будут самые разнообразные.— Существуют разные мнения о составе команды, которая должна быть задействована в написании и поддержке микросервисов. Что ты думаешь об этом? Какова оптимальная численность команды и как должно быть построено взаимодействием внутри нее при разработке микросервисной архитектуры? Есть ли удачный пример командной работы из твоей практики?
Считается правильным разрабатывать сервис таким образом, чтобы вся его предметная область могла уместиться в голове одного разработчика. При этом в разработке этого сервиса могут участвовать несколько человек. Это поможет избежать bus-фактора, когда разработчик ушел в отпуск или заболел. Корректное разбиение на сервисы позволяет новому человеку быстро войти в контекст.
«Микросервисная архитектура» говорит нам о том, что в ее состав часто входят несколько сервисов. Таким образом, одним разработчиком уже не обойтись. Микросервисная архитектура отталкивается от продуктовой модели (или общей бизнес-логики). Разработчики подбираются так, чтобы получилось эту модель реализовать и при этом сфокусироваться на клиенте.
Фокусировка на клиенте организуется за счет прямого контакта разработчика с клиентом. Разработчикам надо видеть, как используется их продукт. Отсюда уже вытекают пожелания к знаниям в технологической области, умению как можно быстрее доставить продукт клиенту и сопровождать продукт.
Про численность команды однозначно сказать сложно. Всем уже, наверно, известно высказывание Джефа Безоса, основателя Amazon, что размер команды при сервис-ориентированной разработке должен быть достаточно мал, чтобы всех можно было накормить двумя пиццами. В комментариях на Хабре была дискуссия на эту тему, и там писали, что одному человеку может быть мало одной пиццы и поэтому команда должна состоять из одного-двух человек. Мартин Фаулер же, цитируя высказывание про две пиццы, сказал, что речь идет про большие американские пиццы, после чего уточнил, что в команде должно быть не 50, а в районе 12 человек. Я считаю, что все зависит от продуктовой модели. Но уточнение Фаулера про «не более 12 человек» на моей практике пока выигрывало. Я заметил, что внутри команды желательно разделиться по технологическим интересам, найти единомышленников.
Необязательно, чтобы все в команде хорошо знали все используемые в работе технологические области, но суммарные знания команды должны быть равномерно глубокими. Например, два человека занимаются первоначальным выстраиванием деплоя и в будущем, скорее всего, они также значительно его усовершенствуют. Но при этом вся команда должна хорошо понимать процесс деплоя. Это позволит ей высказывать пожелания и вносить изменения. Почему два человека? Потому что порой один человек может впасть в творческий ступор. А в дискуссии рождается истина.
Мы естественным образом построились по такому принципу, объединились по технологическим интересам. При этом разработчик может также заниматься налаживанием DevOps-практик, а QA-инженер — разрабатывать вспомогательный, не-продакшн сервис (например, прогревалку кэша или сервис поиска аномалий в данных на разных окружениях).
— Почти каждый доклад о микросервисах начинается с рассказа о том, что «вот у нас был „айсберг“ и мы его пилили, пилили, пилили… Новые части приложения делали на основе микросервисов, а затем уже начали отделять „кусочки“ от основной махины…»
Скажи, ты сторонник разработки с нуля или могут быть ситуации, когда стоит делать постепенный вывод из монолита? Как верно определить стратегию «выхода»?
Я сторонник разработки с нуля. Но это работает только в случае не слишком сложного набора функций. Обычно делается небольшой MVP-монолит. И иногда приходится несколько раз в корне менять его внутреннюю реализацию. Это может быть вызвано как сменой технического задания, так и тем, что приходит понимание реализации — появляются высокоуровневые абстракции на уровне бизнес-модели. После этого можно переходить к микросервисной архитектуре.
Но если хорошо проработать эти абстракции в самом начале и прорисовать их в разных нотациях (UML, BPMN, IDEF), чтобы все участники процесса понимали то, с чем они работают, то вполне возможно реализовать микросервисную архитектуру сразу.
Наш путь к микросервисной архитектуре был не прямой. Сначала был монолит. Он обрабатывал текстовые рекламные материалы. Три с половиной года назад нам потребовалось работать с графическими рекламными материалами (изображениями, логотипами). Было желание внести в бизнес-логику то, чего не хватало при работе с текстовыми рекламными материалами.
Чтобы опробовать новую бизнес-модель, мы реализовали работу с графическими рекламными материалами как второй монолит на другом стеке технологий. После полутора лет эксплуатации мы поняли, что такой подход был верным.За это время у нас появилось много хотелок, выявились шероховатости бизнес-логики.
Реализация второго монолита была сложно расширяемой на первый. Поэтому мы решили не вести разработку сразу в двух монолитах, а объединить их в рамках третьей архитектуры по той самой новой бизнес-модели. Была создана команда в составе семи разработчиков, одного QA-инженера и двух аналитиков. Два разработчика из этой команды ранее создавали и поддерживали первый монолит, а еще один — второй монолит. То есть наша команда уже на входе хорошо знала подводные камни предыдущих монолитов.
Первый монолит был написан на C#. Второй — на PHP. Нам не хотелось терять отлаженные объемные куски кода из первого монолита и при этом требовались многопоточность, безопасный код и строгая типизация. PHP-код под эти требования частично не попадал. Поэтому C# остался как основа и реализовал то, что он хорошо делал в рамках первого монолита — работу с контентом рекламных материалов — но уже на базе другого хранилища: S3-совместимого хранилища и Kafka.
Для работы с той самой новой бизнес-моделью на этот раз была выбрана Scala и база данных PostgreSQL. Scala удовлетворяла нашим техническим требованиям. К тому же, Scala-разработчики располагались на том же этаже, что и C# разработчики. Это сокращало время на межкомандные коммуникации. Сработал закон Конвея — структура компании продиктовала структуру приложения. PHP-разработчик перепрофилировался в Scala-разработчика. Я же как раз закончил работу над независимым микросервисом на Golang с полным CI/CD циклом, после чего пришел в команду и тоже стал Scala-разработчиком.
Интересно, что именно я предложил использовать для работы с бизнес-логикой Scala, а не C#. Дело в том, что у нас не было достаточного количества C#-разработчиков. PHP-шник и я хотели переквалифицироваться на Scala. Плюс у нас была возможность привлечь опытного Scala-разработчика. Еще момент: если бы мы реализовывали все на C#, то на выходе могли бы получить либо микросервисную архитектуру, либо очередной монолит. Разделение же на Scala и C#, различные по потребностям хранилища и наличие опытных разработчиков в каждой из требуемых областей - все это прямо указывало нам на микросервисную архитектуру. И мы от этого только выиграли. Год назад микросервисная архитектура по работе с графическими и текстовыми материалами вышла в промышленную эксплуатацию и успешно работает до сих пор.
К вопросу о том, можно ли создать микросервисную архитектуру с нуля. Полтора года назад в процессе работы над микросервисной архитектурой к нам пришло требование поддержать новое направление в наших продуктах — видеорекламные материалы. Необходимо было в короткие сроки опробовать новую модель продаж. Наша архитектура была в начальной стадии разработки. Работа с видеоматериалами охватывала новую область технологий. Вектор развития мы решили не менять и реализовали MVP по видеоматериалам как отдельно стоящую микросервисную архитектуру на C# и доверенном видеохостинге. Это успешный опыт, и у нас есть доклад на эту тему. Таким образом, у нас появилось две параллельно существующих микросервисных архитектуры. MVP сильно не развивался, по нему также накопились хотелки и в скором времени мы объединим все в рамках одной микросервисной архитектуры — получится единое хранилище рекламных текстовых, графических и видеоматериалов.
— Сходу можно назвать два важных фактора, которые говорят в пользу микросервисов. Первый — возможность вывода отдельных частей в облако и, как следствие, колоссальная масштабируемость. Второй — возможность создания отдельного сервиса на другом языке. Какие еще ты видишь плюсы перехода на микросервисы? Ну и о минусах, конечно, тоже хочется услышать.
Если говорить про технологическую составляющую, то к плюсам, помимо перечисленного, можно отнести возможность использования другого стека технологий. А если он нас не устроит, выберем еще один. Новые технологии обходят проблемы старых решений. Микросервисная архитектура также дает устойчивость и независимость: деградация одного сервиса не должна приводить к полной деградации всей системы. Компонуемость сервисов позволяет переиспользовать функционал сервиса в других микросервисных архитектурах. С точки зрения организационной составляющей разбиение на сервисы позволяет разделить разработку в пределах распределенных команд или одной большой команды.
Основные минусы микросервисной архитектуры: она гораздо сложнее, а ее реализация обходится дороже.Еще нужно быть готовым к поддержке контрактов сервисов, выбору правильного протокола удаленного доступа, решению вопросов безопасного межсервисного взаимодействия, возможным отказам, а также к дедупликации и управлению распределенными транзакциями.
Вообще в свободном доступе можно найти много информации и материалов о том, как с этим работать. Но по факту все зависит от задачи. На моей практике плюсы всегда оказывались весомее минусов.
Еще стоит вспомнить слова Сэма Ньюмена: чем меньше сервис, тем сильнее проявляются все преимущества и недостатки микросервисной архитектуры.
— У тебя есть несколько интересных докладов про микросервисы. Деплой микросервисов и Подход к Continuous Deployment в микросервисной архитектуре. На одном из слайдов первого есть «шаблоны» деплоя, а в докладе ты говоришь о том, что для вас трендовым подходом является распространение через контейнеры. За это время ничего не изменилось? Связка Docker + Kubernetes не утратила актуальность?
Мы переводим на эту связку все больше и больше сервисов. Я думаю, что мы выбрали правильное направление и пока планируем его придерживаться. Если что-то поменяется, я обязательно расскажу об этом в новом докладе или интервью.
— Насколько беспроблемным является непрерывный деплой и передача микросервисов на прод?
Все зависит от того, как выстроить процесс. Вначале кажется, что все просто. Сервисы независимы, деплоятся порознь. Слабая связанность обеспечивается за счет разных подходов к работе с эволюцией контракта. И тут уже приходится выбирать. Например, можно внедрить версионирование контракта или добавить сервис для разъединения контракта (contract decoupling).
Если в микросервисной архитектуре в активной разработке находятся сразу 10 или больше сервисов и у каждого свое версионирование, то возникает проблема согласованности версий. Надо стараться не запутаться в совместимости сервисов разных версий.
Непрерывный деплой подразумевает, что мы можем доставить приложение в любое время на любое окружение.
Приложением в микросервисной архитектуре является коллекция сервисов. Значит, в любой момент времени нам нужно знать стабильное сочетание версий сервисов. А еще мы должны где-то иметь совокупность доменных адресов и другие параметры, свойственные для разных окружений для конфигурирования сервисов и связывания их друг с другом.Все это нужно где-то хранить, править изменения в нескольких местах (микросервисы ведь независимы) и не запутаться.
Непрерывный деплой подразумевает возможность в любое время откатиться на любую версию. Соответственно, может быть так, что потребуется откатить сразу несколько сервисов и нужно будет соблюсти правильный обратный порядок деплоя сервисов.
В одном из своих докладов я рассказываю о нашем подходе к эволюции контрактов, о том, как решили проблему согласованности версий и как выстроили процесс деплоя в микровервисной архитектуре из более чем десяти сервисов. Непрерывный деплой не может быть беспроблемным, но все решаемо.
— Какой может быть начальный набор инструментов для непрерывного деплоя микросервисов? Какие варианты ты бы посоветовал использовать для работы с микросервисами?
Непрерывный деплой — это последовательность этапов (pipelines), включающих непрерывную интеграцию, интеграционные тесты и доставку сервиса на среду оркестрации. Наиболее популярными инструментами для последовательного выполнения этапов являются Jenkins 2 Pipelines, TeamCity Build Chains, Bitbucket Pipelines и GitLab CI Pipelines. Вначале нужно автоматизировать непрерывную интеграцию (Continuous Integration, CI). Нужен удаленный CI сервер, который бы занимался сборкой и прогоном тестов на этой сборке.
Перечисленные инструменты предлагают свои решения. Мы используем GitLab CI, и в качестве таких серверов выступают GitLab Runner’ы. Артефактом сборки является Docker-образ. В рамках интеграционных тестов можно провести нагрузочные и емкостные тесты с помощью Gatling, в частности, чтобы определить требуемые ему ресурсы (процессор и память) для функционирования на среде оркестрации (например, Kubernetes). Для деплоя широко применяется Helm, он позволяет описать микросервисную архитектуру для разных окружений. В нашей компании мы не используем Helm. У нас — собственный инструмент деплоя, который был создан, когда Helm был в версии Classic и не поддерживал разные окружения. Оба эти инструмента обладают схожими полезными качествами, но реализация и дистрибуция различаются. А собственный инструмент позволяет вносить в деплой улучшения и адаптировать все под нашу инфраструктуру.
— Какие технологии оптимальны для небольших и средних компаний, которые хотят внедрить у себя микросервисы? Не слишком ли это дорогое удовольствие для них?
Все чаще встречаю подтверждения, что небольшим и средним компаниям нецелесообразно поднимать собственную среду оркестрации (например, Kubernetes, Docker Swarm или Apache Mesos). Ведь для этого им нужно содержать свое железо и одну или несколько команд для его настройки и поддержки. Проще использовать популярные сторонние облачные сервисы (на базе Google Cloud Platform, Amazon Web Services, Microsoft Azure или Oracle Cloud) и правильно выбрать тариф.
Мы используем бесплатную версию GitLab и GitLab CI. Этот инструмент позволяет хранить код и видеть всю информацию о его деплое в одном месте. GitLab активными темпами интегрируется с Helm и разными облачными сервисами. Я часто говорю Helm и не привожу аналогов. У Helm есть свои недостатки, но эти недостатки для всех разные и зависят от масштаба разработки, поэтому можно с него начать, а потом обязательно найдутся необходимые инструменты в дополнение.
Наш инструмент конфигурации и деплоя, о котором я говорил ранее, я бы тоже порекомендовал, но он пока недоступен как open source, хотя работы в этом направлении активно ведутся.
Интересно также взглянуть на Spinnaker и Heroku — это разные, но зарекомендовавшие себя решения, которые позволяют быстро задеплоиться на облачные сервисы.
— С одной стороны, для обеспечения контроля связей и надежности на уровне, достаточном для промышленной эксплуатации, нужна версионность. При этом при большом количестве сервисов соблюдение версионности может серьезно замедлить развитие инфраструктуры. Как вы решили эту проблему?
Разрабатывая микросервисную архитектуру из нескольких сервисов, мы прошли путь от прототипа до промышленной эксплуатации. С версионированием мы впервые столкнулись, когда нам потребовалось задеплоить единое тестовое окружение из всех сервисов, а потом в ходе изменения каждого сервиса поддерживать стабильность имеющегося функционала.
Мы не хотели плодить версии (а их на этапе прототипа порождается очень много) или внедрять разные сервисы для разъединения контракта. Микросервисная архитектура позволяла заменять один сервис другим с другой реализацией или на другом стеке технологий.
При добавлении или удалении сервисов версионирование не помогает. Мы предложили свой подход к эволюции контракта. Наиболее часто меняющиеся сервисы мы объединили в единый слепок версий, он же пакет (package). Далее реализовали простой пакетный менеджер и стали использовать этот слепок для деплоя коллекции часто меняющихся сервисов.
Версиями сервисов (не контрактов) являются тэги Docker-образов, которые в свою очередь равны хэш коммитам или тэгам в Git. Нам пришлось перестроить непрерывный деплой, но от этого стало только легче. Я подробно рассказывал об этом в докладе, а динамику деплоя можно посмотреть на слайдах. В общем, мы смогли на этапе прототипа отказаться от привычного версионирования контракта и значительно ускорили разработку.
Версионирование контракта мы добавили в сервисы за месяц до выхода в промышленную эксплуатацию, когда нам потребовалось интегрироваться с внешними клиентами. И то: мы добавили его только в публичный API. И вот мы уже год находимся в промышленной эксплуатации и за это время подняли всего три минорные версии. При этом мы продолжаем заниматься активной разработкой: добавляем и удаляем функции. У нас есть клиентские сервисы, которые используют наш публичный API. Мы знаем, какие методы API используют внешние клиенты, а какие — мы. Если изменения касаются только наших методов, то в большинстве случаев мы не поднимаем версию API, изменяем только слепок версий и не делаем необратимых изменений на коротких промежутках времени.
Таким образом мы смогли отказаться от множества версий контракта. Если бы мы их оставили, нам пришлось бы покрывать тестами каждую версию; просить внешних клиентов переходить на новую версию публичного API, хотя большинство изменений их не касаются; следить за тем, не используется ли старая версия, и удалять ее из кода.
Знаю, что вы используете подход, при котором часть микросервисов выделяете в отдельные группы. Можно ли сказать, что они становятся аналогом ядра приложения?
Ядро — это что-то внутреннее. Если смотреть на это как на многослойную микросервисную архитектуру, где есть слои с сервисами для высокоуровневого API, интеграционные сервисы и небольшие атомарные сервисы, то ядром в этом случае являются как раз эти низкоуровневые атомарные сервисы. У нас другое разделение. В отдельную группу выделяем часто меняющиеся сервисы на разных слоях микросервисной архитектуры.
Сервисы, которые меняются редко, имеют собственный независимый процесс непрерывного деплоя. Для деплоя отдельной группы создали отдельный репозиторий, где хранятся конфигурации деплоя, имеется свой менеджер пакетов для слепка версий и выстроен непрерывный деплой на разные окружения. При этом непрерывная интеграция, которая является базовой частью непрерывного деплоя, распределена по репозиториям часто меняющихся сервисов.
— Всегда интересно узнать не только про подходы и технологии, но и про личный опыт компаний. Давай возьмем 2ГИС. Скажи, процесс перехода на микросервисы занял много времени? Что доделывается сейчас?
Переход начался более трех лет назад. Вначале мы провели исследование. Попробовали Mesosphere, OpenShift и другие доступные на тот момент PaaS и IaaS решения. Выбор пал на Deis и у нас есть доклад на эту тему, а также доклад о поддержке Deis в течение полутора лет. Это open source аналог Heroku, он позволял доставлять свой сервис как Heroku Buildpack’ом, Dockerfile’ом или подготовленным Docker-образом. У него была хорошая документация и грамотная архитектура. Мы написали семейство make файлов как универсальный набор команд поверх командной консоли для деплоя в Deis. Это была первая реализация для непрерывного деплоя.
Далее компания перешла на Deis2. У него была обратная совместимость с Deis, но в качестве среды оркестрации уже использовался Kubernetes. Команда Infrastructure & Operations, которая занималась поднятием этих платформ и их дальнейшим обслуживанием, разработала свой инструмент по деплою в Kubernetes и Deis2. Те, кто переходили на микросервисные архитектуры, в большинстве случаев выбирали Deis2, но были и те, кто выбирал низкоуровневый путь — прямую работу с Kubernetes. Вторые оказались правы. Deis2 обладал рядом недостатков: не было возможности зайти в запущенный контейнер, pod мог состоять только из одного контейнера и для каждого pod’а создавался свой namespace. Это не давало гибкости и осложняло работу команде Infrastructure & Operations. Kubernetes был лишен этих недостатков. В итоге приняли решение перенести все сервисы из Deis2 в Kubernetes. Этим летом запланирован полный отказ от Deis2 в пользу Kubernetes.
— Скажи, если бы ты стал сейчас переписывать все под микросервисную архитектуру, уже имея такой опыт за плечами, что бы ты изменил в реализации? Какие интересные идеи уже невозможно применить?
Я бы сразу почитал про Helm. У него очень большая документация, и ее тяжело прочитать за короткий промежуток времени. Но Helm хорошо выстраивает понимание практики деплоя микросервисной архитектуры.
Helm сразу наталкивает на мысль, что микросервисная архитектура — это коллекция сервисов: они хоть и слабо связаны друг с другом, но деплоятся вместе.В Helm есть зависимость chart’ов и можно описать корневой chart, который бы описывал сервисы и связывал их по параметрам, присущим определенному окружению.
Разработанный в 2ГИС инструмент деплоя позволяет объединять деплой сразу нескольких сервисов, но отлично подходит для коллекции из не более 10 сервисов. У нас было больше десяти сервисов (на тестовых окружениях для каждой задачи мы поднимали дополнительно свои backing сервисы: Postgres, Kafka, Zookeeper, Ceph). В какой-то момент я понял, что yaml-программирование не раскрывает всего потенциала, с ним неудобно работать в IDE, алиасы и якоря не покрывают всей вложенности. Я понял, что нужен язык программирования. Выбрал Python, потому что на нем был написан инструмент деплоя и Python точно есть в контейнере с этим инструментом. Описал абстрактную фабрику, которая бы порождала семейство описаний сервисов. Это позволило добавить описания для абстракций, которые мы не деплоим. Например, параметры доступа к третьим сервисам. Они были все так же представлены классами, как и описания наших сервисов, и все так же порождались абстрактной фабрикой. Появилась возможность вкладывать одни описания в другие и переиспользовать описания. Можно наследовать окружения, например, нагрузочное от стейджинга, так как хотелось иметь актуальное одинаковое описание ресурсов (процессор и память) для обоих окружений. Я понял, что я ушел дальше, чем Helm, и мне интересны решения по описанию ресурсов Kubernetes на каком-либо языке программирования, а не на yaml.
Еще бы сделал публичный API раздробленным на несколько сервисов и внедрил бы API Gateway. Но текущая реализация вполне справляется с поставленной задачей, а лучшее — враг хорошего.
Но что бы я точно не стал менять, так это результат выбора: монолит или микросервисная архитектура. Микросервисная архитектура принесла нам гораздо больше плюсов, чем сложностей, которые мы успешно смогли победить.
Принципы DevOps плотно проникли в жизнь разработчиков, пора собраться на большой профессиональной конференции для обмена опытом. Присоединяйтесь к DevOpsConf Russia.
DevOpsConf Russia состоится 1 и 2 октября в Москве и будет логически продолжать серию RootConf, название которой, однако, перестало отражать то, что мы делаем. Мы собираемся обсуждать DevOps как единое целое из всех процессов разработки, тестирования и эксплуатации.
Если в вашей компании DevOps набирает обороты, или же трансформация еще только начата, но уже есть определенные успехи – приходите поделиться ими с сообществом. Программный комитет ждет ваши заявки до 15 августа.