オープンソースなAPI Gateway Kong（とKonga）を使ってみた

# Kongとは
詳しくはこちらの記事が参考になりました。
[KONGことはじめ - Qiita](https://qiita.com/AkihiroTakamura/items/f3dbcace4a9e6ca982a1)

* 無料のAPI Gateway
* 無料のものと、Enterprise版がある（この記事では無料版を使用）
* 無料のものには、デフォルトでは管理コンソール的なものはない（非公式・無料の管理コンソールKongaがある）
* Admin APIと呼ばれるKongに標準搭載されているAPIを介してルーティングなどを設定する
* Gatewayのポートは8000、Admin APIのポートは8001が使用される

## Kongに登場するService、Routeオブジェクトについて
詳しくはこちらが参考になりました
(Kongaの使い方の説明記事ですが、Kongの概念について冒頭で紹介されています)
[Kongの使い方を知る①　～Service & Route編～ | ウルシステムズ株式会社](https://b-rabbit.jp/article/article01/index.html)

また、公式のドキュメントにも同様の説明がありました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526994/93aa15d3-767b-cf13-6d0b-5d912bd0bdc6.png)
[Expose your Services with Kong Gateway - vlatest | Kong - Open-Source API Management and Microservice Management](https://docs.konghq.com/getting-started-guide/latest/expose-services/)

ざっくり言うと、Routeはクライアントがアクセスしてくるときのパスを表していて、
Serviceはそのパスにアクセスがきた時にどのバックエンドサーバーにリクエストを飛ばすかを設定するもの。
なのでServiceには1つ以上のRouteを設定して、ルーティングを設定する。

# 準備
すべてローカルで実施。
## 構成のイメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526994/218f8742-556d-7b5c-05ce-0e8d3a567891.png)

## 環境
* MacOS Catalina v10.15.4
* docker version 19.03.8
* docker-compose version 1.25.4  ← Kong、Postgresql、Kongaの起動に使用

## APIの準備
今回はNode Expressで適当なGET、POSTのAPIを用意。
https://github.com/popy1017/sample-express
これをポートとメッセージを適当に変えて2つ起動
& をつけるとバックグラウンドで起動できる。

```shell
node app.js &
```

### 確認
※ jq は JSON形式の文字列をパースするコマンド

```shell
curl localhost:3000 | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    35  100    35    0     0  35000      0 --:--:-- --:--:-- --:--:-- 35000
{
  "message": "This is service1. GET"
}
```
```shell
curl -X POST -H 'Content-type:application/json' -d '{"service": 1 }' localhost:3000 | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    72  100    57  100    15  28500   7500 --:--:-- --:--:-- --:--:-- 36000
{
  "message": "This is service1. POST",
  "body": {
    "service": 1
  }
}
```
# Kongを起動する
KongやDB、Kongaは下記のdocker-compose.ymlで行った。
https://github.com/Kuari/kong-konga-docker-compose
ただし、1箇所だけ修正

```yml
〜略〜
  konga:
    image: pantsel/konga
    ports:
      - 1337:1337
    links:
      - kong:kong
    container_name: konga
    environment:
      - NODE_ENV=production
    networks:     # <- 追加
      - kong-net  # <- 追加
〜略〜
```
## 起動と確認
まだルーティングなどを設定していないので、Gatewayにアクセスすると、"no Route matched with those values"というメッセージが返ってくる。

```shell
docker-compose up -d

docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                                NAMES
cb08a1c66bdc        pantsel/konga       "/app/start.sh"          41 hours ago        Up 41 hours         0.0.0.0:1337->1337/tcp                                               konga
6562aeafeb48        kong                "/docker-entrypoint.…"   41 hours ago        Up 41 hours         0.0.0.0:8000-8001->8000-8001/tcp, 0.0.0.0:8443-8444->8443-8444/tcp   kong
8df4ff5a0599        postgres:9.6        "docker-entrypoint.s…"   41 hours ago        Up 41 hours         0.0.0.0:5432->5432/tcp                                               kong-database

curl localhost:8000 | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    48  100    48    0     0   5333      0 --:--:-- --:--:-- --:--:--  5333
{
  "message": "no Route matched with those values"
}
```


# まずはAdmin API経由で使ってみる
Kongでは、Admin APIを介してServiceやRouteなどの設定を行う。
Admin APIの詳細な仕様については下記を参照してください。
[Admin API - v2.0.x | Kong - Open-Source API Management and Microservice Management](https://docs.konghq.com/2.0.x/admin-api/)

## Serviceを追加する
Serviceを追加するためには、`http://{Kongサーバーのアドレスorドメイン}:8001/services` にPOSTリクエストを投げる。(今回はローカルで作業するので、`http://localhost:8001/services`)
必須パラメータは`url`または、`host`,`port`,`path`の3点セット。例えば、`url=http://localhost:3000/hoge`と設定するのと、`host=localhost, port=3000, path=/hoge`と設定するのは同義だと思われる。
※ 今回の構成では、Dockerコンテナの中(kong)からローカルサーバー(Node Express)にアクセスする必要があるため、hostに`host.docker.ineternal`を設定する。（localhostを指定すると、コンテナ内のローカルと認識されてしまう。）

urlのみ指定したが、レスポンスを見ると`host="host.docker.internal",port=3000,path="/"`となっていることが確認できる。

```shell
curl -X POST http://localhost:8001/services \
--data name=service1 \
--data url=http://host.docker.internal:3000/ | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   351  100   300  100    51  42857   7285 --:--:-- --:--:-- --:--:-- 50142
{
  "host": "host.docker.internal",
  "created_at": 1587283600,
  "connect_timeout": 60000,
  "id": "936cd429-1e8b-4732-87a6-1215634c5c98",
  "protocol": "http",
  "name": "service1",
  "read_timeout": 60000,
  "port": 3000,
  "path": "/",
  "updated_at": 1587283600,
  "retries": 5,
  "write_timeout": 60000,
  "tags": null,
  "client_certificate": null
}

```

### Serviceの確認
GETで`/services`または、`/services/{service name or id}`を実行すると、Service一覧やSerivceの情報が取得できる。

```shell
# Service一覧
curl http://localhost:8001/services | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   323  100   323    0     0  64600      0 --:--:-- --:--:-- --:--:-- 64600
{
  "next": null,
  "data": [
    {
      "host": "host.docker.internal",
      "created_at": 1587283600,
      "connect_timeout": 60000,
      "id": "936cd429-1e8b-4732-87a6-1215634c5c98",
      "protocol": "http",
      "name": "service1",
      "read_timeout": 60000,
      "port": 3000,
      "path": "/",
      "updated_at": 1587283600,
      "retries": 5,
      "write_timeout": 60000,
      "tags": null,
      "client_certificate": null
    }
  ]
}

# 特定のService情報
curl http://localhost:8001/services/service1 | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   300  100   300    0     0  75000      0 --:--:-- --:--:-- --:--:-- 75000
{
  "host": "host.docker.internal",
  "created_at": 1587283600,
  "connect_timeout": 60000,
  "id": "936cd429-1e8b-4732-87a6-1215634c5c98",
  "protocol": "http",
  "name": "service1",
  "read_timeout": 60000,
  "port": 3000,
  "path": "/",
  "updated_at": 1587283600,
  "retries": 5,
  "write_timeout": 60000,
  "tags": null,
  "client_certificate": null
}
```
## Routeを追加する
Routeは`/services/{service name or id}/routes`にPOSTで追加できる。
必須パラメータは、`methods`, `hosts`, `headers`, `paths`のいずれか。
今回は、methodとpathを指定。

```shell
curl -X POST http://localhost:8001/services/service1/routes --data name=service1-api --data methods[]="GET" --data methods[]="POST" --data paths[]="/service1" | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   512  100   448  100    64  44800   6400 --:--:-- --:--:-- --:--:-- 51200
{
  "id": "c2c410e4-5f0f-4e8f-a040-e5dc55f90a42",
  "path_handling": "v0",
  "paths": [
    "/service1"
  ],
  "destinations": null,
  "headers": null,
  "protocols": [
    "http",
    "https"
  ],
  "methods": [
    "GET",
    "POST"
  ],
  "snis": null,
  "service": {
    "id": "936cd429-1e8b-4732-87a6-1215634c5c98"
  },
  "name": "service1-api",
  "strip_path": true,
  "preserve_host": false,
  "regex_priority": 0,
  "updated_at": 1587285662,
  "sources": null,
  "hosts": null,
  "https_redirect_status_code": 426,
  "tags": null,
  "created_at": 1587285662
}
```
### 確認
#### Admin APIで確認
特定のServiceが持つRoute一覧を取得するには、GETで`/services/{service name or id}/routes`を実行する。

```shell
curl http://localhost:8001/services/service1/routes | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   471  100   471    0     0  78500      0 --:--:-- --:--:-- --:--:-- 78500
{
  "next": null,
  "data": [
    {
      "id": "c2c410e4-5f0f-4e8f-a040-e5dc55f90a42",
      "path_handling": "v0",
      "paths": [
        "/service1"
      ],
      "destinations": null,
      "headers": null,
      "protocols": [
        "http",
        "https"
      ],
      "methods": [
        "GET",
        "POST"
      ],
      "snis": null,
      "service": {
        "id": "936cd429-1e8b-4732-87a6-1215634c5c98"
      },
      "name": "service1-api",
      "strip_path": true,
      "preserve_host": false,
      "regex_priority": 0,
      "updated_at": 1587285662,
      "sources": null,
      "hosts": null,
      "https_redirect_status_code": 426,
      "tags": null,
      "created_at": 1587285662
    }
  ]
}
```
#### Gatewayから確認
`{GatewayURL}:8000/{Routeで設定したPath}`で実行

```shell
# GET
curl localhost:8000/service1 | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    35  100    35    0     0   4375      0 --:--:-- --:--:-- --:--:--  4375
{
  "message": "This is service1. GET"
}

# POST
curl -X POST -H 'Content-type:application/json' -d '{"aaa":"bbb"}' localhost:8000/service1 | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    70  100    57  100    13   8142   1857 --:--:-- --:--:-- --:--:-- 10000
{
  "message": "This is service1. POST",
  "body": {
    "aaa": "bbb"
  }
}
```

以上がAdmin API経由でのルーティング設定。
慣れれば楽なんだろうが、何が起こっているのか、どう設定されているのかが非常にわかりにくい。
ので、続いてKongの非公式GUI Kongaを使ってみる。

# Konga経由でルーティング設定をす
## 初期設定＆ログイン
KongaのURLは http://localhost:1337/
初回アクセス時に、管理アカウントを作成する画面が表示されるので、適当に作成する。
作成が完了すると、ログイン画面に遷移するので、作成した管理アカウントでログインする。
ログインすると下記のような画面が表示されるので、下記のように設定する。（Nameは任意）
Name： KongSample
Kong Admin URL: http://kong:8001
※ KongaとKongはコンテナ間通信であるため、docker-compose.ymlで設定しているcontainer_name（=kong）で名前解決できる。ここでをlocalhostとしてしまうと、Kongaコンテナはコンテナ内を探しにいってしまう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526994/91f0ec24-ea78-c77a-3624-d720a350e70c.png)

## API経由で追加したService＆Route確認
Admin URLの設定（Connectionという）が完了すると、以下のような画面が表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526994/2bba9d3d-23d9-6de2-0db3-69d3f230d0a0.png)

サイドメニューのServiceｓやRouteｓを選択すると、上記でAdmin API経由で追加したServiceやRouteの情報がみられる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526994/aec6604b-f752-09f2-6981-0e09146cbd0a.png)

