Введение в Конг

1. Вступление

Kong - это шлюз API с открытым исходным кодом и уровень управления микросервисами.

Основанная на Nginx и lua-nginx-module (в частности, OpenResty ), подключаемая архитектура Kong делает его гибким и мощным ,

2. Ключевые идеи

Прежде чем мы углубимся в примеры кода, давайте взглянем на ключевые концепции в Kong:

  • API Object - упаковывает свойства любой конечной точки HTTP

выполняет определенную задачу или оказывает некоторую услугу. Конфигурации включают в себя методы HTTP, URI конечной точки, обратный URL-адрес, который указывает на наши серверы API и будет использоваться для прокси-запросов, максимальных удалений, ограничений скорости, тайм-аутов и т. Д.

  • Consumer Object - оборачивает свойства любого, кто использует наш API

конечные точки. Он будет использоваться для отслеживания, контроля доступа и многого другого Upstream Object - ** описывает, как входящие запросы будут прокси или

балансировка нагрузки, представленная виртуальным именем хоста Целевой объект - ** представляет сервисы, реализованные и обслуживаемые

идентифицируется именем хоста (или IP-адресом) и портом. Обратите внимание, что цели каждого восходящего потока могут быть только добавлены или отключены. История целевые изменения поддерживаются вверх по течению Plugin Object - ** подключаемые функции для обогащения функциональности нашего

приложение в течение жизненного цикла запроса и ответа. Например, API функции аутентификации и ограничения скорости можно добавить, включив соответствующие плагины. Kong предоставляет очень мощные плагины в своем https://konghq.com/plugins/ API администратора - ** Конечные точки RESTful API, используемые для управления Kong

конфигурации, конечные точки, потребители, плагины и т. д.

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

