Eine Einführung in Kong

1. Einleitung

Kong ist eine Open-Source-API-Gateway- und Microservice-Verwaltungsschicht.

Die steckbare Architektur von Kong basiert auf Nginx und dem Lua-Nginx-Modul (speziell OpenResty) und macht es flexibel und leistungsstark.

2. Schlüsselkonzepte

Bevor wir uns mit Codebeispielen befassen, werfen wir einen Blick auf die Schlüsselkonzepte in Kong:

  • API-Objekt - Umschließt die Eigenschaften aller HTTP-Endpunkte, die eine bestimmte Aufgabe ausführen oder einen Dienst bereitstellen . Zu den Konfigurationen gehören HTTP-Methoden, Endpunkt-URIs, Upstream-URLs, die auf unsere API-Server verweisen und für Proxy-Anforderungen, maximale Pensionierungen, Ratenbeschränkungen, Zeitüberschreitungen usw. verwendet werden.
  • Consumer Object - Umschließt die Eigenschaften aller Benutzer unserer API-Endpunkte. Es wird für Tracking, Zugriffskontrolle und mehr verwendet
  • Upstream-Objekt - beschreibt, wie eingehende Anforderungen als Proxy oder Lastausgleich ausgeführt werden, dargestellt durch einen virtuellen Hostnamen
  • Zielobjekt - Stellt die Dienste dar, die implementiert und bereitgestellt werden, identifiziert durch einen Hostnamen (oder eine IP-Adresse) und einen Port. Beachten Sie, dass Ziele jedes Upstreams nur hinzugefügt oder deaktiviert werden können. Eine Historie der Zieländerungen wird vom Upstream verwaltet
  • Plugin-Objekt - steckbare Funktionen zur Erweiterung der Funktionen unserer Anwendung während des Anforderungs- und Antwortlebenszyklus. Zum Beispiel können API-Authentifizierungs- und Ratenbegrenzungsfunktionen hinzugefügt werden, indem relevante Plugins aktiviert werden. Kong bietet sehr leistungsfähige Plugins in seiner Plugins-Galerie
  • Admin-API - RESTful API-Endpunkte zum Verwalten von Kong-Konfigurationen, Endpunkten, Verbrauchern, Plugins usw.

Das folgende Bild zeigt, wie sich Kong von einer alten Architektur unterscheidet, was uns helfen könnte zu verstehen, warum diese Konzepte eingeführt wurden:

(Quelle: //getkong.org/)

3. Setup

Die offizielle Dokumentation enthält detaillierte Anweisungen für verschiedene Umgebungen.

4. API-Verwaltung

Nachdem Sie Kong lokal eingerichtet haben, lassen Sie uns einen Blick auf die leistungsstarken Funktionen von Kong werfen, indem wir unseren einfachen Endpunkt für Aktienabfragen als Proxy verwenden:

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

4.1. Hinzufügen einer API

Als nächstes fügen wir unsere Abfrage-API zu Kong hinzu.

Auf die Administrator-APIs kann über // localhost: 8001 zugegriffen werden, sodass alle unsere API-Verwaltungsvorgänge mit diesem Basis-URI ausgeführt werden:

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

Hier haben wir eine API mit der folgenden Konfiguration hinzugefügt:

{ "name": "stock-api", "hosts": "stock.api", "upstream_url": "//localhost:8080", "uris": "/" }
  • "Name" ist eine Kennung für die API, die bei der Manipulation ihres Verhaltens verwendet wird
  • "Hosts" werden verwendet, um eingehende Anforderungen an die angegebene "upstream_url" weiterzuleiten, indem sie mit dem "Host" -Header übereinstimmen
  • Relative Pfade werden an die konfigurierten "Uris" angepasst.

Falls wir eine API verwerfen möchten oder die Konfiguration falsch ist, können wir sie einfach entfernen:

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

Nachdem APIs hinzugefügt wurden, können sie über // localhost: 8000 : verwendet werden.

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

Im obigen Codebeispiel versuchen wir, den Aktienkurs über die API abzufragen, die wir gerade zu Kong hinzugefügt haben.

Mit dem Antrag // localhost: 8000 / Lager / btc , erhalten wir den gleichen Service wie direkt von der Abfrage // localhost: 8080 / Lager / btc .

4.2. Hinzufügen eines API-Verbrauchers

Lassen Sie uns nun über die Sicherheit sprechen - insbesondere über die Authentifizierung für die Benutzer, die auf unsere API zugreifen.

Fügen wir unserer Aktienabfrage-API einen Verbraucher hinzu, damit wir die Authentifizierungsfunktion später aktivieren können.

Das Hinzufügen eines Verbrauchers für eine API ist genauso einfach wie das Hinzufügen einer API. Der Name (oder die ID) des Verbrauchers ist das einzige erforderliche Feld aller Eigenschaften des Verbrauchers:

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

Hier haben wir "Eugenp" als neuen Verbraucher hinzugefügt:

{ "username": "eugenp" }

4.3. Aktivieren der Authentifizierung

Hier kommt die mächtigste Funktion von Kong, Plugins.

Jetzt wenden wir ein Auth-Plugin auf unsere Proxy-Aktienabfrage-API an:

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

Wenn wir versuchen, den Kurs einer Aktie über den Proxy-URI abzufragen, wird die Anfrage abgelehnt:

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

Denken Sie daran, dass Eugen einer unserer API-Konsumenten ist. Daher sollten wir ihm erlauben, diese API durch Hinzufügen eines Authentifizierungsschlüssels zu verwenden:

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

Dann kann Eugen diese API wie zuvor verwenden:

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. Erweiterte Funktionen

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 diesem Artikel haben wir Kong vorgestellt - eine Plattform für das Microservice-API-Gateway, die sich auf ihre Kernfunktionalität konzentriert - die Verwaltung von APIs und das Weiterleiten von Anforderungen an Upstream-Server sowie einige erweiterte Funktionen wie den Lastenausgleich.

Es gibt jedoch noch viele weitere solide Funktionen, die wir untersuchen können, und wir können bei Bedarf unsere eigenen Plugins entwickeln. Sie können die offizielle Dokumentation hier weiter untersuchen.

Wie immer finden Sie die vollständige Implementierung auf Github.