Une introduction à Kong

1. Introduction

Kong est une passerelle API open-source et une couche de gestion de microservices.

Basée sur Nginx et le module lua-nginx (en particulier OpenResty), l'architecture enfichable de Kong le rend flexible et puissant.

2. Concepts clés

Avant de plonger dans des exemples de code, jetons un coup d'œil aux concepts clés de Kong:

  • Objet API - encapsule les propriétés de tout point de terminaison HTTP (s) qui accomplit une tâche spécifique ou fournit un service. Les configurations incluent des méthodes HTTP, des URI de point de terminaison, une URL en amont qui pointe vers nos serveurs API et qui sera utilisée pour les demandes de proxy, les retraits maximum, les limites de débit, les délais d'expiration, etc.
  • Objet consommateur - englobe les propriétés de toute personne utilisant nos points de terminaison API. Il sera utilisé pour le suivi, le contrôle d'accès et plus
  • Objet en amont - décrit comment les demandes entrantes seront transmises par proxy ou en charge équilibrées, représentées par un nom d'hôte virtuel
  • Objet cible - représente les services mis en œuvre et servis, identifiés par un nom d'hôte (ou une adresse IP) et un port. Notez que les cibles de chaque amont ne peuvent être ajoutées ou désactivées. Un historique des changements d'objectifs est conservé par
  • Plugin Object - fonctionnalités enfichables pour enrichir les fonctionnalités de notre application pendant le cycle de vie des demandes et des réponses. Par exemple, des fonctionnalités d'authentification API et de limitation de débit peuvent être ajoutées en activant les plugins appropriés. Kong fournit des plugins très puissants dans sa galerie de plugins
  • Admin API - Points de terminaison d'API RESTful utilisés pour gérer les configurations Kong, les points de terminaison, les consommateurs, les plugins, etc.

L'image ci-dessous montre en quoi Kong diffère d'une architecture héritée, ce qui pourrait nous aider à comprendre pourquoi il a introduit ces concepts:

(source: //getkong.org/)

3. Configuration

La documentation officielle fournit des instructions détaillées pour divers environnements.

4. Gestion des API

Après avoir configuré Kong localement, examinons les puissantes fonctionnalités de Kong en envoyant un proxy à notre simple point de terminaison de requête boursière:

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

4.1. Ajouter une API

Ensuite, ajoutons notre API de requête dans Kong.

Les API d'administration sont accessibles via // localhost: 8001 , donc toutes nos opérations de gestion d'API seront effectuées avec cet URI de 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());

Ici, nous avons ajouté une API avec la configuration suivante:

{ "name": "stock-api", "hosts": "stock.api", "upstream_url": "//localhost:8080", "uris": "/" }
  • «Nom» est un identifiant de l'API, utilisé lors de la manipulation de son comportement
  • "Hosts" sera utilisé pour acheminer les demandes entrantes vers "upstream_url" donné en faisant correspondre l'en- tête "Host"
  • Les chemins relatifs seront mis en correspondance avec les "uris" configurés

Dans le cas où nous voulons désapprouver une API ou que la configuration est erronée, nous pouvons simplement la supprimer:

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

Une fois les API ajoutées, elles seront disponibles à la consommation via // 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());

Dans l'exemple de code ci-dessus, nous essayons d'interroger le cours de l'action via l'API que nous venons d'ajouter à Kong.

En demandant // localhost: 8000 / stock / btc , nous obtenons le même service que l'interrogation directement depuis // localhost: 8080 / stock / btc .

4.2. Ajout d'un consommateur d'API

Parlons maintenant de sécurité - plus spécifiquement d'authentification pour les utilisateurs accédant à notre API.

Ajoutons un consommateur à notre API de requête boursière afin que nous puissions activer la fonction d'authentification plus tard.

Ajouter un consommateur pour une API est aussi simple que d'ajouter une API. Le nom (ou identifiant) du consommateur est le seul champ obligatoire de toutes les propriétés du consommateur:

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

Ici, nous avons ajouté «eugenp» en tant que nouveau consommateur:

{ "username": "eugenp" }

4.3. Activation de l'authentification

Voici la fonctionnalité la plus puissante de Kong, les plugins.

Nous allons maintenant appliquer un plugin d'authentification à notre API de requête de stock par 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());

Si nous essayons d'interroger le prix d'une action via l'URI proxy, la demande sera rejetée:

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

N'oubliez pas qu'Eugen est l'un de nos consommateurs d'API, nous devrions donc lui permettre d'utiliser cette API en ajoutant une clé d'authentification:

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

Ensuite, Eugen peut utiliser cette API comme auparavant:

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. Fonctionnalités avancées

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

Dans cet article, nous avons présenté Kong - une plate-forme pour la passerelle d'API de microservice et nous nous sommes concentrés sur sa fonctionnalité de base - la gestion des API et le routage des demandes vers les serveurs en amont, ainsi que sur certaines fonctionnalités plus avancées telles que l'équilibrage de charge.

Pourtant, il y a beaucoup plus de fonctionnalités solides à explorer, et nous pouvons développer nos propres plugins si nécessaire - vous pouvez continuer à explorer la documentation officielle ici.

Comme toujours, l'implémentation complète peut être trouvée sur Github.