Преобразование монолитных приложений в микросервисы

Serverless архитектура (бессерверная архитектура) позволяет по мере необходимости внедрять серверные веб-сервисы. Вместо того чтобы поддерживать собственную конфигурацию сервера, вы свести к минимуму связанные с этим накладные расходы, разрабатывая программное обеспечение с поддержкой serverless проводников. Serverless приложения обычно развертываются из репозитория Git в среду, которая может увеличиваться или уменьшаться при необходимости.

Обычно serverless развертывания включают микросервисы. Микросервисы структурируют приложение как набор слабо связанных сервисов, которые можно развертывать независимо друг от друга, а также независимо обслуживать и тестировать. Архитектура, основанная на микросервисах, предшествовала serverless развертываниям, но они естественным образом сочетаются друг с другом. Микросервисы можно использовать в любом контексте, который позволяет развертывать их независимо и управлять центральным процессом или сервером задач. Serverless архитектура исключает центральное управление процессами, позволяя вам сосредоточиться на логике приложения.

Краткий обзор микросервисов

Реструктуризация (или рефакторинг) монолитного приложения часто подразумевает несущественные изменения структуры. Если вы планируете значительно переписать логику приложения, не добавляя при этом новых функций, ваша цель должна состоять в том, чтобы максимально избежать перебоев в обслуживании. Для этого вам может понадобиться использовать blue-green развертывания. Внедрение микросервисов обычно также влечет за собой пошаговую замену функциональности приложения. Это требует тщательного модульного тестирования – иначе вы не можете быть уверены, что ваше приложение правильно обрабатывает неожиданные и пограничные случаи. Кроме того, такое тестирование предоставляет множество возможностей для анализа логики вашего приложения и оценки уместности замены существующих функций отдельными микросервисами.

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

Фреймворки и управление состоянием

Некоторые архитектуры лучше подходят для микросервисов, чем другие. Если логика вашего приложения содержит несколько последовательных этапов, зависящих друг от друга, абстрагировать каждый из них в отдельные микросервисы – скорее всего, плохая идея. В этом случае вам потребуется сложная архитектура контроллера, которая могла бы обрабатывать и перенаправлять все ошибки промежуточного этапа. Такое возможно с микросервисной архитектурой, которая использует инфраструктуру вроде Gearman для диспетчеризации подпроцессов, но неудобно при работе с serverless развертываниями и может все только еще сильнее усложнить.

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

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

Развертывание из Git

При работе с микросервисами следует максимально использовать принципы GitOps. Воспринимайте репозитории Git как единственный источник достоверной информации для целей развертывания. Большинство языковых менеджеров пакетов (например, pip в Python и npm в Node.js) предоставляют синтаксис для развертывания пакетов из пользовательских репозиториев Git. Его можно использовать в дополнение к стандартным функциям установки из PyPI, npmjs.com или других вышестоящих репозиториев. Таким образом, вы можете изящно комбинировать собственные разрабатываемые функции со сторонними библиотеками, не отклоняясь от передовых практик удобства обслуживания или воспроизводимости.

Конечные точки API

Каждый из микросервисов может реализовать свой собственный API. Кроме того, вы можете реализовать еще один уровень API поверх него (и повторить это необходимое количество раз) в зависимости от сложности вашего приложения. Также вы можете предоставлять вашим пользователям доступ только к API самого высокого уровня. Хотя поддержка нескольких различных маршрутов API может усложнить работу, эту сложность можно устранить с помощью хорошей документации по каждой из конечных точек API отдельных микросервисов. Взаимодействие между процессами посредством четко определенных вызовов API, таких как HTTP GET и POST, практически не накладывает дополнительных издержек и делает микросервисы более пригодными для повторного использования, чем если бы они взаимодействовали более оригинальным межпроцессным образом.

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

Миграция на микросервисы

Эффективный переход на микросервисы требует выполнения комплекса передовых методов разработки и развертывания программного обеспечения. Давайте поговорим о них.

Принципы CI/CD

При изменении архитектуры приложения на микросервисы нужно учитывать рекомендации по непрерывной интеграции и непрерывной доставке – так вы сможете постепенно заменить функции монолитной архитектуры.

Читайте также: Введение в непрерывную интеграцию, доставку и развертывание

Например, для рефакторинга производственного кода без каких-либо помех для пользователей вы можете использовать ветвление по абстракции (создание слоя абстракции в существующей реализации, через который можно параллельно построить новую реализацию). Кроме того, можно использовать декораторы (это такая функция TypeScript и Python), чтобы добавить дополнительные пути кода к уже существующим функциям. Таким образом, вы сможете постепенно переключать или откатывать функции.

Портативность

Не зря микросервисы стали популярными одновременно с фреймворками контейнеризации типа Docker. У них схожие цели и архитектурные возможности:

  • Контейнеры обеспечивают изоляцию процессов и зависимостей, что позволяет развертывать их на индивидуальной основе.
  • Контейнеры позволяют другим приложениям, работающим вместе с ними, функционировать как «черный ящик» — им не нужно обмениваться состояниями или какой-либо другой информацией, кроме ввода и вывода.
  • Реестры контейнеров, такие как Docker Hub, позволяют публиковать и использовать пользовательские зависимости взаимозаменяемо со сторонними.

