OSS 版 API Gateway、Kong Gateway をつかってみる

この記事では Kong Gateway とは何か、なぜ使うのか、どうやって使うのか、を簡単にまとめてみました。

Kong Gatewayとは

Kong 社が提供しているオープンソースの API ゲートウェイです。マルチクラウドやマイクロサービス、分散アーキテクチャに最適化されています。Kong には Enterprise 版と OSS 版がありますが、ここでは OSS 版の Kong Gateway について記載します。

kong oss

なぜ Kong Gateway を使うのか

テックタッチでは、フロントエンドとバックエンドに分かれて開発をしています。バックエンドでは API を複数のコンテナに分けて管理できるようマイクロサービス化して開発をしています。インフラは AWS を使っていますが、AWS の API Gateway を使わず Kong Gateway を使っています。テックタッチは、主に toE・toB 向けにサービスを展開しており、サービスの特性上、マルチクラウドやオンプレが求められるケースがあります。そのため、API の入り口はできるだけベンダーロックインしなくてすむように Kong Gateway を利用しています。

Kong Gateway をインストールできる環境

Kong Gateway は、下記のように複数の OS にインストールできます。本記事では「Mac + DB Less(YML)環境」と「Docker + Postgres 環境」でサンプルを作成し動かしてみました。

https://konghq.com/kong/

Kong Gateway の特徴

特徴は、下図にあるように Kong Gateway を使うことによって、認証・アクセス制限・ロギング・分析・キャッシングなどをそれぞれの API で実装する必要がなくなる点にあります。また、Kong Gateway のプラグインを利用することで簡単にこれらの機能を組み込むことができます。

kong feature
https://apifriends.com/より引用


またGitHub を確認してみると、現時点でスター数は 26,000 で Insights の Commits 状況を見ても比較的頻繁に更新されていることが分かります。ちなみに開発言語は Lua で、ライセンスは Apache License 2.0 です。

insights commits

Kong Gateway の概念と機能

Kong Gateway には Service・Route・Consumer・Plugin・Admin API という概念が存在し、それらをまとめているのが下記の図と表になります。*1

kong overview Getting Started Guide – vCE-2.0.x_KE-1.5.x | Kong – Open-Source API Management and Microservice Management

概念説明
ServiceKong Gateway に登録するサービス
Routeリクエストをサービスに送信するルート。1 つのサービスに複数のルートを含めることが可能
ConsumerAPI のエンドユーザー。API にアクセスするユーザー制御、ロギング、トラフィックレポートなどが可能
PluginKong Gateway の機能を変更および制御するためのモジュラーシステム
Admin API管理目的で利用する内部 RESTful API。API コマンドはクラスタ内の任意のノードで実行できる

Mac + DB Less(YML)環境で動かしてみる

Kong Gateway を利用するには、データベース(Cassandra or PostgreSQL)が必要ですが、多くの機能は DB Less(YML)環境でも実現が可能です。まずはデータベースと RESTful API である Admin API を使わずに「Mac + DB Less(YML)環境」で動かしてみます。

構成

Kong Gateway とサンプル用 API サーバを 2 つ用意して動作確認を行います。

kong structure

下準備

Kong Gateway に登録するサンプル API を 2 つ用意します。8000 番と 8001 番ポートは Kong Gateway で利用するので、8002 番と 8003 番を利用します。また、ここでは Go を利用しましたが、何で作成してもかまいません。

// user-api/main.go
package main

import (
    "encoding/json"
    "net/http"
)

type User struct {
    FirstName string `json:"firstName"`
    LastName  string `json:"lastName"`
}

func users(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    user := User{
        FirstName: "John",
        LastName: "Doe",
    }
    var users []User
    users = append(users, user)
    json.NewEncoder(w).Encode(users)
}

func main() {
    http.HandleFunc("/users", users)
    http.ListenAndServe(":8002", nil)
}
// client-api/main.go
package main

import (
    "encoding/json"
    "net/http"
)

type Client struct {
    CompanyName string `json:"companyName"`
    Email  string `json:"email"`
}

