Мы рассмотрели способы разбиения крупных сервисов на более мелкие, но в чем первопричина того, что сервисы разрослись до таких больших размеров? Сначала нужно понять, что разрастание сервиса до определенного объема, требующего его разбиения, — это вполне нормальное явление. Нам нужно, чтобы архитектура системы со временем изменялась. Главное — разобраться в том, что она требует разбиения еще до того, как такое разбиение станет обходиться слишком дорого.
Однако на практике многие из нас видели, как сервисы разрастаются, приобретая размеры, абсолютно не отвечающие здравому смыслу. Несмотря на то что нам известно, что с меньшим набором сервисов проще работать, чем с тем огромным чудовищем, которое у нас получилось, мы по-прежнему занимаемся выращиванием чудовища. Почему?
Часть проблемы заключается в том, чтобы знать, с чего начать, и я надеюсь, что эта глава помогла вам в этом разобраться. Но другой проблемой являются затраты, связанные с разбиением сервисов на части. Нелегкими задачами будут поиск среды для запуска сервиса, раскрутка нового стека сервисов и т. д. Как же со всем этим справиться? Если дело нужное, но сложное, мы должны попытаться все упростить. Снизить затраты, связанные с созданием нового сервиса, может ставка на применение библиотек и облегченных сред сервисов. Упростить предоставление и тестирование систем может обеспечение людям доступа к самообслуживаемым виртуальным машинам или даже создание платформы в качестве услуги (PaaS). В следующих главах будет рассмотрен ряд способов, позволяющих снизить эти затраты.
Мы разбиваем систему на части путем поиска стыков, по которым могут проходить границы сервисов, и применение этого подхода может носить поэтапный характер. Совершенствуя в первую очередь поиск этих стыков и работу по снижению стоимости разбиения сервисов, мы можем продолжить наращивание и развитие систем, реагируя на все встречающиеся на этом пути требования. Как вы уже могли заметить, часть этой работы требует особого усердия. Но сам по себе тот факт, что это можно делать постепенно, означает, что этой работы не нужно бояться.
Итак, мы можем разбивать сервисы, но при этом проявляются некоторые новые проблемы. Теперь у нас намного больше движущихся частей, требующих доводки до работы в производственном режиме! Следовательно, настало время погрузиться в мир развертывания микросервисов.
6. Развертывание
Монолитные системы развертываются довольно просто. А вот с развертыванием микросервисов с их взаимозависимостями придется повозиться. Если выбрать неверный подход к развертыванию, оно настолько усложнится, что ваша жизнь превратится в сплошные мучения. В этой главе будет рассматриваться ряд приемов и технологий, призванных помочь в развертывании микросервисов в мелкоструктурных архитектурах.
Но начнем мы с того, что рассмотрим вопросы непрерывной интеграции и непрерывной поставки. Эти родственные, но различные понятия помогут наметить контуры других решений, которые будут приниматься в ходе размышлений о том, что именно создавать, как создавать и как развертывать созданное.
Понятие непрерывной интеграции (continuous integration (CI)) существует уже несколько лет. Но на объяснение ее основ все же стоит потратить немного времени, особенно при обдумывании отображений между микросервисами, сборок и хранилищ управления версиями, требующем рассмотрения нескольких разных вариантов.
При использовании CI основной целью является поддержка всеобщего синхронизированного состояния, которая достигается путем проверки того, что только что введенный в эксплуатацию код интегрируется с существующим кодом должным образом. Для достижения этой цели CI-сервер определяет переданный код и проводит ряд проверок, убеждаясь в том, что код скомпилирован и тесты с ним проходят без сбоев.
В качестве части данного процесса часто создается артефакт (или артефакты), используемый для дальнейшей проверки, например развертывания работающего сервиса с целью запуска для него ряда тестов. В идеале нам потребуется создавать эти артефакты только один раз и использовать их для всех развертываний той или иной версии кода. Это делается для того, чтобы избежать многократного повторения одних и тех же действий и чтобы можно было подтвердить, что развернутый нами артефакт уже прошел тестирование. Чтобы допустить повторное использование этих артефактов, мы помещаем их в особое хранилище, либо предоставляемое самим CI-инструментарием, либо находящееся в отдельной системе.
Вскоре мы рассмотрим, какого именно сорта артефакты можно будет использовать для микросервисов, а о тестировании более подробно поговорим в главе 7.
CI предоставляет ряд преимуществ. Мы получаем некое довольно быстро формируемое представление о качестве кода. Непрерывная интеграция позволяет автоматизировать создание двоичных артефактов. Весь код, требующийся для создания артефакта, проходит самостоятельное управление версиями, поэтому при необходимости артефакт можно создать заново. Мы также получаем некоторую возможность отслеживания, проходящего от развернутого артефакта назад к его коду, и в зависимости от возможностей CI-инструментария можем наблюдать, какие тесты запущены в отношении как кода, так и артефакта. Именно эти обстоятельства и обусловили успешное применение CI.
А вам приходилось этим заниматься? Подозреваю, что вы уже использовали непрерывную интеграцию в своей организации. Если нет, то пора приступить к ее применению. Дело в том, что это основной способ, позволяющий быстрее и легче вносить изменения, и без него путь к микросервисам будет слишком труден. Мне приходилось работать с командами, которые, несмотря на заявления о применении CI, вообще ею не пользовались. Они путали применение CI-инструментария с внедрением практического использования CI. Инструментарий — лишь средство, позволяющее применить сам подход.
Мне нравятся три вопроса, которые Джез Хамбл (Jez Humble) задает людям, чтобы проверить, правильно ли они понимают, что такое CI.
• Вы проходите ежедневную проверку в основной программе?
Вам нужно убедиться в том, что ваш код интегрируется в систему. Если не проводить частые проверки своего кода вместе с чьими-либо изменениями, можно существенно усложнить будущую интеграцию. Даже при использовании для управления изменениями недолговечных ответвлений интеграцию с единой основной ветвью нужно проводить как можно чаще.
• Имеется ли у вас набор тестов для проверки правильности вносимых вами изменений?
Без тестов мы просто знаем, что синтаксически интеграция работает, но не знаем, не нарушим ли поведение системы. CI без ряда проверок, подтверждающих ожидаемое поведение кода, — это не CI.
• Служит ли исправление неисправной сборки главным приоритетом команды?
Проход зеленой сборки означает, что изменения были безопасно интегрированы. Красная сборка означает, что последнее изменение, возможно, не прошло интеграцию. Нужно остановить все последующие проверки, не применяемые для исправления сборок, чтобы пройти тесты повторно. Если нагромоздить большое количество изменений, время исправления сборки существенно возрастет. Мне приходилось работать с командами, у которых сборка оставалась неисправной по нескольку дней, что приводило к необходимости прикладывать существенные усилия для получения в итоге проходящей тесты сборки.
Размышляя о микросервисах и непрерывной интеграции, нужно подумать о том, как средство CI создает отображение на отдельно взятые микросервисы. Как я уже не раз говорил, нужно убедиться в том, что мы можем внести изменение в отдельный микросервис и развернуть его независимо от остальных микросервисов. Как, памятуя об этом, мы отображаем отдельно взятые микросервисы на CI-сборки и исходный код?
Если начать с самого простого варианта, то можно было бы свалить все в кучу. У нас есть одно огромное хранилище, в котором находится весь код (рис. 6.1). Любая проверка этого исходного кода запустит сборку, где мы запустим все этапы верификации, связанные с микросервисами, и создадим несколько артефактов, имеющих обратную связь с той же самой сборкой.
Рис. 6.1. Использование единого хранилища исходного кода и CI-сборки для всех микросервисов
На первый взгляд все кажется намного проще других подходов: меньше приходится беспокоиться о хранилищах, да и создание сборок концептуально выглядит проще. С точки зрения разработчика, все также выглядит довольно просто. Нужно лишь проверять находящийся внутри код. Если приходится работать сразу с несколькими сервисами, следует беспокоиться только об одном совершаемом действии.
Такая модель может работать весьма успешно при условии, что вы придерживаетесь замысла жестко регламентированных выпусков, где вас не смущает одновременное развертывание сразу нескольких сервисов. Вообще-то это именно тот шаблон, которого следует всячески избегать, но на самой ранней стадии проекта, особенно если над проектом работает одна команда, его кратковременное применение имеет определенный смысл.
У данной модели есть ряд весьма существенных недостатков. При внесении самых незначительных изменений в какой-либо сервис, например при изменении поведения пользовательского сервиса, показанного на рис. 6.1, должны быть собраны и верифицированы все остальные сервисы. На это может уйти больше времени, чем требуется в других обстоятельствах, поскольку придется ожидать прохождения тестов там, где в тестировании нет никакого смысла. Это влияет на продолжительность цикла и скорость, с которой мы можем пройти путь от разработки до внедрения отдельного изменения. Но большее беспокойство вызывает мысль о том, что нужно знать, какие артефакты должны быть развернуты, а какие — нет. Знаю ли я о том, нужно ли развертывать все собранные сервисы для того, чтобы запустить мои небольшие изменения в производство? Ответ на этот вопрос может вызвать затруднения, нелегко бывает догадаться, какие сервисы действительно подверглись изменениям, в процессе простого чтения сообщений о совершении действий. Организации, использующие такой подход, часто склоняются к одновременному развертыванию всех сервисов, чего нужно избегать.