Pull to refresh

Миграция микросервисной архитектуры на API Gateway

Level of difficultyEasy
Reading time10 min
Views14K

Всем привет! Меня зовут Тигран, я руковожу разработкой в Amediateka. Наша микросервисная архитектура была спроектирована 6 лет назад предыдущей командой разработки. С тех пор у нас добавилось много новых микросервисов, но фундаментальная часть архитектуры осталась прежней. В этом году мы решили внедрить несколько глобальных изменений, одно из которых - API Gateway. Статья расскажет о том, что это такое, какие преимущества дает, как просто и безопасно мигрировать функционирующую систему на API Gateway и сделает обзор готовых решений.

Как полагается онлайн кинотеатру, клиентских приложений больше обычного. У нас есть 6 типов клиентских приложений, не считая большую вариацию сборок в некоторых из них. Это web-приложение и нативные приложения iOS, Android, tvOS, AndroidTV и SmartTV. На стороне бэкенда несколько десятков микросервисов. Давайте посмотрим как клиентские приложения взаимодействуют с нашими микросервисами.

Взаимодействие с микросервисной архитектурой без использования API Gateway
Взаимодействие с микросервисной архитектурой без использования API Gateway

Схема пропускает взаимодействие микросервисов со своими СУБД и общение через AMQP брокера. Отображена лишь коммуникация через HTTP. 

В чем заключается глобальная проблема? Каждое клиентское приложение напрямую взаимодействует с микросервисами. На первый взгляд звучит безобидно, но на самом деле из этого вытекают следующие проблемы:

  • безопасность;

  • сложность разработки;

  • скорость разработки;

  • дублирование функционала.

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

Сложность разработки повышается из-за отсутствия единой точки входа. На практике микросервисы пишутся разными командами, в разное время и могут иметь разные конвенции проектирования API. В итоге командам клиентского приложения приходится мириться (или ругаться) с разными конвенциями проектирования API и усложнять логику на своей стороне, например разной обработкой ошибок. API Gateway (вместе с API Composition, о котором поговорим в следующем разделе) тут помогает тем, что дает возможность управлять конвенциями и стандартами в едином месте. Другая сложность заключается в том, что часто клиентским приложениям требуется собрать данные из нескольких микросервисов. Само по себе наличие такого функционала на стороне клиентского приложения не есть хорошо. Работать с композированными данными проще и быстрее, нежели собирать это все самому.

Исходя из предыдущего пункта вытекает следующий, скорость разработки. Унификация API конвенций напрямую влияет на скорость разработки и выпуск релизов. Наконец, не имея единой точки входа, мы рискуем дублировать такой функционал, как аутентификация и авторизация пользователей.

Что делать на данном этапе развития? Внедрять шаблон проектирования API Gateway.

API Gateway

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

Шаблон проектирования имеет вариацию BFF (Backends for Frontends), где каждое клиентское приложение (или его тип) обращается к своему собственному API Gateway.

Вместе с ним часто внедряется другой шаблон, API Composition. API Composition — шаблон проектирования, используемый в микросервисной архитектуре, решающий задачу получения данных из нескольких микросервисов (и их СУБД) соединяя их результат в единый ответ.

Шаблон проектирования API Composition
Шаблон проектирования API Composition

Композиция данных сама по себе является отдельной задачей и в принципе ее наличие в API Gateway нарушает принцип единственной ответственности. Не смотря на это, в этом вопросе мы солидарны с точкой зрения Криса Ричардсона, автором отличной книги про микросервисную архитектуру и приняли решение, что для нашей небольшой команды разработки будет удобнее решить эти две задачи в рамках одного микросервиса. Как Крис комментирует данный вопрос:

An API Gateway often does API composition.

Давайте посмотрим на то, как будет выглядеть архитектура с использованием API Gateway.

Взаимодействие с микросервисной архитектурой с использованием API Gateway
Взаимодействие с микросервисной архитектурой с использованием API Gateway

План миграции

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

  1. внедрить API Gateway не меняя спецификацию API

  2. внедрить API Composition

Первый этап подразумевает, что наши клиентские приложения остаются на тех же API, добавляя в путь всего-лишь префикс gateway/v1. Микросервисы перестают быть публичными, остается единая точка входа. Фактически, мы просто уносим микросервисы во внутрь не меняя спецификацию существующих API. Для решения этой задачи отлично подходит nginx/ingress.

Вот как это примерно выглядит в конфигурации nginx:

server {
    ...

    location /gateway/404.json {
        default_type application/json;
        return 404 '{"message": "Not Found"}';
    }

    location /gateway/v1/ {
        error_page 404 /gateway/404.json;
        rewrite ^/gateway/v1(/.*) $1 break;

        access_log /var/log/nginx/gateway.access.log proxyed buffer=32k flush=5s;
        error_log /var/log/nginx/gateway.error.log;

        location /gateway/v1/microservice1/ {        
            access_log /var/log/nginx/gateway.microservice1.access.log proxyed buffer=32k flush=5s;        
            error_log /var/log/nginx/gateway.microservice1.error.log;

            location /gateway/v1/microservice1/method1/ {             
                rewrite ^/gateway/v1(/.) $1 break;             
        
                proxy_set_header Host $host;             
                proxy_pass http://microservice1;       
            }         

            location /gateway/v1/microservice1/method2/ {                        
                rewrite ^/gateway/v1(/.) $1 break;

                proxy_set_header Host $host;                        
                proxy_pass http://microservice1;              
            }        

            # Other routes    
        }    

       # Other microservices
    }

    location / {
        error_page 404 /gateway/404.json;
        rewrite ^(/.*) /gateway/v1$1;
    }
}

Тут стоит обратить внимание на несколько интересных моментов. Во первых, мы определили общий 404 ответ в случае отсутствия запрашиваемого ресурса. Во вторых, мы четко определили публичный интерфейс через API Gateway. Во третьих, строка rewrite ^/gateway/v1(/.*) $1 break; играет ключевую роль: она убирает gateway/v1 из пути перед проксированием в сам микросервис. Микросервисы не знают и не должны знать про API Gateway. Такой подход позволяет мигрировать без особых изменений микросервисов.

Второй этап подразумевает, что разворачивается уже полноценный микросервис (собственное или готовое решение) под префиксом gateway/v2, реализующее все функции API Gateway и API Composition, где проектируются новые, удобные API методы с композицией данных.

Вот как выглядит процесс в схематичном виде.

Схема реализации API Gateway v1 и v2
Схема реализации API Gateway v1 и v2

Миграция: Этап 1

В отличие от второго этапа, где разворачивается новый микросервис и проектируются новые API методы, первый этап — это про миграцию существующей архитектуры.  

Давайте рассмотрим варианты миграции.

Жесткая миграция

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

Такое решение сработает, если имеется только web-приложение. Подход никуда не годится, если есть клиентские приложения с их разными версиями, на которых всегда будет какая-то аудитория. 

Нам это решение не подходит.

Миграция с помощью Feature Toggle

Feature Toggle (далее фича-тоггл) — это метод разработки программного обеспечения, позволяющий командам контролировать доступность определенных функций и возможностей в приложении без явного внесения изменений в код. Простыми словами, это переменная с булевым значением, которая решает, включать функционал или нет. Мы для этих целей используем Unleash.

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

Рассмотрим пример iOS приложения, с использованием Unleash:

curl --location 'https://unleash.client/api/client/features/ios-use-direct-endpoints' \
--header 'Content-Type: application/json' \
--data '{
  “enabled”: true,   
  “name”: “ios-use-direct-api-endpoints” 
}'

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

Этот вариант позволит “унести” больше пользователей на новую точку входа, но все равно останется та аудитория пользователей, которая никогда не обновляет приложение по той или иной причине. 

Это неплохое решение, но нам оно все равно не подходит.

Миграция с помощью Feature Toggle и жесткое обновление приложения

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

Рассмотрим пример реализации на том же iOS приложении:

curl --location 'https://unleash.client/api/client/features/ios-update-app' \
--header 'Content-Type: application/json' \
--data '{
  "enable": true,    
  "name": "ios-update-app",    
  "variants": [        
    {            
      "name": "settings",           
      "payload": {               
        "type": "json",               
        "value": "{"version":"4.20.0", "title":"Обновление приложения","message" :"Для правильной работы необходимо обновить приложение Амедиатека в App Store","action" :"Обновить приложение","url" :"https: //apps.apple.com/app/id695094274"}"            
      }      
    }     
  ]  
}'

Фича-тоггл возвращает мета-информацию для рендера страницы и самое главное - версия, на которую требуется обновиться. Клиентское приложение в свою очередь проверяет статус фича-тоггла и сравнивает свою версию приложения с пришедшей версией и показывает экран обновления, закрыть который невозможно. Если же у вас нет такого механизма, можно реализовать переадресацию со старых endpoint-ов на API Gateway.

Миграция: Этап 2

Как ранее упоминалось, второй этап подразумевает, что разворачивается уже полноценный микросервис (готовое или собственное решение) под префиксом gateway/v2, реализующее все функции API Gateway и API Composition, где проектируются новые, удобные API методы с композицией данных. Сегодня есть настолько много разных вариантов готовых решений, опенсорсных, облачных, что мне сложно представить ситуацию, где нужно разрабатывать собственное решение.

