
Одна из наиболее важных задач при разделении системы на микросервисы - обеспечить надежный механизм их репликации и обнаружения и создать набор правил для маршрутизации входящих запросов к соответствующим контейнерам или сетевым узлам. Идеальная система также должна уметь отслеживать состояние доступности и исключать недоступные реплики из маршрутизации. В этой статье мы поговорим об использовании маршрутизатора Kong, который принимает на себя не только задачи умной маршрутизации, но и возможности по протоколированию и трансформации запросов, контролю доступа, мониторингу запросов, а также может быть расширен с использованием плагинов.
Маршрутизатор Kong Gateway существует в свободном и коммерческом варианте, может быть запущен как самостоятельное приложение (например, через docker-контейнер kong), либо установлен в Kubernetes (в этом случае он представляется как специализированный Ingress-контроллер). Для управления Kong можно использовать как запросы к API, так и веб-интерфейсы Kong Manager или Konga. Многие расширения предустановлены в официальном контейнере, но они также могут быть найдены в Kong Plugin Hub. Мы с вами рассмотрим установку и настройку на примере простого приложения, состоящего из трех микросервисов (один из которых представляет публичный интерфейс без авторизации, для одного мы будем задавать сложные правила маршрутизации, а третий будет требовать обязательное использование токена для доступа к точкам подключения).
Для начала составим топологию нашей системы и выделим микросервисы:
сервис поиска местоположения и расширенной информации о городе по стране и названию (используется приложение Chercheville на Elixir , доступное на Docker Hub);
сервис для получения информации из профиля авторизованного пользователя;
сервис для выполнения авторизации и получения токена.
Каждый сервис будет иметь собственную базу данных (или сторонний источник данных) и все взаимодействие между системами будет выполняться через шлюз API, который мы и будем настраивать с использованием Kong API Gateway.
Kong использует для сохранения своей конфигурации базу данных PostgreSQL, либо может быть запущен с определением сервисов и привязок к json/yaml-файле. Мы будем рассматривать вариант запуска с базой данных. Для координации запуска будем использовать Docker Compose, который можно найти в официальном репозитории: https://github.com/Kong/docker-kong (путь /compose).
docker compose --profile database up -d
После выполнения первоначальных миграций базы данных запускается основной процесс, который начинает прослушать несколько TCP-портов:
8000/8443 - HTTP/HTTPS-трафик для маршрутизации к серверам;
8001/8444 - HTTP/HTTPS порты для отправки административных запросов (также используется веб-интерфейсом Konga для управления ресурсами);
8002/8445 - HTTP/HTTPS подключение к Kong Manager (веб-интерфейс), должен быть дополнительно разрешен и опубликован через docker-compose.yaml.
Перед настройкой маршрутов нужно разобраться с используемыми терминами в Kong:
Service - конфигурация backend-сервиса (протокол http/https, адрес хоста, порт, количество попыток подключения и таймауты, идентификатор клиентского сертификата при использовании https). К конфигурации сервиса могут быть подключены плагины для аудита/трансформации запросов, а также определены допустимые потребители (consumer).
Route - правило маршрутизации входящих запросов, включает в себя перечисления связанных сервисов (может быть больше одного для отказоустойчивости) и группу фильтров (для http-протоколов) или набор правил для tcp-stream маршрутизации. К каждому маршруту может быть также добавлен плагин для обработки запросов.
название домена (Hosts);
префиксы пути (Paths);
HTTP-методы (Methods);
протоколы (http / https);
проверка доменов для tcp-stream маршрутизации (SNI);
источники запроса для tcp-stream (sources);
получатели запроса для tcp-stream (destinations).
Certificate - зарегистрированные клиентские сертификаты для подключения к https backend-сервисам (для каждого формируется уникальный id, который используется при конфигурации сервиса).
Upstream - правило пересылки http/https трафика на один или несколько сервисов. После создания и настройки upstream появляется возможность добавить targets (ip/dns-адреса целевых серверов, порты и значение веса, которое влияет на вероятность выбора сервера при использовании хэш-правила none), а также Alert для отправки уведомлений администраторам по почте или в Slack при недоступности целевого сервера:
Hash on - метод хэширования для выбора целевого сервиса из пула (может быть consumer, ip, cookie, header, none). В случае none используется последовательный выбор сервисов для равномерного распределения;
Hash fallback - альтернативный метод хэширования, если основной не вернул результат (например, отсутствовал cookie или заголовок);
Active Health Check - конфигурация проверки доступности (http-метод, путь, коды успешных и неуспешных ответов, количество попыток проверки, интервал проверки);
Consumer - зарегистрированные авторизованные пользователи сервисов и метод их авторизации. Для Consumer определяется набор Credentials - это может быть Basic Auth (имя и пароль), API Key, Hash-based message authentication code (HMAC) - имя и секрет, OAuth2 (id и секрет приложения, redirect uri), JSON Web Token (JWT).
Для управления ресурсами можно использовать http-запросы к порту администрирования 8001 (или 8444 для https-запросов):
http://ip:8001/services- просмотр списка/регистрация нового сервиса;http://ip:8001/routes- просмотр/создание маршрута;http://ip:8001/consumers- управление авторизованными пользователями;http://ip:8001/upstreams- изменение правил проксирования с проверкой доступности (upstream);http://ip:8001/certificates- управление клиентскими сертификатами;http://ip:8001- просмотр информации о запущенном экземпляре kong.
Для удобства доступа можно создать маршрут к admin api через основной порт (8000):
curl -X POST http://127.0.0.1:8001/services \ --data name=admin-api \ --data host=127.0.0.1 \ --data port=8001 curl -X POST http://127.0.0.1:8001/services/admin-api/routes \ --data paths[]=/admin-api
И далее будет возможно получить доступ к сервису администрирования через http://localhost:8000/admin-api/. Наличие API для администрирования позволяет выполнить саморегистрацию сервиса и маршрута при запуске, но сейчас у нас доступ к api выполняется без авторизации, что нельзя назвать безопасным решением. Создадим новый consumer с APIKey и назначим его на доступ к маршруту api:
curl -i -X POST \ --url http://localhost:8001/services/admin-api/plugins/ \ --data 'name=key-auth' curl -i -X POST \ --url http://localhost:8000/admin-api/consumers/ \ --data "username=admin" curl -i -X POST \ --url http://localhost:8000/admin-api/consumers/admin/key-auth/ \ --data 'key=6Y6fKCsFWWWSRMXGyUWs8g9q'
Теперь доступ к API будет происходить только с использованием заголовка apikey: 6Y6fKCsFWWWSRMXGyUWs8g9q.
Кроме плагинов авторизации, можно добавить следующие расширения:
session- отслеживание сессии между запросами (чаще всего с использованием cookie);bot detection- обнаружение активности ботов;cors- управление политикой cors;ip restriction- ограничение доступа по белым и черным спискам;acme- интеграция с letsencrypt для автоматического обновления сертификатов;rate limiting- ограничение количества запросов в секунду;response ratelimiting- ограничение количества ответов в секунду;request size limiting- ограничение размера запроса;proxy cache- кэширование ответа;pre function- запустить lua-функцию перед обработкой запроса;post function- запустить lua-функцию после обработки запроса;aws lambda- запустить внешнюю функцию с AWS при обработке правила;azure functions- запустить Azure-функцию при обработке правила;datadog- отправить метрики запросов в datadog;prometheus- отправить метрики по правилам в prometheus;zipkin- отправка меток по обработке правил для распределенной трассировки zipkin;request transformer- изменение запроса перед передачей на upstream-сервер;response transformer- изменение ответа upstream-сервера;correlation ID- отслежи��ание связи в цепочке пересылок;tcp/udp/http log- отправка информации о запросе и ответе на сервер журналирования через tcp/udp/http;file log- запись информации о запросе/ответе в файл;syslog- отправка запроса и ответа в системный процесс syslog;statsd- отправка статистики по обработке запроса в statsd;loggly- пересылка информации о запросе/ответе в loggly.
При необходимости создания собственных расширений можно использовать Python Developent Kit.
Для управления также можно установить konga для настройки всех ресурсов через веб-интерфейс:
docker network create konga docker rm -f konga-db docker run -d --network=konga --name konga-db -v /data/mongo:/data/db mongo docker rm -f konga2 docker run -d --network=konga --name konga -e NODE_ENV=production -e "TOKEN_STRING=fdoER#2sa" -e DB_ADAPTER=mongo -e DB_HOST=konga-db -e DB_DATABASE=konga --network=konga -p 443:1337 -v /data/konga:/data -e SSL_KEY_PATH=/data/cert.key -e SSL_CRT_PATH=/data/cert.crt pantsel/konga
При выполнении первого подключения нужно будет сконфигурировать адрес Kong API и далее можно будет подключиться к https и выполнить аналогичные настройки через веб-интерфейс:

Запустим теперь наш сервис для маршрутизации и выполним привязку его методов к префиксу /place. Для этого клонируем репозиторий и запустим docker compose up -d. После запуска будет необходимо выполнить импорт данных по странам:
docker exec -ti chercheville-app-1 sh ./bin/chercheville rpc 'ChercheVille.SeedData.import_data(["FR"])'
Для проверки доступности сервиса выполним запрос http://localhost:5000/cities/?q=Paris. Теперь выполним связывание сервиса и маршрута через konga:

Здесь Host - внешний адрес сервера (альтернативно можно использовать DNS-имя во внутренней сети или через используемый Service Discovery, например Consul). Дальше необходимо настроить привязку сервиса к внешнему маршруту:

Strip Path позволяет трансформировать адрес и убрать префикс /places при пересылке запроса. Теперь сервис будем доступен через адрес http://localhost:8000/places/ и можно будет выполнить запрос между микросервисами или от внешнего клиента через API Gateway.
Следующим шагом выполним саморегистрацию сервиса авторизации в kong, который также будет управлять Consumer для доступа к сервису профиля:
import requests import socket from flask import Flask gateway = "http://localhost:8000" kong_api = f"{gateway}/admin-api" headers = { "apikey": "6Y6fKCsFWWWSRMXGyUWs8g9q" } hostname = socket.gethostname() local_ip = socket.gethostbyname(hostname) # саморегистрация сервиса requests.post(f"{kong_api}/services", headers=headers, data={ "name": "authorizer", "url": "http://{local_ip}:10080" }) requests.post(f"{kong_api}/services/authorizer/routes", data={ "paths[]=/auth" }) app = Flask(__name__) @app.route("/login") def login(): # extract and check login/password login = "login" requests.post(f"{kong_api}/consumers", data={"username": login}) # регистрируем consumer @app.route("/logout") def logout(): requests.delete(f"{kong_api}/consumers/{login}") app.run(port=10080)
При регистрации сервиса profile необходимо добавить плагин key-auth для проверки токена при обращении к адресам profile. При авторизации будет добавлен заголовок X-Consumer-ID для хранения идентификатора, который соответствует представленному токену.
import requests import socket import json import flask gateway = "http://localhost:8000" kong_api = f"{gateway}/admin-api" headers = { "apikey": "6Y6fKCsFWWWSRMXGyUWs8g9q" } hostname = socket.gethostname() local_ip = socket.gethostbyname(hostname) # саморегистрация сервиса requests.post(f"{kong_api}/services", headers=headers, data={ "name": "profile", "url": "http://{local_ip}:9080" }) requests.post(f"{kong_api}/services/profile/routes", data={ "paths[]=/profile" }) requests.post(f"{kong_api}/services/profile/plugins", data={ "name=key-auth" }) app = flask.Flask(__name__) @app.route("/profile") def login(): consumers = json.loads(requests.get(f"{kong_api}/consumers")) # определение имени (идентификатора) пользователя # список содержит id (consumer id) и имя пользователя (name) id = flask.request.headers.get("X-Consumer-ID") username = None for c in consumers: if c["id"]==id: username = c["username"] if not username: flask.abort(403, "Not authorized") return f"Logged user: {username}" app.run(port=9080)
Аналогично можно настроить удаление сервисов и маршрутов при завершении приложения. Таким образом с использованием API-шлюза Kong можно динамически изменять привязку маршрутов к экземплярам сервисов, а также решать типичные задачи контроля доступа и получения метрик по времени выполнения запросов.
Всех, кому интересна тема микросервисов, хочу пригласить на бесплатный урок по теме: "Авторизация и аутентификация в микросервисной архитектуре". Регистрация доступна по ссылке ниже.
