APIゲートウェイKongを試す

はじめに

最近触れた技術としてKongと呼ばれるNginxベースのAPIゲートウェイがあります。個人的に興味が湧いたので調べてまとめようと思います。以下のようなことをまとめたいなと思います。

  • Kongの概要
  • 基本的な機能の使い方(インストールはDocker前提)
    • ServiceとRouteの登録

f:id:yuya_hirooka:20200402115910p:plain
kong

Kongとは

公式の「Kong APIとは」のページに以下のようなことが書かれていました。

Kong は、API ゲートウェイです。つまり、クライアントと API ベースのアプリケーションをつなぐミドルウェアの一種です。 Kong を使うことにより、API の機能を一貫して簡単に拡張できます。Kong で導入できる主な機能には、 認証、セキュリティ、トラフィック制御、 サーバーレス、 アナリティクス & モニタリング、リクエスト/レスポンス変換、および ログなどが挙げられます。

複数のAPIに共通の処理を入れたい場合、APIゲートウェイと呼ばれるようなプロキシを通し、共通処理やルーティングを行なうことで、APIの管理や実行を容易にすることが可能になります。

ざっと全体を読む限り、Kongはクラウドでの利用を意識しているようで、公式サイトでクラウドネイティブという言葉を多くみることができました。

導入してみる

Kongのインストール

Kongのインストールに関して、Kong Inc によりいくつかの方法が用意されています。

今回はDockerでの利用をしてみたいと思います。 KongのDocker Hubは以下のところになります。

docker-composeのテンプレートも用意されておりますが、今回は純粋にDockerのみでのインストールを使いたいと思います。
また、ここで、Kongのバージョンは現在(2020年4月2日時点)でのDocker Hubでのlatestタグがつけられているイメージで利用可能な2.0.2を用います。

Kong実行の際にデータベースを利用する or しないの選択を行なう必要があります。
もし、DBを利用しない、DBレスモードで動かす場合は以下のようなことに注意しておく必要があります。

  • Kongのノード間における中央調整ポイントがない
    • Kongを複数ノードで実行している場合に、それぞれのKongのインスタンスがノード内で完全に独立します。設定の共有などは行われませせん。
  • 読み取り専用のAdminAPI
    • DBレスモードでは、宣言的な設定ファイルでのみの設定が有効になっており、AdminAPIが読み取り専用になっています。更新APIを叩こうとするとMethod Not Allowed 405が返って来ます。

今回は、DBレスモードで特に問題がなさそうなのでそちらを利用したいと思います。

それでは、KongのDocker Installationを参考にインストールしていきます。

Docker networkを作成する

以下のコマンドで、Docker networkを作成します。

$ docker network create kong-net

一旦、公式に従っていますが、kong-netの部分は適当な値を当てておくことが可能なようです。
また、このDocker networkの設定自体はDBレスモードで必ず必要と言うわけでは無いですが、レイトリミットプラグインを導入したい場合などに必要になるので、実行しておくのが良いと書かれていました。
また、今回は後ほど必要となるので作成しておきます。

Docker Volumeの作成

以下のコマンドでDocker Volumeの作成を行います。

$ docker volume create kong-vol
$ docker volume inspect kong-vol
[
    {
        "CreatedAt": "2020-04-02T13:47:01+09:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/kong-vol/_data",
        "Name": "kong-vol",
        "Options": {},
        "Scope": "local"
    }
]

マウントポイントが/var/lib/docker/volumes/kong-vol/_dataとなっているのがわかります。
次のステップで、書かれますが、実際にKongの設定を書く際は上記のディレクトリにkong.ymlを作成し、書いていくことになります。

KongのDBレスモードを起動する。

起動時には以下のコマンドを利用しmKongのDBレスモードで起動します。 が、今の状態で起動しようとするとKongのkong.ymlが無いことで怒られて起動に失敗するのでまずはkong.ymlをホストPCの/var/lib/docker/volumes/kong-vol/_dataに作成します。

_format_version: "1.1"

services:
- name: my-service
  url: https://example.com
  plugins:
  - name: key-auth
  routes:
  - name: my-route
    paths:
    - /

consumers:
- username: my-user
  keyauth_credentials:
  - key: my-key

