Un'introduzione a Kong

1. Introduzione

Kong è un gateway API open source e un livello di gestione dei microservizi.

Basato su Nginx e sul modulo lua-nginx (in particolare OpenResty), l'architettura collegabile di Kong lo rende flessibile e potente.

2. Concetti chiave

Prima di immergerci negli esempi di codice, diamo un'occhiata ai concetti chiave di Kong:

  • Oggetto API: racchiude le proprietà di qualsiasi endpoint HTTP che esegue un'attività specifica o fornisce un servizio. Le configurazioni includono metodi HTTP, URI dell'endpoint, URL upstream che punta ai nostri server API e verrà utilizzato per richieste proxy, ritiri massimi, limiti di velocità, timeout, ecc.
  • Oggetto consumatore: racchiude le proprietà di chiunque utilizzi i nostri endpoint API. Verrà utilizzato per il monitoraggio, il controllo degli accessi e altro ancora
  • Oggetto upstream: descrive il modo in cui le richieste in arrivo verranno inviate tramite proxy o bilanciato il carico, rappresentato da un nome host virtuale
  • Oggetto di destinazione: rappresenta i servizi implementati e serviti, identificati da un nome host (o un indirizzo IP) e una porta. Nota che i target di ogni upstream possono essere solo aggiunti o disabilitati. Una cronologia delle modifiche al target viene mantenuta dall'upstream
  • Oggetto plug-in: funzionalità innestabili per arricchire le funzionalità della nostra applicazione durante il ciclo di vita di richieste e risposte. Ad esempio, è possibile aggiungere funzionalità di autenticazione API e limitazione della velocità abilitando i plug-in pertinenti. Kong fornisce plugin molto potenti nella sua galleria di plugin
  • API amministrativa: endpoint API RESTful utilizzati per gestire configurazioni, endpoint, consumatori, plug-in e così via di Kong

L'immagine sotto mostra come Kong differisce da un'architettura legacy, il che potrebbe aiutarci a capire perché ha introdotto questi concetti:

(fonte: //getkong.org/)

3. Configurazione

La documentazione ufficiale fornisce istruzioni dettagliate per vari ambienti.

4. Gestione API

Dopo aver configurato Kong a livello locale, diamo un'occhiata alle potenti funzionalità di Kong inviando tramite proxy il nostro semplice endpoint di query di borsa:

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

4.1. Aggiunta di un'API

Successivamente, aggiungiamo la nostra API di query in Kong.

Le API di amministrazione sono accessibili tramite // localhost: 8001 , quindi tutte le nostre operazioni di gestione delle API verranno eseguite con questo URI di base:

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());

Qui abbiamo aggiunto un'API con la seguente configurazione:

{ "name": "stock-api", "hosts": "stock.api", "upstream_url": "//localhost:8080", "uris": "/" }
  • "Nome" è un identificatore per l'API, utilizzato durante la manipolazione del suo comportamento
  • "Hosts" verrà utilizzato per instradare le richieste in arrivo a "upstream_url" facendo corrispondere l' intestazione "Host"
  • I percorsi relativi verranno abbinati agli "uris" configurati

Nel caso in cui desideriamo deprecare un'API o la configurazione è sbagliata, possiamo semplicemente rimuoverla:

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

Dopo che le API sono state aggiunte, saranno disponibili per il consumo tramite // 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());

Nell'esempio di codice sopra, proviamo a interrogare il prezzo delle azioni tramite l'API che abbiamo appena aggiunto a Kong.

Richiedendo // localhost: 8000 / stock / btc , otteniamo lo stesso servizio di una query direttamente da // localhost: 8080 / stock / btc .

4.2. Aggiunta di un consumatore API

Parliamo ora della sicurezza, più specificamente dell'autenticazione per gli utenti che accedono alla nostra API.

Aggiungiamo un consumatore alla nostra API di query stock in modo da poter abilitare la funzione di autenticazione in un secondo momento.

Aggiungere un consumatore per un'API è semplice come aggiungere un'API. Il nome (o id) del consumatore è l'unico campo obbligatorio di tutte le proprietà del consumatore:

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());

Qui abbiamo aggiunto "eugenp" come nuovo consumatore:

{ "username": "eugenp" }

4.3. Abilitazione dell'autenticazione

Ecco la caratteristica più potente di Kong, i plugin.

Ora applicheremo un plug-in di autenticazione alla nostra API di query stock proxy:

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());

Se proviamo a interrogare il prezzo di un titolo tramite l'URI proxy, la richiesta verrà rifiutata:

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());

Ricorda che Eugen è uno dei nostri consumatori di API, quindi dovremmo consentirgli di utilizzare questa API aggiungendo una chiave di autenticazione:

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());

Quindi Eugen può utilizzare questa API come prima:

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. Funzionalità avanzate

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

In questo articolo, abbiamo introdotto Kong, una piattaforma per il gateway API di microservizi e focalizzata sulle sue funzionalità principali: la gestione delle API e le richieste di instradamento ai server upstream, nonché alcune funzionalità più avanzate come il bilanciamento del carico.

Tuttavia, ci sono molte più solide funzionalità da esplorare e, se necessario, possiamo sviluppare i nostri plug-in: puoi continuare a esplorare la documentazione ufficiale qui.

Come sempre, l'implementazione completa può essere trovata su Github.