## Konga経由でServiceを追加する
1. 左サイドメニューの「Services」を選択する
2. 「＋Add New Service」ボタンを押す
3. 「CREATE SERVICE」というモーダルが表示されるので、下記のように設定する。
  * Name: service2
  * Url: http://host.docker.internal:3001/
4. 「Submit Service」ボタンを押し、Serviceを追加する。

登録が完了すると、Service一覧にservice2が追加されているはず。

## Konga経由でRouteを追加する
続いてRouteを追加するが、Routeはサイドメニューの「Services」を選択して、いずれかのServiceを選択した後表示される画面で追加する。（サイドメニューRoutesを選択した場合の画面からは追加できない）
1. 左サイドメニューの「Services」を選択する
2. Service一覧から、「service2」を選択する
3. 画面中央のサイドメニューから「Routeｓ」を選択する
4. 「＋Add Route」ボタンを押す
5. モーダルが表示されるので、下記のように設定する(他はデフォルト)
  * Name: service2-api
  * Paths: /service2
  * Methods: GET,POST
  ※ PathsやMethodsのように複数の値を設定できる項目は、値を1つ入力するごとにEnterを押さないと反映されないので注意
6. 追加が成功すると、Route一覧にservice2-apiが表示されるはず

## Gatewayから確認してみる
```shell
# GET
curl localhost:8000/service2 | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    35  100    35    0     0   4375      0 --:--:-- --:--:-- --:--:--  4375
{
  "message": "This is service2. GET"
}

# POST
curl -X POST -H 'Content-type:application/json' -d '{"aaa":"bbb"}' localhost:8000/service2 | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    70  100    57  100    13   3000    684 --:--:-- --:--:-- --:--:--  3684
{
  "message": "This is service2. POST",
  "body": {
    "aaa": "bbb"
  }
}
```