Как обеспечить непрерывную интеграцию и доставку (CI/CD) в микросервисных архитектурах и в чем особенности этого процесса по сравнению с монолитными системами?
Отличие монолитных приложений от микросервисных
В статье речь пойдет именно о продуктах. В отличие от проекта, продукт не имеет конечной даты завершения — он постоянно развивается и совершенствуется, чтобы сделать работу и жизнь определенных заинтересованных групп легче за счет автоматизации. Со временем потребности могут изменяться, поэтому важная техническая особенность продукта заключается в его гибкости для наращивания функциональных возможностей. Для этого мы устанавливаем и проверяем гипотезы, то есть предполагаем, как то или иное изменение продукта повлияет на заинтересованные группы, оцениваем это влияние и затем принимаем решение о его внедрении.
«Монолит» представляет собой приложение, где все компоненты находятся внутри одной кодовой базы. Основной плюс монолитов — простота разработки и тестирования. Микросервисные архитектуры имеют другую природу происхождения. Микросервисы необходимы для того, чтобы раздробить функционал на такие блоки, которые можно анализировать без учета сотен взаимосвязей, неизбежно возникающих в крупных информационных системах. И если в монолитных приложениях вся система обновляется разом, то для этих целейв микросервисных приложениях будет работать конвейер CI/CD.
Про конвейер интеграции и доставки (CI/CD) ПО
Напомним, что это такое и какой эффект процессы CI/CD оказывают на бизнес.
Непрерывная интеграция (Continuous Integration, CI) — практика разработки программного обеспечения, которая заключается в постоянном слиянии рабочих копий в общую основную ветвь и выполнении частых автоматизированных сборок проекта.
Непрерывная доставка (Continuous delivery, CD) — это подход к разработке программного обеспечения, при котором ПО производится короткими итерациями, гарантируя, что оно стабильно и может быть передано в эксплуатацию в любое время, а передача его не происходит вручную.
Обычно выделяют несколько показателей, отражающих, насколько эффективно продукт может отреагировать на меняющиеся обстоятельства.
Time to Market (TTM) — ключевой показатель, который отражает временной интервал с момента фиксации идеи до момента запуска продукта на рынок. Также важными показателями являются Lead Time — это TTM, из которого исключен этап исследования и проверки гипотез, а также Cycle Time, который представляет собой только разработку, тестирование и развертывание.
Процесс CI/CD влияет на все измеряемые показатели, в том числе на наиболее важный для бизнеса TTM. Условно говоря, TTM определяет, с какой скоростью те идеи и концепции, которые мы планируем реализовывать в продукте, будут выливаться в конкретные изменения. Например, мы разрабатываем информационную систему, в которой пользователь может оформлять авиауслуги. Чем быстрее возможность оформить новый вид услуги появится в нашем продукте, тем лучше для бизнеса.
Обновления монолитных приложений
Каким же образом мы производили изменения до эпохи микросервисных приложений? Конечно, при разработке любых (монолитных или микросервисных) приложений используется система управления версиями (source control). Это место, где хранится исходный код продукта. Все современные Source Control оперируют понятием «ветки» (branches) и имеют набор инструментов для того, чтобы перенести изменения исходного кода между этими ветками. Также для разработки и развертывания используется набор сред или окружений, где та или иная версия приложения может быть развернута.
Когда у нас были большие монолитные системы, мы тщательно отслеживали версии приложений. Более того, новая версия должна была пройти определенный цикл, в том числе с точки зрения CI/CD. В последнее время стандартом такого цикла являлся gitflow. Это значит, что исходный код версии приложения сначала попадал в dev-ветку, где находился самый низкокачественный код. Обычно проверялась техническая возможность его интеграции (тот самый процесс CI), после этого он мог быть развернут (вручную или автоматически) в dev-среде. Далее, пройдя несколько итераций тестирования/исправления ошибок, код попадал в тестовую (test) ветку, откуда разворачивался в test-среду. И в конце концов код попадал в продуктовую (prod) ветку и соответствующую среду, где мог быть использован конечными пользователями.
При работе с монолитными приложениями характерным был down-time при обновлении версии. Другими словами. требуется время, чтобы продукт полностью выключить, накатить обновления, затем включить и проверить, что все работает корректно, а уже после этого пользоваться новой версией. Важной и сложной частью применения этих изменений была работа со структурой информации, хранимой в базе данных. И при обновлении версии зачастую приходилось выполнять долгие и ресурсоемкие конвертации этих баз, во время которых приложение не было доступно конечным пользователям, поэтому для обновления приложения разрабатывались сложные регламенты.
Обновления микросервисных приложений
Микросервисные приложения разделены на мелкие независимые блоки, которые могут поддерживаться небольшой командой разработчиков. Микросервисы мы реализуем в виде контейнеров на docker-платформе, и в качестве окружений (dev, test, prod) используются оркестраторы контейнеров — Kubernetes или совместимые решения.
Обновление микросервисного приложения похоже на обновление монолитного приложения с использованием оркестратора контейнеров. Вы можете обновлять версии всех микросервисов по одной, дожидаясь их загрузки и инициализации. Затем вы переключаете трафик со старых версий на новые.
Способ обновления № 1 — синхронное обновление хранилищ
В случае с микросервисами есть одна сложность: данные обычно находятся в нескольких небольших хранилищах, а не в одном крупном, как в монолитных системах. Каждый сервис использует небольшую собственную базу данных, и выходит так, что вся информация лежит в разрозненных базах. Но когда одна и та же информация содержится в разных сервисах, это нестрашно и даже удобно — никому никуда дополнительно ходить не надо. При обновлении системы нужно обновить структуру всех хранилищ синхронно, чтобы данные оставались согласованными.
Способ обновления № 2 — совместимость старых версий с новыми и плавный переход на новые
Альтернативный подход заключается в разработке новой версии микросервиса таким образом, чтобы она была совместима как со старыми, так и с новыми версиями других микросервисов и хранилищ данных. Это означает, что вы можете развертывать новую версию микросервиса постепенно, не влияя на другие микросервисы или хранилища данных.
Сравнение обновления монолитных и микросервисных приложений
При работе с монолитом есть свои сложности: например, все изменения накапливаются в одной большой версии. Это может привести к длительным циклам разработки и тестирования: пока мы тестируем новую версию, накапливаются очередные изменения, объем тестирования увеличивается, изменения растут и т. д. Таким образом, можно направлять свои усилия либо на тестирование и доработку одной большой версии монолита (либо совместной работы новых версий всех микросервисов), а можно направлять на обеспечение взаимодействия с новыми и старыми версиями других приложений. Для этого есть свои подходы, например, при разработке микросервисов — поддерживающий REST API.
Сложная для монолитных приложений задача обновления структур хранилища обычно становится проще в случае микросервиных приложений, так как само хранилище меньше, а его структура проще. Обновление сущностей в микросервисной архитектуре может быть сложным, поскольку необходимо обеспечить совместимость новой версии сущности со старыми версиями. Например, если вы измените тип атрибута в новой версии сущности, вам необходимо добавить новый столбец в хранилище данных для хранения атрибута нового типа, но при этом сохранить старый столбец для хранения атрибута старого типа до тех пор, пока все компоненты системы не перейдут на работу с новой версией сущности.
Наш опыт подсказывает, что в большинстве случаев изменения хранилищ данных могут происходить автоматически и без простоев. Это означает, что вы можете обновлять сущности в микросервисной архитектуре без необходимости вручную контролировать процесс перехода на новые версии.
Метод Features
При работе с продуктом и заинтересованными группами весь функционал может быть представлен в виде набора неких features — например, добавление возможности подгружать сканы к отчету.
Пожалуй, всегда хочется, чтобы одна feature соответствовала одному микросервису, но так бывает далеко не всегда. В том же примере с возможностью приложения сканов, мы должны:
- Разработать микросервис, в котором хранятся эти сканы.
- Доработать микросервис отчетов так, чтобы он мог хранить ссылки на эти сканы.
- Доработать микросервис пользовательского интерфейса (UI), чтобы он мог отображать соответствующие элементы управления сканами.
Все эти изменения нужно уметь включить и выключить одновременно, и нельзя сделать так, чтобы в UI элементы управления сканами появились, а микросервис сканов не работал. Для этого можно использовать так называемые feature flags — «переключатели» функциональности, которые управляют тем, с какими именно версиями микросервисов будет взаимодействовать тот или иной микросервис. Для работы с feature flags можно использовать специализированные решения, обеспечивающие совместное включение или выключение flags. Примеры таких решений есть: в частности, в нашей компании для этого используется собственная разработка — портал Feature Flag.
И если gitflow больше заточен на создание крупных монолитных систем, то для микросервисов предпочтителен другой подход, известный как FBD (feature based development). Теперь у нас нет однозначного соответствия между веткой кода и окружением, а есть множество микросервисов, каждый из которых имеет некую свою версию и способен взаимодействовать с микросервисами каких-то версий. Суть процесса состоит в том, что, разрабатывая новый код, мы не прогоняем его через систему веток, а сразу заливаем его в master-ветку, и далее он может попадать в prod-среду. При развертывании новой версии микросервиса новый код обычно находится в отключенном состоянии, то есть не будет использоваться системой, пока мы не решим, что код протестирован и готов к включению.
Это позволяет безопасно развертывать новые версии микросервисов без риска нарушения работы системы. Вы можете протестировать новый код в отключенном состоянии и убедиться, что он работает должным образом, прежде чем включать его для пользователей.
Таким образом, процесс CI/CD в микросервисных приложений как бы распадается на две части:
- Собственно CI/CD в классическом понимании, которые вносит изменения в код, но не вызывает никакого видимого эффекта для пользователей.
- Манипуляции с Feature Flags, которые, наоборот, не вносят изменения в код, но вызывают видимый для пользователя эффект.
Вообще говоря, разработка программного обеспечения — вещь невероятно гибкая. В ней допустимы практические любые сочетания. Можно делать монолитные приложения без использования автоматизированного CI/CD, можно делать микросевисы без контейнеров или вообще попытаться «запихать» монолитное приложение в контейнер. Весь вопрос в том, насколько эффективным будет такой процесс. В практике нашей компании именно комбинация микросервисной архитектуры, FBD, автоматизированных процессов CI/CD, оказалась очень эффективной именно при разработке продуктов, так как позволяет быстро протестировать идею или гипотезу на целевых или вообще на всех пользователях. Это в свою очередь делает бизнес более быстрым, гибким и конкурентным в своей отрасли.
На микросервисах развитие информационных технологий, конечно, не остановится, и наверняка будут еще более продвинутые и интересные решения. Но на данный момент это действительно хорошо зарекомендовавший себя подход к разработке сложных решений, который стал стандартом при разработке Enterprise-решений — будь то веб-, интеграционное решение или мобильное приложение.
Опубликовано 25.04.2024