1. 序章

Kong は、オープンソースのAPIゲートウェイおよびマイクロサービス管理レイヤーです。

Nginxとlua-nginx-module(具体的には OpenResty )に基づいた、Kongのプラグイン可能なアーキテクチャにより、柔軟で強力になります。

2. 重要な概念

コードサンプルに飛び込む前に、Kongの主要な概念を見てみましょう。

  • APIオブジェクト– は、特定のタスクを実行したり、何らかのサービスを提供したりするHTTPエンドポイントのプロパティをラップします。 構成には、HTTPメソッド、エンドポイントURI、APIサーバーを指すアップストリームURLが含まれ、リクエストのプロキシ、最大リタイア、レート制限、タイムアウトなどに使用されます。
  • Consumer Object – は、APIエンドポイントを使用するすべてのユーザーのプロパティをラップします。 追跡、アクセス制御などに使用されます
  • アップストリームオブジェクト– は、仮想ホスト名で表される、着信要求がどのようにプロキシまたは負荷分散されるかを示します
  • ターゲットオブジェクト– は、ホスト名(またはIPアドレス)とポートで識別される、実装および提供されるサービスを表します。 すべてのアップストリームのターゲットは、追加または無効化することしかできないことに注意してください。 ターゲットの変更の履歴は、アップストリームによって維持されます
  • プラグインオブジェクト– プラグイン可能な機能により、リクエストとレスポンスのライフサイクル中にアプリケーションの機能を強化します。 たとえば、関連するプラグインを有効にすることで、API認証とレート制限機能を追加できます。 Kongは、プラグインギャラリーで非常に強力なプラグインを提供しています。
  • Admin API – Kong構成、エンドポイント、コンシューマー、プラグインなどを管理するために使用されるRESTfulAPIエンドポイント

次の図は、Kongが従来のアーキテクチャとどのように異なるかを示しています。これは、Kongがこれらの概念を導入した理由を理解するのに役立ちます。

(出典:https://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にはhttp:// localhost:8001 からアクセスできるため、すべてのAPI管理操作は次のベースURIを使用して実行されます。

APIObject stockAPI = new APIObject(
  "stock-api", "stock.api", "http://localhost:8080", "/");
HttpEntity<APIObject> apiEntity = new HttpEntity<>(stockAPI);
ResponseEntity<String> addAPIResp = restTemplate.postForEntity(
  "http://localhost:8001/apis", apiEntity, String.class);

assertEquals(HttpStatus.CREATED, addAPIResp.getStatusCode());

ここでは、次の構成でAPIを追加しました。

{
    "name": "stock-api",
    "hosts": "stock.api",
    "upstream_url": "http://localhost:8080",
    "uris": "/"
}
  • 「name」はAPIの識別子であり、その動作を操作するときに使用されます
  • 「hosts」は、「Host」ヘッダーと照合することにより、着信リクエストを特定の「upstream_url」にルーティングするために使用されます
  • 相対パスは、構成された「URI」に一致します

APIを廃止したい場合、または構成が間違っている場合は、単純に削除できます。

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

APIが追加されると、 http:// localhost:8000から使用できるようになります。

String apiListResp = restTemplate.getForObject(
  "http://localhost:8001/apis/", String.class);
 
assertTrue(apiListResp.contains("stock-api"));

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

assertEquals("10000", stockPriceResp.getBody());

上記のコードサンプルでは、Kongに追加したAPIを介して株価を照会しようとしています。

http:// localhost:8000 / stock / btc をリクエストすると、 http:// localhost:8080 / stock /btcから直接クエリを実行するのと同じサービスが得られます。

4.2. APIコンシューマーの追加

次に、セキュリティ、より具体的にはAPIにアクセスするユーザーの認証について説明します。

後で認証機能を有効にできるように、ストッククエリAPIにコンシューマーを追加しましょう。

APIのコンシューマーを追加するのは、APIを追加するのと同じくらい簡単です。 コンシューマーの名前(またはID)は、すべてのコンシューマーのプロパティの唯一の必須フィールドです。

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

ここでは、新しいコンシューマーとして「eugenp」を追加しました。

{
    "username": "eugenp"
}

4.3. 認証の有効化

Kongの最も強力な機能であるプラグインが登場します。

次に、プロキシされた株式クエリAPIに認証プラグインを適用します。

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

プロキシURIを介して株価を照会しようとすると、リクエストは拒否されます。

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

Eugen はAPIコンシューマーの1つであるため、認証キーを追加して、このAPIの使用を許可する必要があります。

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

次に、Eugenは以前と同じようにこのAPIを使用できます。

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

5. 高度な機能

基本的なAPIプロキシと管理に加えて、KongはAPIの負荷分散、クラスタリング、ヘルスチェック、モニタリングなどもサポートしています。

このセクションでは、Kongを使用してリクエストの負荷を分散する方法と、管理APIを保護する方法について説明します。

5.1. 負荷分散

Kongは、バックエンドサービスへのリクエストの負荷分散の2つの戦略を提供します。それは、動的リングバランサーと単純なDNSベースの方法です。 わかりやすくするために、リングバランサーを使用します。

前述したように、アップストリームは負荷分散に使用され、各アップストリームは複数のターゲットを持つことができます。

Kongは、加重ラウンドロビンとハッシュベースのバランシングアルゴリズムの両方をサポートしています。 デフォルトでは、加重ラウンドロビンスキームが使用されます –リクエストはその重みに従って各ターゲットに配信されます。

まず、アップストリームを準備しましょう。

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

次に、アップストリームの2つのターゲットを追加します。 weight =10のテストバージョンとweight=40のリリースバージョンです。

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

上記の構成では、リクエストの1/5がテストバージョンに移行し、4/5がリリースバージョンに移行すると想定できます。

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

assertTrue(Math.round(releaseCount * 1.0 / testCount) == 4);

加重ラウンドロビンスキームは、バックエンドサービスへのリクエストをほぼ加重比にバランスさせるため、上記のコードの最後の行に反映されているように、比の概算のみを検証できることに注意してください。

5.2. 管理APIの保護

デフォルトでは、Kongはローカルインターフェースからの管理者リクエストのみを受け入れます。これはほとんどの場合十分な制限です。 ただし、他のネットワークインターフェイスを介して管理する場合は、kong.confadmin_listen値を変更し、ファイアウォールルールを構成できます。

または、KongをAdminAPI自体のプロキシとして機能させることもできます。 パス「/admin-api」でAPIを管理したい場合、次のようなAPIを追加できます。

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

これで、プロキシされた管理APIを使用してAPIを管理できます。

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

assertEquals(HttpStatus.CREATED, addAPIResp.getStatusCode());

確かに、プロキシされたAPIを保護する必要があります。 これは、プロキシされた管理APIの認証プラグインを有効にすることで簡単に実現できます。

6. 概要

この記事では、マイクロサービスAPIゲートウェイのプラットフォームであるKongを紹介し、そのコア機能であるAPIの管理とアップストリームサーバーへのリクエストのルーティング、および負荷分散などのより高度な機能に焦点を当てました。

それでも、探索できる確かな機能は他にもたくさんあり、必要に応じて独自のプラグインを開発できます。公式ドキュメントを引き続き探索できます。

いつものように、完全な実装はGithubにあります。