Теоретически ваши микросервисы должны одинаково подходить для работы как в контейнере Docker или кластере Kubernetes, так и в serverless развертывании. На практике же у того или иного варианта могут быть существенные преимущества. Микросервисы с высокой нагрузкой на ЦП (к примеру, обработка видео) могут быть неэкономными в serverless средах, а поддержка управления и конфигурации Kubernetes требует значительных усилий. Тем не менее, разработка с учетом мобильности всегда является выгодным направлением. В зависимости от сложности вашей архитектуры вы можете поддерживать несколько сред, просто создавая соответствующие объявления метаданных .yml и Dockerfiles. Создание прототипов как для Kubernetes, так и для serverless сред может повысить общую устойчивость архитектуры.

Вообще-то, вам можно не беспокоиться о параллелизме БД и о других проблемах масштабирования хранилища внутри самих микросервисов. Любые направления оптимизации должны решаться и реализовываться непосредственно вашей базой данных, уровнем абстракции БД или поставщиком DBaaS, чтобы ваши микросервисы могли без труда выполнять любые операции CRUD. Микросервисы должны иметь возможность одновременно запрашивать и обновлять одни и те же источники данных, и ваша серверная часть БД должна поддерживать это.

Версии и совместимость

При создании критических, несовместимых с предыдущими версиями обновлений микросервисов вы должны предоставить новые конечные точки. Например, можно предоставить /my/service/v2 в дополнение к уже существующему /my/service/v1 и запланировать постепенное прекращение поддержки конечной точки /v1. Это важно, потому что производственные микросервисы, скорее всего, будут поддерживаться за пределами их первоначально предполагаемого контекста. По этой причине многие serverless провайдеры автоматически переводят конечные точки URL в /v1 при развертывании новых функций.

Пример миграции микросервиса

Реализация микросервисов в вашем приложении может заменить вложенные вызовы функций или частные методы, превратив их в отдельный автономный сервис. Возьмем, к примеру, это приложение Flask, которое выполняет запрос Google на основе пользовательского ввода в веб-форму, а затем обрабатывает результат, прежде чем вернуть его обратно пользователю:

from flask import *
from flask import render_template
from flask import Markup
from googleapiclient.discovery import build
from config import *

app = Flask(__name__)

def google_query(query, api_key, cse_id, **kwargs):
    query_service = build("customsearch", "v1", developerKey=api_key)
    query_results = query_service.cse().list(q=query, cx=cse_id, **kwargs).execute()
    return query_results['items']

def manipulate_result(input, cli=False):
    search_results = google_query(input, keys["api_key"], keys["cse_id"])
    for result in search_results:
        abc(result)
…
    return manipulated_text

@app.route('/<string:text>', methods= ["GET"])
def get_url(text):
    manipulated_text = manipulate_result(text)
    return render_template('index.html', prefill=text, value=Markup(manipulated_text))

if __name__ == "__main__":
    serve(app, host='0.0.0.0', port=5000)

Это приложение предоставляет собственную конечную точку, которая включает HTTP метод GET. Когда эта конечная точка получает текстовую строку, она вызывает функцию manage_result(), которая сначала отправляет текст в другую функцию google_query(), а затем обрабатывает его из результатов запроса, прежде чем вернуть его пользователю.

Это приложение можно преобразовать в два отдельных микросервиса, каждый из которых принимает HTTP-параметры GET в качестве входных аргументов. Первый вернет результаты запроса Google на основе некоторого ввода, используя библиотеку Python googleapiclient:

microservice_1.py

from googleapiclient.discovery import build
from config import *

def main(input_text):
    query_service = build("customsearch", "v1", developerKey=api_key)
    query_results = query_service.cse().list(q=query, cx=cse_id, **kwargs).execute()
    return query_results['items']

А второй микросервис будет извлекать соответствующие данные, чтобы вернуть их пользователям:

microservice_2.py

import requests

def main(search_string, standalone=True):
    if standalone == False:
        search_results = requests.get('https://path/to/microservice_1/v1/'+search_string).text
    else:
        search_results = search_string
    for result in search_results:
        abc(result)
…
    return manipulated_text

В этом примере microservice_2.py выполняет всю обработку ввода и вызывает microservice_1.py напрямую через HTTP post-запрос, если вы предоставили дополнительный аргумент standalone=False. При желании можно создать отдельную третью функцию для объединения этих двух микросервисов (если вы предпочитаете, чтобы они были полностью разделены, но при этом предоставляли всю функциональность с помощью одного вызова API).

Это довольно простой пример, и исходный код Flask, конечно, не представляет значительной нагрузки по обслуживанию, но возможность вообще обойтись без Flask все же имеет свои преимущества. Если вам больше не нужен собственный обработчик веб-запросов, вы можете вернуть эти результаты на статический сайт, используя среду Jamstack вместо серверной части Flask.

Итоги

В этом руководстве мы вкратце перечислили некоторые рекомендации по переносу монолитных приложений на микросервисы, а также рассмотрели краткий пример разделения приложения Flask на две отдельные конечные точки.

Tags:

Добавить комментарий