func clients(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    user := Client{
        CompanyName: "John Inc.",
        Email: "john@example.com",
    }
    var clients []Client
    clients = append(clients, user)
    json.NewEncoder(w).Encode(clients)
}

func main() {
    http.HandleFunc("/clients", clients)
    http.ListenAndServe(":8003", nil)
}

それぞれ実行するとJSONが返ってくるのが確認できます。これらの API を Kong Gateway に登録していきます。

# user-api

$ go run user-api/main.go
# http://localhost:8002/users
[
  {
    "firstName": "John",
    "lastName": "Doe"
  }
]
# client-api

$ go run client-api/main.go
# http://localhost:8003/clients
[
  {
    "companyName": "John Inc.",
    "email": "john@example.com"
  }
]

Kong をセットアップする

Homebrew でインストールすると kong コマンドが使えます。

$ brew tap kong/kong
$ brew install kong

$ kong
Usage: kong COMMAND [OPTIONS]

The available commands are:
 check
 config
 health
 hybrid
 migrations
 prepare
 quit
 reload
 restart
 roar
 start
 stop
 version

Options:
 --v              verbose
 --vv             debug

kong を初期化する
kong config initコマンドを実行すると kong.yml が生成されます。この kong.yml をデータベースの変わりに利用します。

$ kong config init
# kong.ymlが生成される

kong.conf ファイルを作成する
kong.conf ファイルを作成し、データベースを利用しないという設定と kong.yml のパスを指定します。

# kong.conf

# データベースを利用しない
database = off
# kong.ymlのパスを指定する
declarative_config = kong.yml

ここまでのディレクトリ構成
ここまでのディレクトリ構成は下記のようになっています。

.
├── client-api
│   └── main.go
├── kong.conf
├── kong.yml
└── user-api
    └── main.go

kong.yml にサービスとルーティング情報を記述する

kong.yml ファイルを編集して user-api と client-api をサービスに登録し、ルーティング情報を記述します。kong.yml は生成後にデフォルトでサンプルの記述がコメントアウトされた状態で存在しているので、その値を少し変えていきます。

# kong.yml
_format_version: "1.1"

services:
- name: user-api
  url: http://localhost:8002
  routes:
  - name: user-api-routes
    paths:
    - /user-api
- name: client-api
  url: http://localhost:8003
  routes:
  - name: client-api-routes
    paths:
    - /client-api

-cオプションで kong.yml ファイルを指定して kong を起動します。

$ kong start -c kong.conf

そのほかの kong コマンド(参考)

# 停止コマンド
$ kong stop

# 再起動コマンド
$ kong restart -c kong.conf

# コンフィグチェックコマンド
$ kong config -c kong.conf parse kong.yml

動作確認をする
http://localhost:8000/user-api/usersにアクセスすると user-api の結果が返ってきて正常にサービスの登録ができたことが確認できます。

user-api

http://localhost:8000/clients-api/clientsにアクセスすると client-api の結果が返ってきて正常にサービスの登録ができたことが確認できます。

client-api

Rate-Limit プラグインを利用する

rate-limiting プラグインを利用し、client-api に 1 分以内に 6 回以上アクセスしたら API rate limit exceededとなるように設定します。

# kong.yml
services:
- name: user-api
  url: http://localhost:8002
  routes:
  - name: user-api-route
    paths:
    - /user-api
- name: client-api
  url: http://localhost:8003
  routes:
  - name: client-api-route
    paths:
    - /client-api
  # rate-limiting プラグインを設定する
  plugins:
  - name: rate-limiting
    config:
      minute: 5
      policy: local

kong を再起動します。

$ kong restart -c kong.conf

動作確認をする
6 回目のアクセスでAPI rate limit exceededとなります。

$ curl http://localhost:8000/client-api/clients
clients
$ curl http://localhost:8000/client-api/clients
clients
$ curl http://localhost:8000/client-api/clients
clients
$ curl http://localhost:8000/client-api/clients
clients
$ curl http://localhost:8000/client-api/clients
clients
$ curl http://localhost:8000/client-api/clients
{"message":"API rate limit exceeded"}%