Конечно же, в первую очередь стоит изучить готовые решения. Для нас было важно наличие on-premise решения, поэтому статья не рассматривает облачные решения. Тем не менее, на Хабре есть хорошая статья, рассматривающая облачные решения. Нашими основными критериями выбора являлись:

  • open source on-premise решение

  • поддержка API Composition

  • возможность писать свои плагины

Все нижеприведенные решения в той или иной степени имеют все функции API Gateway. Я остановлюсь на преимуществах и недостатках, которые на мой взгляд интересны.

Kong Gateway

https://konghq.com/products/kong-gateway

Kong Gateway —  это open source, cloud-native решение, написанное на Lua. По сути, это надстройка над Nginx. Kong Gateway поддерживает DB-less декларативную конфигурацию, используя только in-memory хранилище и собственные Kubernative CRD.

Одно из самых популярных open source решений. Есть богатая библиотека плагинов Plugin Hub, содержащая более 90 плагинов, за счет чего и имеется самое большое количество интеграций с различными внешними сервисами. Есть возможность писать кастомные плагины на Go и Lua. Есть поддержка GraphQL из коробки.

Тем не менее, Kong Gateway не умеет выполнять композицию данных из коробки. Есть старый, непопулярный сторонний плагин, который не поддерживается.

Apinto

https://github.com/eolinker/apinto

Apinto (в прошлом Goku) — это open source решение, написанное на Go. Нет библиотеки плагинов и нет возможности писать свои собственные. Авторы выделяют производительность как ключевое преимущество. В статье  (на китайском) на своем блоге, они сравнивают свое решение с Kong Gateway и Nginx.

Производительность Apinto в сравнении с Nginx и Kong GatewayИсточник: apinto.com
Производительность Apinto в сравнении с Nginx и Kong Gateway
Источник: apinto.com

Gravitee.io

https://www.gravitee.io

Gravitee.io —  это open source решение, написанное на Java. Есть своя библиотека плагинов и возможность писать свои собственные на Java. Как таковая композиция данных из коробки отсутствует. Авторы советуют делать это через callout policies, что выглядит неявно и неудобно.

KrakenD

https://www.krakend.io

KrakenD —  это open source решение, написанное на Go. Нет библиотеки плагинов, но есть возможность писать свои сообственные на Go. Есть поддержка GraphQL из коробки.

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

Композиция данных есть из коробки, описывая правила в виде конфигурации. Можно написать свои собственные плагины на Go, чтобы расширить функционал. Ребята из МТС Travel уже прошли этот путь и написали недостающие плагины.

Отдельно KrakenD делает акцент на производительность за счет своей stateless архитектуры и является самым быстрым по сравнению с предыдущими кандидатами. 

Производительность KrakenD по сравнению с Kong и Tyk. Запросы в секунду в равных условиях. Источник: kraken.io
Производительность KrakenD по сравнению с Kong и Tyk. Запросы в секунду в равных условиях. Источник: kraken.io

Tyk

https://tyk.io

Tyk —  это open source, cloud-native решение, написанное на Go. Есть своя библиотека плагинов и возможность писать свои собственные на Go и даже Middleware на JavaScript. Композиция данных есть из коробки с помощью API Compositon.

Gloo Edge

https://docs.solo.io/gloo-edge/latest/

Gloo Edge —  это open source решение, написанное на Go. Есть своя библиотека плагинов. Возможности писать свои собственные плагины нет, но можно добавлять свою авторизацию. Композиции данных нет.

Одна из интересных особенностей Gloo Edge — маршрутизация запросов непосредственно к функциям. Запрос к функции может представлять собой вызов serverless функции (например, Lambda, Google Cloud, OpenFaaS), вызов API микросервиса (REST API, XML/SOAP) или публикацию в очереди сообщений (например, NATS, AMQP). Эта делает Gloo Gateway единственным шлюзом API, который поддерживает гибридные приложения, а также единственным, который не привязывает пользователя к определенной парадигме.

Apache APISIX

https://apisix.apache.org/

Apache APISIX —  это open source, cloud-native решение, написанное на Lua. Есть поддержка GraphQL из коробки. Есть своя большая библиотека плагинов Plugin Hub и возможность свои собственные. Композиции данных нет.

В open source сообществе есть и другие, менее популярные решения, такие как Fusio, Apiman, API Umbrella. Все они поддерживают основные функции API Gateway.

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

Бенчмарки

Итоги

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

Tags:
Hubs:
Total votes 15: ↑14 and ↓1+14
Comments2

Articles