設定の詳細に関しては後ほどまとめますので、ここでは深く考えずに一旦以上の設定を記述します。

それでは、設定ファイルの準備をできたところで、Kongの起動コマンドを少し見てみようと思います。

$ docker run -d --name kong \
     --network=kong-net \
     -v "kong-vol:/usr/local/kong/declarative" \
     -e "KONG_DATABASE=off" \
     -e "KONG_DECLARATIVE_CONFIG=/usr/local/kong/declarative/kong.yml" \
     -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
     -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
     -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
     -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
     -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
     -p 8000:8000 \
     -p 8443:8443 \
     -p 127.0.0.1:8001:8001 \
     -p 127.0.0.1:8444:8444 \
     kong:latest

色々書かれていますが、環境変数として設定されているKONG_DATABASE=offKONG_DECLARATIVE_CONFIGKONG_ADMIN_LISTENの3つの部分に注目します。

まず、KONG_DATABASE=offでは、DBモードをoffにしており、この設定を入れることによって、KongがDBレスモードで起動します。

KONG_DECLARATIVE_CONFIGのところではKongの設定ファイルであるkong.ymlを配置するディレクトリに指定しています。また、-v "kong-vol:/usr/local/kong/declarative"で先程作成したkong-vol/usr/local/kong/declarativeに当てているため、ホストの/var/lib/docker/volumes/kong-vol/_dataに配置したkong.ymlがkongのコンテナ内でKongの設定として読み込まれるようになっています。

KONG_ADMIN_LISTENでは、AdminAPIのエンドポイントのバインディング設定を書いてます。
ここで、設定したエンドポイントをポートフォワードでコンテナ外に公開しているようです。
https用に0.0.0.0:8444 sslも設定していますが、今回は基本的に8001のポートにバインディングされたものを使いたいと思います。

上記のDockerコマンドでKongを起動してみましょう。 起動ができたら以下のcurlを叩くと、kong.ymlのService entityを得ることができます。

$ curl -i http://localhost:8001/services
HTTP/1.1 200 OK
Date: Fri, 03 Apr 2020 02:48:01 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/2.0.2
Content-Length: 316
X-Kong-Admin-Latency: 128

{"next":null,"data":[{"host":"example.com","created_at":1585882034,"connect_timeout":60000,"id":"0855b320-0dd2-547d-891d-601e9b38647f","protocol":"https","name":"my-service","read_timeout":60000,"port":443,"path":null,"updated_at":1585882034,"client_certificate":null,"tags":null,"write_timeout":60000,"retries":5}]}

kong.confで設定していたサービスが取得でき、無事、Kongが立ち上がってそうですね。

ServiceとRouteの設定をしてみる

Kongが起動できたところで、実際にその設定を書いて行きたいと思います。
Kongでは以下の2つの設定方法が用意されています。

  • AdminAPI
  • The Declarative Configuration

AdminAPIを通しての設定はCRUDのWebAPIがKongに用意されており、そのAPIを通して、設定を行なうことが可能です。

AdminAPIに対して、The Declarative Configurationの方はKongの宣言的な設定ファイルをを記述し、Kong側からその設定ファイルを読み込ませる機能です。今までにkong.ymlを用意しましたが、これがその設定ファイルに当たります。

DBレスモードではAdminAPIはリードオンリーになっているため、The Declarative Configurationの方を使うことになります。また、Infrastructure as Codeの観点からも設定をファイルで保存しておくことは良いプラクティスであると思われます。
よって、この記事では基本的にはThe Declarative Configurationの機能の方を利用していこうと思います。

The Declarative Configurationについて

The Declarative ConfigurationはKognのバージョン1.1から追加されています。
この機能を利用することに寄って、

先程、インストールの際に一旦追加しておいたkong.ymlを見てます。

_format_version: "1.1"

services:
- name: my-service
  url: https://example.com
  plugins:
  - name: key-auth
  routes:
  - name: my-route
    paths:
    - /

consumers:
- username: my-user
  keyauth_credentials:
  - key: my-key

まず、一行目に記述されている_format_version: "1.1"では、APIのバーションを指定しています。
kong.ymlの設定ではこの部分のみが必須項目で後のトップレベルの項目は書かなくても起動できたっぽいです。