プロキシキャッシングを利用する

proxy-cache プラグインを利用し、user-api に 30 秒間キャッシングする設定をします。

# kong.yml
services:
- name: user-api
  url: http://localhost:8002
  routes:
  - name: user-api-route
    paths:
    - /user-api
  # proxy-cache プラグインを設定する
  plugins:
  - name: proxy-cache
    config:
      content_type:
      - application/json
      cache_ttl: 30
      strategy: memory

kong を再起動します。

$ kong restart -c kong.conf

動作確認をする
1 回目のアクセスはX-Cache-Status: Missとなります。

$ http :8000/user-api/users
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 40
Content-Type: application/json
Date: Thu, 09 Jul 2020 22:27:00 GMT
Via: kong/2.0.5
X-Cache-Key: 94c7fe79e6cb766f3ac9bdeffeabc1b6
**X-Cache-Status: Miss**
X-Kong-Proxy-Latency: 1
X-Kong-Upstream-Latency: 0

[
    {
        "firstName": "John",
        "lastName": "Doe"
    }
]

2 回目以降のアクセスは 30 秒以内であればX-Cache-Status: Hitとなります。

$ http :8000/user-api/users
HTTP/1.1 200 OK
Age: 2
Connection: keep-alive
Content-Length: 40
Content-Type: application/json
Date: Thu, 09 Jul 2020 22:28:16 GMT
Server: openresty
Via: kong/2.0.5
X-Cache-Key: 94c7fe79e6cb766f3ac9bdeffeabc1b6
**X-Cache-Status: Hit**
X-Kong-Proxy-Latency: 0
X-Kong-Upstream-Latency: 0

[
    {
        "firstName": "John",
        "lastName": "Doe"
    }
]

キー認証プラグインを利用する

key-auth プラグインを利用し、user-api にキー認証によるアクセス制御を設定します。キーを設定した consumer も合わせて登録し、認証キーを持ったユーザーのみアクセス可能にします。

# kong.yml
- name: user-api
  url: http://localhost:8002
  routes:
  - name: user-api-route
    paths:
    - /user-api
  plugins:
  - name: proxy-cache
    config:
      content_type:
      - application/json
      cache_ttl: 30
      strategy: memory
  # key-auth プラグインを設定する
  - name: key-auth

# キーを設定した consumer を設定する
consumers:
- username: user-api-user
  keyauth_credentials:
  # キーにmy-keyを設定する
  - key: my-key

kong を再起動します。

$ kong restart -c kong.conf

動作確認をする
キーがない状態で user-api へアクセスするとNo API key found in requestとなります。

$ http :8000/user-api/users
HTTP/1.1 401 Unauthorized
Connection: keep-alive
Content-Length: 41
Content-Type: application/json; charset=utf-8
Date: Thu, 09 Jul 2020 22:48:21 GMT
Server: kong/2.0.5
WWW-Authenticate: Key realm="kong"
X-Kong-Response-Latency: 1

{
    "message": "No API key found in request"
}

apikey に kong.yml に設定した認証キー(my-key)を指定するとアクセスできます。

$ http :8000/user-api/users apikey:my-key
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 40
Content-Type: application/json
Date: Thu, 09 Jul 2020 22:48:53 GMT
Via: kong/2.0.5
X-Cache-Key: 0ce40fdb3f0a21242f3c5c10a5b6a278
X-Cache-Status: Miss
X-Kong-Proxy-Latency: 1
X-Kong-Upstream-Latency: 1

[
    {
        "firstName": "John",
        "lastName": "Doe"
    }
]

特定ユーザーに Rate-Limit プラグインを適用する

先程は client-api 全体に rate-limiting プラグインを利用しましたが、consumer に rate-limiting プラグインを適用することで、特定のユーザーに対して Rate-Limit を設定することもできます。

# kong.yml
consumers:
- username: user-api-user
  keyauth_credentials:
  - key: my-key
  # consumer に rate-limiting プラグインを設定する
  plugins:
  - name: rate-limiting
    config:
      minute: 5
      policy: local

