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

1. Въведение

Kong е API с отворен код шлюз и слой за управление на микроуслуги.

Въз основа на Nginx и lua-nginx-модула (по-специално OpenResty), plug-in архитектурата на Kong го прави гъвкав и мощен.

2. Основни понятия

Преди да се потопим в примерни кодове, нека да разгледаме ключовите понятия в Конг:

  • API обект - обгръща свойства на всяка HTTP (s) крайна точка, която изпълнява определена задача или предоставя някаква услуга. Конфигурациите включват HTTP методи, URI на крайни точки, URL адрес нагоре по веригата, който сочи към нашите API сървъри и ще се използва за заявки за прокси, максимално оттегляне, ограничения на скоростта, изчаквания и т.н.
  • Consumer Object - обгръща свойствата на всеки, който използва нашите крайни точки на API. Той ще се използва за проследяване, контрол на достъпа и др
  • Upstream Object - описва как входящите заявки ще бъдат прокси или балансирани натоварване, представени от виртуално име на хост
  • Целеви обект - представлява услугите, които са внедрени и обслужвани, идентифицирани с име на хост (или IP адрес) и порт. Имайте предвид, че целите на всеки нагоре по веригата могат да се добавят или деактивират само. Историята на промените в целите се поддържа от горния поток
  • Plugin Object - добавящи се функции за обогатяване на функционалностите на нашето приложение по време на жизнения цикъл на заявката и отговора. Например могат да се добавят API удостоверяване и функции за ограничаване на скоростта, като се активират съответните приставки. Kong предлага много мощни приставки в своята галерия с приставки
  • Admin API - RESTful крайни точки на API, използвани за управление на конфигурации на Kong, крайни точки, потребители, приставки и т.н.

На снимката по-долу е показано как Kong се различава от наследствена архитектура, което може да ни помогне да разберем защо е въвел тези понятия:

(източник: //getkong.org/)

3. Настройка

Официалната документация предоставя подробни инструкции за различни среди.

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 са достъпни чрез // localhost: 8001 , така че всички наши операции по управление на API ще се извършват с този основен URI:

APIObject stockAPI = new APIObject( "stock-api", "stock.api", "//localhost:8080", "/"); HttpEntity apiEntity = new HttpEntity(stockAPI); ResponseEntity addAPIResp = restTemplate.postForEntity( "//localhost:8001/apis", apiEntity, String.class); assertEquals(HttpStatus.CREATED, addAPIResp.getStatusCode());

Тук добавихме API със следната конфигурация:

{ "name": "stock-api", "hosts": "stock.api", "upstream_url": "//localhost:8080", "uris": "/" }
  • “Name” е идентификатор за API, използван при манипулиране на поведението му
  • “Hosts” ще се използва за маршрутизиране на входящите заявки към даден “upstream_url” чрез съвпадение на заглавката “Host”
  • Относителните пътища ще бъдат съпоставени с конфигурирания „uris“

В случай, че искаме да оттеглим API или конфигурацията е грешна, можем просто да го премахнем:

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

След добавяне на API, те ще бъдат достъпни за консумация чрез // localhost: 8000 :

String apiListResp = restTemplate.getForObject( "//localhost:8001/apis/", String.class); assertTrue(apiListResp.contains("stock-api")); HttpHeaders headers = new HttpHeaders(); headers.set("Host", "stock.api"); RequestEntity requestEntity = new RequestEntity( headers, HttpMethod.GET, new URI("//localhost:8000/stock/btc")); ResponseEntity stockPriceResp = restTemplate.exchange(requestEntity, String.class); assertEquals("10000", stockPriceResp.getBody());

В примера на кода по-горе се опитваме да потърсим цената на акциите чрез API, който току-що добавихме към Kong.

Като заявим // localhost: 8000 / stock / btc , получаваме същата услуга като заявка директно от // localhost: 8080 / stock / btc .

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

Нека сега поговорим за сигурността - по-точно удостоверяване за потребителите, които имат достъп до нашия API.

Let's add a consumer to our stock query API so that we can enable the authentication feature later.

To add a consumer for an API is just as simple as adding an API. The consumer's name (or id) is the only required field of all consumer's properties:

ConsumerObject consumer = new ConsumerObject("eugenp"); HttpEntity addConsumerEntity = new HttpEntity(consumer); ResponseEntity addConsumerResp = restTemplate.postForEntity( "//localhost:8001/consumers/", addConsumerEntity, String.class); assertEquals(HttpStatus.CREATED, addConsumerResp.getStatusCode());

Here we added “eugenp” as a new consumer:

{ "username": "eugenp" }

4.3. Enabling Authentication

Here comes the most powerful feature of Kong, plugins.

Now we're going to apply an auth plugin to our proxied stock query API:

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

If we try to query a stock's price through the proxy URI, the request will be rejected:

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

Remember that Eugen is one of our API consumers, so we should allow him to use this API by adding an authentication key:

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

Then Eugen can use this API as before:

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

5. Advanced Features

Aside from basic API proxy and management, Kong also supports API load-balancing, clustering, health checking, and monitoring, etc.

In this section, we're going to take a look at how to load balance requests with Kong, and how to secure admin APIs.

5.1. Load Balancing

Kong provides two strategies of load balancing requests to backend services: a dynamic ring-balancer, and a straightforward DNS-based method. For the sake of simplicity, we'll be using the ring-balancer.

As we mentioned earlier, upstreams are used for load-balancing, and each upstream can have multiple targets.

Kong supports both weighted-round-robin and hash-based balancing algorithms. By default, the weighted-round-robin scheme is used – where requests are delivered to each target according to their weight.

First, let's prepare the upstream:

UpstreamObject upstream = new UpstreamObject("stock.api.service"); ResponseEntity addUpstreamResp = restTemplate.postForEntity( "//localhost:8001/upstreams", new HttpEntity(upstream), String.class); assertEquals(HttpStatus.CREATED, addUpstreamResp.getStatusCode());

Then, add two targets for the upstream, a test version with weight=10, and a release version with weight=40:

TargetObject testTarget = new TargetObject("localhost:8080", 10); ResponseEntity addTargetResp = restTemplate.postForEntity( "//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( "//localhost:8001/upstreams/stock.api.service/targets", new HttpEntity(releaseTarget), String.class); assertEquals(HttpStatus.CREATED, addTargetResp.getStatusCode());

With the configuration above, we can assume that 1/5 of the requests will go to test version and 4/5 will go to release version:

APIObject stockAPI = new APIObject( "balanced-stock-api", "balanced.stock.api", "//stock.api.service", "/"); HttpEntity apiEntity = new HttpEntity(stockAPI); ResponseEntity addAPIResp = restTemplate.postForEntity( "//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 requestEntity = new RequestEntity( headers, HttpMethod.GET, new URI("//localhost:8000/stock/btc")); ResponseEntity stockPriceResp = restTemplate.exchange(requestEntity, String.class); assertEquals("10000", stockPriceResp.getBody()); } int releaseCount = restTemplate.getForObject( "//localhost:9090/stock/reqcount", Integer.class); int testCount = restTemplate.getForObject( "//localhost:8080/stock/reqcount", Integer.class); assertTrue(Math.round(releaseCount * 1.0 / testCount) == 4);

Note that weighted-round-robin scheme balances requests to backend services approximately to the weight ratio, so only an approximation of the ratio can be verified, reflected in the last line of above code.

5.2. Securing the Admin API

By default, Kong only accepts admin requests from the local interface, which is a good enough restriction in most cases. But if we want to manage it via other network interfaces, we can change the admin_listen value in kong.conf, and configure firewall rules.

Or, we can make Kong serve as a proxy for the Admin API itself. Say we want to manage APIs with path “/admin-api”, we can add an API like this:

APIObject stockAPI = new APIObject( "admin-api", "admin.api", "//localhost:8001", "/admin-api"); HttpEntity apiEntity = new HttpEntity(stockAPI); ResponseEntity addAPIResp = restTemplate.postForEntity( "//localhost:8001/apis", apiEntity, String.class); assertEquals(HttpStatus.CREATED, addAPIResp.getStatusCode());

Now we can use the proxied admin API to manage APIs:

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

Surely, we want the proxied API secured. This can be easily achieved by enabling authentication plugin for the proxied admin API.

6. Summary

В тази статия представихме Kong - платформа за шлюз за API на микросервиз и се фокусирахме върху неговата основна функционалност - управление на API и заявки за маршрутизация към сървъри нагоре по веригата, както и върху някои по-разширени функции като балансиране на натоварването.

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

Както винаги, пълното внедряване може да бъде намерено в Github.