次にトップレベルで設定されているservicesconsumersですが、これらはKongのentityとなります。
kong.confではentityとその設定を宣言的に記述することができます。
Kongのentityには主に以下のようなものがあります。

  • Service Object
  • Consumer Object
    • サービスの利用者(ユーザ等)を指します。
  • Route Object
    • クライアントのリクエストにマッチするルールを定義し、それらのルールをサービスに紐付けることで、ルーティングの設定を行なうことができます。
  • Plugin Object
    • HTTPのリクエストとレスポンスのライフサイクルで実行されるプラグインの設定を行なうためのentityです。

ちなみに公式ではObjectという呼び方を指定していたりentityという呼び方をしていたりしますが、違いがあるのかはよくわかっていないです(この辺知っている方がいたら教えてほしいです...)。

ここまでまとめたところで、もう一度記述しているkong.ymlを見てみます。
まずは、Serviceの方から

services:
- name: my-service
  url: https://example.com
  plugins:
  - name: key-auth
  routes:
  - name: my-route
    paths:
    - /

このサービスでは、/ のアクセスに対してhttps://example.comへのリクエストを流すサービスを定義しています。また、Pluginとしてkey-authというのを使っているようです(今回は、このプラグインに関しては深く触れません。というか、調べてみたけどまだ良くわかなかったので、今回のゴールはここを深く触れなくても達成できると思ったので、一旦スルーします)

次に、consumerです

consumers:
- username: my-user
  keyauth_credentials:
  - key: my-key

ここでは新たにmy-userというユーザを追加しています。Consumerを定義することによりサービス利用者等を定義することができ、タグをつけフィルタリングをかけたりすることができます。
具体的に利用イメーシがついてないのが正直なところですが、一旦深堀はここまでにしておきます。

ルーティング先を用意する。

それではルーティングの設定を書いていきますが、その前にKongがルーティングするサーバを2つほど用意しておきます。
用意するサーバはNginxでさくっと立ち上げておこうと思います。

サーバ1

$ docker run -d --name kong-nginx \
     --network kong-net \
     -p 127.0.0.1:8111:80 \
     nginx:1.12 

$ docker exec -it kong-nginx bash

root@<コンテナID>: echo "Hello, kong." > /usr/share/nginx/html/index.html

$ curl -i localhost:8111
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Sat, 04 Apr 2020 16:57:59 GMT
Content-Type: text/html
Content-Length: 15
Last-Modified: Sat, 04 Apr 2020 16:57:54 GMT
Connection: keep-alive
ETag: "5e88bc92-f"
Accept-Ranges: bytes

Hello, kong. 

それぞれのNginxのサーバはHello, kongを返すようにしておきます。
また、KongのコンテナからNginxのコンテナへの通信を行えるようにネットワークにkong-netを指定しておきましょう。

Kongの設定を記述する

先ほどを立てたNginxのサーバに対して、サービスとルートを登録してみようと思います。
まずはkong.ymlに先程作成したサービスを追加します。

_format_version: "1.1"
                                                                                                                                              
services:
- name: my-service
  url: https://example.com
  plugins:
  - name: key-auth
  routes:
  - name: my-route
    paths:
    - /
# ServiceとRouteの設定を追記する。
- name: kong-service
  url: http://172.18.0.3
  routes:
  - name: kong-route
    paths:
    - /kong

consumers:
- username: my-user
  keyauth_credentials:
  - key: my-key

/kongkong-nginxにつながるような設定を記述しました。また、ServiceオブジェクトのURLのホストはそれぞれのコンテナのIPを入れました。

設定、記述後にコンテナを再起動します。

それではKongのそれぞれのパスにアクセスして結果を見てみたいと思います。 Kongはデフォルトでは、8000番ポートのトラフィックを上流サービスにプロキシします。

$ curl localhost:8000/kong
Hello, kong.

Kongを通して、それぞれのNginxへアクセスできているみたいですね。

感想

まだ、わからないことが多くてところどころごまかしながらやりましたが、Kongの概要把握と動かしてみることはできたので一旦OKとしようと思います。
プラグインを自作することもできるみたいなので、また別の機会に試してみたいとおもいます。

参考資料