ссылка:/uploads/Screen-Shot-2018-01-18-at-14.39.07-768x497.png%20768w[](источник: https://getkong.org/ )

3. Настройка

Официальная документация содержит инструкции detailed для различных сред.

4. Управление API

После локальной настройки Kong, давайте попробуем несколько мощных функций Kong, предоставив нашу простую конечную точку:

@RestController
@RequestMapping("/stock")
public class QueryController {

    @GetMapping("/{code}")
    public String getStockPrice(@PathVariable String code){
        return "BTC".equalsIgnoreCase(code) ? "10000" : "0";
    }
}

4.1. Добавление API

Далее, давайте добавим наш API запросов в Kong.

API администратора доступны через http://localhost : 8001 , поэтому все наши операции по управлению API будут выполняться с этим базовым URI:

APIObject stockAPI = new APIObject(
  "stock-api", "stock.api", "http://localhost:8080", "/");
HttpEntity<APIObject> apiEntity = new HttpEntity<>(stockAPI);
ResponseEntity<String> addAPIResp = restTemplate.postForEntity(
  "http://localhost:8001/apis", apiEntity, String.class);

assertEquals(HttpStatus.CREATED, addAPIResp.getStatusCode());

Здесь мы добавили API со следующей конфигурацией:

{
    "name": "stock-api",
    "hosts": "stock.api",
    "upstream__url": "http://localhost:8080",
    "uris": "/"
}
  • «Name» - это идентификатор API, используемый при манипулировании его

поведение ** «Хосты» будут использоваться для маршрутизации входящих запросов на данный

«Upstream url» путем сопоставления «Host» __ заголовка ** Относительные пути будут соответствовать настроенному «uris»

Если мы хотим отказаться от API или если конфигурация неверна, мы можем просто удалить ее:

restTemplate.delete("http://localhost:8001/apis/stock-api");

После добавления API они будут доступны для использования через http://localhost : 8000 :

String apiListResp = restTemplate.getForObject(
  "http://localhost:8001/apis/", String.class);

assertTrue(apiListResp.contains("stock-api"));

HttpHeaders headers = new HttpHeaders();
headers.set("Host", "stock.api");
RequestEntity<String> requestEntity = new RequestEntity<>(
  headers, HttpMethod.GET, new URI("http://localhost:8000/stock/btc"));
ResponseEntity<String> stockPriceResp
  = restTemplate.exchange(requestEntity, String.class);

assertEquals("10000", stockPriceResp.getBody());

В приведенном выше примере кода мы пытаемся запросить цену акций через API, который мы только что добавили в Kong.

Запрашивая http://localhost : 8000/stock/btc , мы получаем тот же сервис, что и прямой запрос с http://localhost : 8080/stock/btc

4.2. Добавление потребителя API

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

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

Добавить потребителя для API так же просто, как добавить API. Имя (или идентификатор) потребителя является единственным обязательным полем всех свойств потребителя:

ConsumerObject consumer = new ConsumerObject("eugenp");
HttpEntity<ConsumerObject> addConsumerEntity = new HttpEntity<>(consumer);
ResponseEntity<String> addConsumerResp = restTemplate.postForEntity(
  "http://localhost:8001/consumers/", addConsumerEntity, String.class);

assertEquals(HttpStatus.CREATED, addConsumerResp.getStatusCode());

Здесь мы добавили «eugenp» в качестве нового потребителя:

{
    "username": "eugenp"
}

4.3. Включение аутентификации

Здесь представлена ​​самая мощная функция Kong - плагины.

Теперь мы собираемся применить плагин авторизации к нашему прокси API-интерфейсу запросов на акции:

PluginObject authPlugin = new PluginObject("key-auth");
ResponseEntity<String> enableAuthResp = restTemplate.postForEntity(
  "http://localhost:8001/apis/stock-api/plugins",
  new HttpEntity<>(authPlugin),
  String.class);
assertEquals(HttpStatus.CREATED, enableAuthResp.getStatusCode());

Если мы попытаемся запросить цену акции через прокси-URI, запрос будет отклонен:

HttpHeaders headers = new HttpHeaders();
headers.set("Host", "stock.api");
RequestEntity<String> requestEntity = new RequestEntity<>(
  headers, HttpMethod.GET, new URI("http://localhost:8000/stock/btc"));
ResponseEntity<String> stockPriceResp = restTemplate
  .exchange(requestEntity, String.class);

assertEquals(HttpStatus.UNAUTHORIZED, stockPriceResp.getStatusCode());

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

String consumerKey = "eugenp.pass";
KeyAuthObject keyAuth = new KeyAuthObject(consumerKey);
ResponseEntity<String> keyAuthResp = restTemplate.postForEntity(
  "http://localhost:8001/consumers/eugenp/key-auth",
  new HttpEntity<>(keyAuth),
  String.class);
assertTrue(HttpStatus.CREATED == keyAuthResp.getStatusCode());

Тогда Eugen может использовать этот API, как и раньше:

HttpHeaders headers = new HttpHeaders();
headers.set("Host", "stock.api");
headers.set("apikey", consumerKey);
RequestEntity<String> requestEntity = new RequestEntity<>(
  headers,
  HttpMethod.GET,
  new URI("http://localhost:8000/stock/btc"));
ResponseEntity<String> stockPriceResp = restTemplate
  .exchange(requestEntity, String.class);

assertEquals("10000", stockPriceResp.getBody());

5. Расширенные возможности

Помимо основного прокси-сервера API и управления, Kong также поддерживает балансировку нагрузки API, кластеризацию, проверку работоспособности, мониторинг и т. Д.

В этом разделе мы рассмотрим, как загружать запросы баланса с Kong и как защищать API-интерфейсы администратора.

5.1. Балансировки нагрузки

Kong предоставляет две стратегии запросов балансировки нагрузки для внутренних служб: динамический балансировщик кольца и простой метод на основе DNS. Для простоты мы будем использовать балансировщик колец .

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

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

Во-первых, давайте подготовим верхний поток:

UpstreamObject upstream = new UpstreamObject("stock.api.service");
ResponseEntity<String> addUpstreamResp = restTemplate.postForEntity(
  "http://localhost:8001/upstreams",
  new HttpEntity<>(upstream),
  String.class);

assertEquals(HttpStatus.CREATED, addUpstreamResp.getStatusCode());

Затем добавьте две цели для апстрима, тестовую версию с weight = 10 и версию выпуска с weight = 40

TargetObject testTarget = new TargetObject("localhost:8080", 10);
ResponseEntity<String> addTargetResp = restTemplate.postForEntity(
  "http://localhost:8001/upstreams/stock.api.service/targets",
  new HttpEntity<>(testTarget),
  String.class);

assertEquals(HttpStatus.CREATED, ddTargetResp.getStatusCode());

TargetObject releaseTarget = new TargetObject("localhost:9090",40);
addTargetResp = restTemplate.postForEntity(
  "http://localhost:8001/upstreams/stock.api.service/targets",
  new HttpEntity<>(releaseTarget),
  String.class);

assertEquals(HttpStatus.CREATED, addTargetResp.getStatusCode());

С приведенной выше конфигурацией мы можем предположить, что 1/5 запросов пойдет на тестовую версию, а 4/5 - на релизную:

APIObject stockAPI = new APIObject(
  "balanced-stock-api",
  "balanced.stock.api",
  "http://stock.api.service",
  "/");
HttpEntity<APIObject> apiEntity = new HttpEntity<>(stockAPI);
ResponseEntity<String> addAPIResp = restTemplate.postForEntity(
  "http://localhost:8001/apis", apiEntity, String.class);

assertEquals(HttpStatus.CREATED, addAPIResp.getStatusCode());

HttpHeaders headers = new HttpHeaders();
headers.set("Host", "balanced.stock.api");
for(int i = 0; i < 1000; i++) {
    RequestEntity<String> requestEntity = new RequestEntity<>(
      headers, HttpMethod.GET, new URI("http://localhost:8000/stock/btc"));
    ResponseEntity<String> stockPriceResp
     = restTemplate.exchange(requestEntity, String.class);

    assertEquals("10000", stockPriceResp.getBody());
}

int releaseCount = restTemplate.getForObject(
  "http://localhost:9090/stock/reqcount", Integer.class);
int testCount = restTemplate.getForObject(
  "http://localhost:8080/stock/reqcount", Integer.class);

assertTrue(Math.round(releaseCount **  1.0/testCount) == 4);

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

5.2. Защита Admin API

По умолчанию Kong принимает запросы администратора только от локального интерфейса, что в большинстве случаев является достаточно хорошим ограничением. Но если мы хотим управлять им через другие сетевые интерфейсы, мы можем изменить значение admin listen в kong.conf__ и настроить правила брандмауэра.

Или мы можем заставить Kong служить прокси для самого Admin API. Скажем, мы хотим управлять API с помощью пути «/admin-api», мы можем добавить такой API:

APIObject stockAPI = new APIObject(
  "admin-api",
  "admin.api",
  "http://localhost:8001",
  "/admin-api");
HttpEntity<APIObject> apiEntity = new HttpEntity<>(stockAPI);
ResponseEntity<String> addAPIResp = restTemplate.postForEntity(
  "http://localhost:8001/apis",
  apiEntity,
  String.class);

assertEquals(HttpStatus.CREATED, addAPIResp.getStatusCode());

Теперь мы можем использовать прокси администрирующий API для управления API:

HttpHeaders headers = new HttpHeaders();
headers.set("Host", "admin.api");
APIObject baeldungAPI = new APIObject(
  "baeldung-api",
  "baeldung.com",
  "http://ww.baeldung.com",
  "/");
RequestEntity<APIObject> requestEntity = new RequestEntity<>(
  baeldungAPI,
  headers,
  HttpMethod.POST,
  new URI("http://localhost:8000/admin-api/apis"));
ResponseEntity<String> addAPIResp = restTemplate
  .exchange(requestEntity, String.class);

assertEquals(HttpStatus.CREATED, addAPIResp.getStatusCode());

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

6. Резюме

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

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

Как всегда, полную реализацию можно найти over на Github