Introducción a Kong

1. Introducción

Kong es una puerta de enlace API de código abierto y una capa de gestión de microservicios.

Basado en Nginx y el módulo lua-nginx (específicamente OpenResty), la arquitectura conectable de Kong lo hace flexible y poderoso.

2. Conceptos clave

Antes de sumergirnos en ejemplos de código, echemos un vistazo a los conceptos clave en Kong:

  • Objeto API: envuelve las propiedades de cualquier punto final HTTP que realice una tarea específica o brinde algún servicio. Las configuraciones incluyen métodos HTTP, URI de punto final, URL ascendente que apunta a nuestros servidores API y se utilizará para solicitudes de proxy, retiros máximos, límites de velocidad, tiempos de espera, etc.
  • Objeto de consumidor: envuelve las propiedades de cualquier persona que use nuestros puntos finales de API. Se utilizará para seguimiento, control de acceso y más
  • Objeto ascendente: describe cómo las solicitudes entrantes se transferirán o equilibrarán la carga, representadas por un nombre de host virtual
  • Objeto de destino: representa los servicios que se implementan y prestan, identificados por un nombre de host (o una dirección IP) y un puerto. Tenga en cuenta que los objetivos de cada flujo ascendente solo se pueden agregar o desactivar. El historial de los cambios de destino se mantiene por
  • Objeto de complemento: características conectables para enriquecer las funcionalidades de nuestra aplicación durante el ciclo de vida de la solicitud y la respuesta. Por ejemplo, se pueden agregar funciones de limitación de velocidad y autenticación API habilitando complementos relevantes. Kong proporciona complementos muy potentes en su galería de complementos
  • API de administración: puntos finales de la API RESTful que se utilizan para administrar configuraciones de Kong, puntos finales, consumidores, complementos, etc.

La siguiente imagen muestra cómo Kong se diferencia de una arquitectura heredada, lo que podría ayudarnos a comprender por qué introdujo estos conceptos:

(fuente: //getkong.org/)

3. Configuración

La documentación oficial proporciona instrucciones detalladas para varios entornos.

4. Gestión de API

Después de configurar Kong localmente, echemos un vistazo a las poderosas funciones de Kong mediante el proxy de nuestro punto final de consulta de acciones simple:

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

4.1. Agregar una API

A continuación, agreguemos nuestra API de consulta en Kong.

Se puede acceder a las API de administración a través de // localhost: 8001 , por lo que todas nuestras operaciones de administración de API se realizarán con este URI 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());

Aquí, agregamos una API con la siguiente configuración:

{ "name": "stock-api", "hosts": "stock.api", "upstream_url": "//localhost:8080", "uris": "/" }
  • "Nombre" es un identificador de la API, que se utiliza al manipular su comportamiento
  • "Hosts" se utilizará para enrutar las solicitudes entrantes a "upstream_url" dado haciendo coincidir el encabezado "Host"
  • Las rutas relativas se compararán con los "uris" configurados

En caso de que queramos desaprobar una API o la configuración sea incorrecta, simplemente podemos eliminarla:

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

Una vez agregadas las API, estarán disponibles para su consumo a través de // 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());

En el ejemplo de código anterior, intentamos consultar el precio de las acciones a través de la API que acabamos de agregar a Kong.

Al solicitar // localhost: 8000 / stock / btc , obtenemos el mismo servicio que consultar directamente desde // localhost: 8080 / stock / btc .

4.2. Agregar un consumidor de API

Hablemos ahora de seguridad, más específicamente autenticación para los usuarios que acceden a nuestra API.

Agreguemos un consumidor a nuestra API de consulta de acciones para que podamos habilitar la función de autenticación más adelante.

Agregar un consumidor para una API es tan simple como agregar una API. El nombre (o identificación) del consumidor es el único campo obligatorio de todas las propiedades del consumidor:

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

Aquí agregamos “eugenp” como nuevo consumidor:

{ "username": "eugenp" }

4.3. Habilitación de la autenticación

Aquí viene la característica más poderosa de Kong, los complementos.

Ahora vamos a aplicar un complemento de autenticación a nuestra API de consulta de stock con 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 intentamos consultar el precio de una acción a través del proxy URI, la solicitud será rechazada:

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

Recuerde que Eugen es uno de nuestros consumidores de API, por lo que deberíamos permitirle usar esta API agregando una clave de autenticación:

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

Entonces Eugen puede usar esta API como antes:

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. Funciones avanzadas

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

En este artículo, presentamos Kong, una plataforma para la puerta de enlace de API de microservicio y centrada en su funcionalidad principal: la gestión de API y las solicitudes de enrutamiento a servidores ascendentes, así como algunas características más avanzadas, como el equilibrio de carga.

Sin embargo, hay muchas más funciones sólidas que podemos explorar, y podemos desarrollar nuestros propios complementos si es necesario; puede continuar explorando la documentación oficial aquí.

Como siempre, la implementación completa se puede encontrar en Github.