kong を再起動します。

$ kong restart -c kong.conf

動作確認
user-api に my-key を使ってアクセスできる consumer でアクセスすると、6 回目でAPI rate limit exceededとなります。

$ http :8000/user-api/users apikey:my-key
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 40
Content-Type: application/json
Date: Thu, 09 Jul 2020 22:52:02 GMT
RateLimit-Limit: 5
RateLimit-Remaining: 4
RateLimit-Reset: 58
Via: kong/2.0.5
X-Cache-Key: 0ce40fdb3f0a21242f3c5c10a5b6a278
X-Cache-Status: Miss
X-Kong-Proxy-Latency: 2
X-Kong-Upstream-Latency: 0
X-RateLimit-Limit-Minute: 5
X-RateLimit-Remaining-Minute: 4

[
    {
        "firstName": "John",
        "lastName": "Doe"
    }
]

....(トータル5回アクセス)

$ http :8000/user-api/users apikey:my-key
HTTP/1.1 429 Too Many Requests
Connection: keep-alive
Content-Length: 37
Content-Type: application/json; charset=utf-8
Date: Thu, 09 Jul 2020 22:52:30 GMT
RateLimit-Limit: 5
RateLimit-Remaining: 0
RateLimit-Reset: 30
Retry-After: 30
Server: kong/2.0.5
X-Kong-Response-Latency: 0
X-RateLimit-Limit-Minute: 5
X-RateLimit-Remaining-Minute: 0

{
    "message": "API rate limit exceeded"
}

Docker + PostgreSQL環境で動かしてみる

ここまでは「Mac + DB Less(YML)環境」で動作確認をしましたが、次は同様のことを「Docker + PostgreSQL 環境」で RESTful API である Admin API を利用して動かしてみます。(ここまでの内容である程度想像がつく方は読み飛ばしてもらっても大丈夫です。)

構成

kong structure postgres

下準備

API は先程利用した user-api と client-api を利用します。それぞれの API 用に Dockerfile を作成しておきます。

# user-api/Dockerfile
FROM golang:1.14-alpine

WORKDIR /opt/client-api

COPY . .

RUN go build -o app main.go

CMD ["/opt/client-api/app"]
# client-api/Dockerfile
FROM golang:1.14-alpine

WORKDIR /opt/user-api

COPY . .

RUN go build -o app main.go

CMD ["/opt/user-api/app"]

Kong をセットアップする

kong gateway の docker-compose.yml ファイルは下記リンクにサンプルがあるのでそのまま利用できます(ローカルから Postgres にアクセスできるようポートだけ追加しました)。また user-api と client-api 用の必要な記述も追記します。

https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2FKong%2Fdocker-kong%2Ftree%2Fmaster%2Fcomposegithub.com

# docker-compose.yml
version: '3.7'

# --- 省略 ---

  db:
    # --- 省略 ---
    
    # ローカルから Postgres にアクセスできるようポートを追加する
    ports:
      - "5432:5432"

  # user-api の記述を追記
  user-api:
    container_name: user-api
    image: user-api
    build: ./user-api
    networks:
      - kong-net
    ports:
      - "8002:8002"

  # client-api の記述を追記
  client-api:
    container_name: client-api
    image: client-api
    build: ./client-api
    networks:
      - kong-net
    ports:
      - "8003:8003"

secrets:
  kong_postgres_password:
    file: ./POSTGRES_PASSWORD

起動します。

$ docker-compose up -d

ここまでのディレクトリ構成
ここまでのディレクトリ構成は下記のようになっています。

.
├── POSTGRES_PASSWORD
├── client-api
│   ├── Dockerfile
│   └── main.go
├── docker-compose.yml
└── user-api
    ├── Dockerfile
    └── main.go

サービスを登録する

RESTful API である Admin API を使ってサービスを登録します。Admin API を利用するには 8001 番ポートを利用します。user-api と client-api のホストにはローカル IP を指定しておきます。

# user-api を登録する
$ curl -i -X POST http://localhost:8001/services \
 --data name=user-api \
 --data url='http://[ローカル IP]:8002'

