Как стать автором
Обновить
1056.27
OTUS
Цифровые навыки от ведущих экспертов

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

Время на прочтение8 мин
Количество просмотров10K

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


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

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 8: ↑7 и ↓1+6
Комментарии1

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS