バックエンドエンジニアの taisa です。テックタッチでは API Gateway として、AWS の API Gateway ではなく、クラウドでもオンプレでも使えるオープンソースの Kong Gateway を利用しています。この記事では Kong Gateway とは何か、なぜ使うのか、どうやって使うのか、を簡単にまとめてみました。
- Kong Gatewayとは
- Mac + DB Less(YML)環境で動かしてみる
- Docker + PostgreSQL環境で動かしてみる
- Kong のバージョン
- v2.0.0 から Go でプラグインが作れる
- まとめ
Kong Gatewayとは
Kong 社が提供しているオープンソースの API ゲートウェイです。マルチクラウドやマイクロサービス、分散アーキテクチャに最適化されています。Kong には Enterprise 版と OSS 版がありますが、ここでは OSS 版の Kong Gateway について記載します。
なぜ 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 環境」でサンプルを作成し動かしてみました。
Kong Gateway の特徴
特徴は、下図にあるように Kong Gateway を使うことによって、認証・アクセス制限・ロギング・分析・キャッシングなどをそれぞれの API で実装する必要がなくなる点にあります。また、Kong Gateway のプラグインを利用することで簡単にこれらの機能を組み込むことができます。
またGitHub を確認してみると、現時点でスター数は 26,000 で Insights の Commits 状況を見ても比較的頻繁に更新されていることが分かります。ちなみに開発言語は Lua で、ライセンスは Apache License 2.0 です。
Kong Gateway の概念と機能
Kong Gateway には Service・Route・Consumer・Plugin・Admin API という概念が存在し、それらをまとめているのが下記の図と表になります。*1
概念 | 説明 |
---|---|
Service | Kong Gateway に登録するサービス |
Route | リクエストをサービスに送信するルート。1 つのサービスに複数のルートを含めることが可能 |
Consumer | API のエンドユーザー。API にアクセスするユーザー制御、ロギング、トラフィックレポートなどが可能 |
Plugin | Kong Gateway の機能を変更および制御するためのモジュラーシステム |
Admin API | 管理目的で利用する内部 RESTful API。API コマンドはクラスタ内の任意のノードで実行できる |
Kong Gateway のドキュメント
Kong Gateway のドキュメントは下記にあります。こちらを確認すればだいたいのことは分かると思います。ドキュメントを参考に実際に動かしてみます。
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 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 の結果が返ってきて正常にサービスの登録ができたことが確認できます。
http://localhost:8000/clients-api/clients
にアクセスすると 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 を利用して動かしてみます。(ここまでの内容である程度想像がつく方は読み飛ばしてもらっても大丈夫です。)
構成
下準備
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 用の必要な記述も追記します。
# 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 テーブルに保存されます。
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 に記載しているので確認してみてください。
v2.0.0 から Go でプラグインが作れる
Kong 2.0 までは Lua でプラグインを作成する必要がありましたが、2.0 からは Go でもプラグインを作成できるようになりました。
まとめ
長くなりましたが「Mac + DB Less(YML)環境」と「Docker + PostgreSQL 環境」それぞれで Kong Gateway を操作する方法についてまとめてみました。プラグインはほかにも豊富にあるので、サービスに合ったプラグインを利用しながら、必要に応じて自分でもプラグインを作成していきたいと思います。
本記事で作成したサンプルは GitHub にもあげていますので参考にしてみてください。
*1:Enterprise 版にしか存在しない機能もいくつか存在するので注意してください