# client-api を登録する
$ curl -i -X POST http://localhost:8001/services \
 --data name=client-api \
 --data url='http://[ローカル IP]:8003'

動作確認の記述は省略して進めていきます。

ルーティングを登録する

登録した API にルーティング情報を追加します。ここでは単純に 1 つのパスを指定していますが、エンドポイント毎に個別に設定することも可能です。

$ curl -i -X POST http://localhost:8001/services/user-api/routes \
  --data 'paths[]=/user-api' \
  --data 'name=user-api-route'

$ curl -i -X POST http://localhost:8001/services/client-api/routes \
  --data 'paths[]=/client-api' \
  --data 'name=client-api-route'

Rate-limit プラグインを利用する

client-api に 1 分以内に 6 回以上アクセスしたら API rate limit exceeded となるように設定します。

$ curl -i -X POST http://localhost:8001/services/client-api/plugins \
--data "name=rate-limiting" \
--data "config.minute=5" \
--data "config.policy=local"

プロキシキャッシングを利用する

user-api に 30 秒間キャッシングする設定をします。

$ curl -i -X POST http://localhost:8001/services/user-api/plugins \
--data name=proxy-cache \
--data config.content_type="application/json" \
--data config.cache_ttl=30 \
--data config.strategy=memory

キー認証プラグインを利用する

user-api にキー認証によるアクセス制御を設定します。キーを設定した consumer も合わせて追加し、特定のユーザーのみキー認証でアクセスできるようにします。

$ curl -X POST http://localhost:8001/services/user-api/plugins \
  --data 'name=key-auth'

キー認証する consumer を作成しキーの登録をします。

# consumer を新規登録する
$ curl -i -X POST -d "username=consumer&custom_id=consumer" http://localhost:8001/consumers/

# consumer にキーを登録する
$ curl -i -X POST http://localhost:8001/consumers/consumer/key-auth -d 'key=my-key'

# キーを利用してアクセス確認する
$ curl -i http://localhost:8000/user-api/users -H 'apikey:my-key'
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 40
Connection: keep-alive
Date: Mon, 13 Jul 2020 06:25:49 GMT
X-Kong-Upstream-Latency: 2
X-Kong-Proxy-Latency: 0
Via: kong/2.0.5

特定ユーザーに Rate-Limit プラグインを適用する

consumer に rate-limiting プラグインを適用し、特定のユーザーに対して Rate-Limit プラグインを設定します。

$ curl -i -X POST http://localhost:8001/consumers/consumer/plugins \
--data "name=rate-limiting" \
--data "config.minute=5" \
--data "config.policy=local"

これで「Docker + PostgreSQL 環境」でも同様の動作確認ができました。参考までに PostgreSQL のテーブル一覧をあげておきます。今回 API で登録した情報は、services、routes、plugins、consumers テーブルに保存されます。

postgres kong gateway tables

Kong のバージョン

2020 年 7 月現在の最新バージョンは 2.0.5 です。Kong をすでに利用していてバージョンアップをする場合、v2.0.0 以降では、v1.0.0 より前のバージョンから一気にバージョンアップするサポートがないので注意してください。v0.14.1 より前の v0.x バージョンを使っている場合は、まず v0.14.1 に移行する必要があります。v0.14.1 へ移行したら、v1.5.0 に移行ができ、その後 v2.0.0 への移行ができます。詳しくは下記 CHANGELOG.md に記載しているので確認してみてください。

https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2FKong%2Fkong%2Fblob%2Fmaster%2FCHANGELOG.mdgithub.com

v2.0.0 から Go でプラグインが作れる

Kong 2.0 までは Lua でプラグインを作成する必要がありましたが、2.0 からは Go でもプラグインを作成できるようになりました。

まとめ

長くなりましたが「Mac + DB Less(YML)環境」と「Docker + PostgreSQL 環境」それぞれで Kong Gateway を操作する方法についてまとめてみました。プラグインはほかにも豊富にあるので、サービスに合ったプラグインを利用しながら、必要に応じて自分でもプラグインを作成していきたいと思います。