Прокладываем тропинки до микросервисов

от автора

Одна из наиболее важных задач при разделении системы на микросервисы — обеспечить надежный механизм их репликации и обнаружения и создать набор правил для маршрутизации входящих запросов к соответствующим контейнерам или сетевым узлам. Идеальная система также должна уметь отслеживать состояние доступности и исключать недоступные реплики из маршрутизации. В этой статье мы поговорим об использовании маршрутизатора 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 и выполнить аналогичные настройки через веб-интерфейс:

Интерфейс Konga
Интерфейс Konga

Запустим теперь наш сервис для маршрутизации и выполним привязку его методов к префиксу /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 можно динамически изменять привязку маршрутов к экземплярам сервисов, а также решать типичные задачи контроля доступа и получения метрик по времени выполнения запросов.


Всех, кому интересна тема микросервисов, хочу пригласить на бесплатный урок по теме: «Авторизация и аутентификация в микросервисной архитектуре». Регистрация доступна по ссылке ниже.


ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/669342/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *