話題の記事

APIアグリゲータのKongでマイクロサービスを統合管理する

2015.06.24

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ども、大瀧です。
「これからのWeb開発はマイクロサービスアーキテクチャだ!」とそこかしこで言われる昨今ですが、実際にマイクロサービスでAPIサーバーを実装しようとすると面倒な部分がいろいろ出てきます。例えば以下のような。

  • サービス毎にAPIが異なっていて統一性が無い
  • サービス毎に認証機構を実装するのがつらい
  • サービス毎にログの形式が異なる、アクセスログが残らないものがある
  • DoS攻撃が来たらどうしよう

これらWeb APIサービスに求められる課題をまとめて解決してくれるのが、APIアグリゲータです。(下図はAPIアグリゲータなしとAPIアグリゲータとしてKongを用いる場合)

kong01_1

Kongとは

Kongは、商用APIアグリゲータのMashapeのOSS版としてGitHubで開発が進めらているAPIアグリゲータです。

Empireのブログエントリーを書くときに、Ericのブログで"外部向けリバースプロキシとしてKongとかも将来使えるように"とKongが紹介されていて知った次第です。

APIアグリケータと聞くと仕組みがイメージしずらいですが、要はWebアクセスを他のホストに転送する高機能なリバースプロキシと説明することができます。KongはNginx + Luaモジュールという実装、クラスタを組むためのデータストアにCassandraを採用することで高パフォーマンス、高スケーラビリティを謳っています。

kong-detailed

現在は以下の機能をプラグイン形式で備えます。

  • セキュリティ
    • BASIC認証
    • APIキー認証
    • CORS
    • SSLターミネーション
  • 変換
    • リクエスト変換
    • レスポンス変換
  • ユーティリティー
    • リクエストレート制限
    • リクエストサイズ制限
  • ログ
    • サーバーへのログ送信(TCP/UDP/HTTP)
    • ファイルへのロギング

では、実際に動かしてその様子を見てみます。Kongのバイナリは主なディストリビューション向けのRPMやAPTパッケージのほか、Dockerイメージが提供されています。今回は以下のドキュメントに従い、手元のMacBookでDocker環境を用意して試してみます。

動作確認環境

  • OS : OS X 10.10.3 Yosemite
  • Docker : Boot2Docker バージョン1.6.2

1. Kong Dockerコンテナの実行

Kongを実行するためにはKVSのCassandraが必要ですので、先にCassandraのDockerイメージmashape/cassandraを実行します。

$ docker run -p 9042:9042 -d --name cassandra mashape/cassandra
Unable to find image 'mashape/cassandra:latest' locally
latest: Pulling from mashape/cassandra
511136ea3c5a: Pull complete
  (略)
d3cbe188016c: Already exists
Digest: sha256:fa97eb1146e2c8e1d3dd52a8f8000bdf779e6110d7ea90abb249044b0adc5b81
Status: Downloaded newer image for mashape/cassandra:latest
dc70b7fa2074393d760ed8a6466d2f36a2998921304917c6338f0f1c719f382f
$

続いて、Cassandraへのリンク設定をしつつ、KongのDockerイメージmashape/kongを実行します。

$ docker run -p 8000:8000 -p 8001:8001 -d --name kong --link cassandra:cassandra mashape/kong
Unable to find image 'mashape/kong:latest' locally
latest: Pulling from mashape/kong
6941bfcbbfca: Pull complete
  (略)
c307e0d3283c: Already exists
Digest: sha256:8884b923c1b1a47a802ef4bda450004dcfb9a2ee58e9ae27c767392cb24fd251
Status: Downloaded newer image for mashape/kong:latest
f92843790de50d06b858b26514b4b79850249a1a97795aa67b4b1cb91f153fd8
$ docker ps
CONTAINER ID        IMAGE                      COMMAND                CREATED              STATUS              PORTS                                                                         NAMES
f92843790de5        mashape/kong:latest        "/bin/sh -c 'kong st   33 seconds ago       Up 32 seconds       0.0.0.0:8000-8001->8000-8001/tcp                                              kong
dc70b7fa2074        mashape/cassandra:latest   "cassandra -f"         About a minute ago   Up About a minute   22/tcp, 7000-7001/tcp, 7199/tcp, 8888/tcp, 9160/tcp, 0.0.0.0:9042->9042/tcp   cassandra
$

起動しました!KongはTCP8000番ポートでリバースプロキシ、TCP8001番ポートでKong自身のRestful APIを待ち受けます。今回はBoot2Docker環境なので、以後はBoot2Docker VMにcurlコマンドでHTTPリクエストを発行します。既定でBoot2Docker VMのIPアドレスは192.168.59.103がセットされるはずです。念のためboot2docker ipコマンドで確認しておきましょう。

$ boot2docker ip
192.168.59.103
$

2. APIアグリゲータの動作確認

KongのRestful APIのルートにGETリクエストを送ると、KongのサマリーがJSON形式で返ってきます。

$ curl 192.168.59.103:8001
{"version":"0.3.2","lua_version":"LuaJIT 2.1.0-alpha","tagline":"Welcome to Kong","hostname":"","plugins":{"enabled_in_cluster":{},"available_on_server":["ssl","keyauth","basicauth","ratelimiting","tcplog","udplog","filelog","httplog","cors","request_transformer","response_transformer","requestsizelimiting"]}}
$

では、APIをKongに登録してみましょう。KongのRest APIの/apis/にPOSTリクエストを発行します。今回はHTTPリクエスト&レスポンスの検証サービスであるMockbinを試しに登録します。target_urlがKongからのアクセス先(Nginxのproxy_passディレクティブ相当)、public_dnsはKongの待ち受けDNS名(Nginxのserver_nameディレクティブ相当)です。

$ curl -i -X POST 
  --url http://192.168.59.103:8001/apis/ 
  --data 'name=mockbin' 
  --data 'target_url=http://mockbin.com/' 
  --data 'public_dns=mockbin.com'
HTTP/1.1 201 Created
Date: Mon, 22 Jun 2015 08:05:07 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: kong/0.3.2

{"public_dns":"mockbin.com","target_url":"http://mockbin.com/","id":"5656b41f-c3c7-4ea7-ce30-b187c779d9e5","created_at":1434960307000,"name":"mockbin"}
$

登録されました!では、curlコマンドに--headerオプションでHostヘッダを付与してリバースプロキシポートにアクセスしてみます。

$ curl -i -X GET \
  --url http://192.168.59.103:8000/ \
  --header 'Host: mockbin.com'
HTTP/1.1 200 OK
Date: Mon, 22 Jun 2015 08:05:33 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: __cfduid=d73f83d5ee63c47bea8274b9debe016421434960333; expires=Tue, 21-Jun-16 08:05:33 GMT; path=/; domain=.mockbin.com; HttpOnly
Etag: W/"WjyUny1hiU0eFTCRGSBgnQ=="
Vary: Accept-Encoding
Via: kong/0.3.2
Server: cloudflare-nginx
CF-RAY: 1fa672e2192f045b-NRT
  : 
<!DOCTYPE html><html><head>...
$

MockbinのHTMLコンテンツがレスポンスとして返ってきました。Kongがリバースプロキシとして動作していることがわかりますね。 実際にMockbinでサンプルの構成を作成してみます。

kong03

Shell-cURLの例に合わせてKongのリバースプロキシポートにリクエストを送信します。

$ curl -i -X GET \
  --url 'http://192.168.59.103:8000/bin/01a7ecfa-1236-4ab9-97ab-d09e6f6f6c10?foo=bar&foo=baz' \
  --header 'Host: mockbin.com' \
  --header 'accept: application/json' \
  --header 'content-type: application/x-www-form-urlencoded' \
  --cookie 'foo=bar; bar=baz' \
  --data   'foo=bar&bar=baz'
HTTP/1.1 200 OK
Date: Mon, 22 Jun 2015 14:42:04 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 27
Connection: keep-alive
Set-Cookie: __cfduid=d1bcbab49b3f28fd9495006a18ded336d1434984123; expires=Tue, 21-Jun-16 14:42:03 GMT; path=/; domain=.mockbin.com; HttpOnly
Etag: W/"1b-6e161ab4"
Vary: Accept-Encoding
Via: kong/0.3.2
Server: cloudflare-nginx
CF-RAY: 1fa8b7b46a51132f-NRT

{
    "foo": "Hello Word"
}$

ちゃんとAPIレスポンスが返ってきていますね。まずはAPIアグリゲータとして動作することが確認できました。

3. APIキー認証の追加

では、Kongのプラグインの一つ、APIキー認証を試してみます。今回利用しているDockerコンテナでは既にAPIキー認証が有効になっているので、登録したmockbinでAPIキー認証を有効にします。Rest APIの/apis/<API名>/plugins/にPOSTで追加するプラグイン(今回はkeyauth)を指定します。

$ curl -i -X POST \
  --url http://192.168.59.103:8001/apis/mockbin/plugins/ \
  --data 'name=keyauth'
HTTP/1.1 201 Created
Date: Tue, 23 Jun 2015 09:20:02 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: kong/0.3.2

{"api_id":"966e541a-d3c0-4a26-ca20-ce63be9da3a3","id":"db497a57-3538-478a-c791-9321c7a36cc7","value":{"key_names":["apikey"],"hide_credentials":false},"enabled":true,"created_at":1435051202000,"name":"keyauth"}
$

APIキー認証が有効になりました。APIキー認証が有効な場合は、APIキーがリクエストに含まれない場合や不正なAPIキーのリクエストの場合、403エラーになります。まずは先ほど同じリクエストをリバースプロキシポートに送信してみます。

$ curl -i -X GET \
  --url http://192.168.59.103:8000/ \
  --header 'Host: mockbin.com'
HTTP/1.1 403 Forbidden
Date: Tue, 23 Jun 2015 09:20:37 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: kong/0.3.2

{"message":"Invalid authentication credentials"}
$

403エラーになりましたね。期待通りのレスポンスです。では、APIキーを登録します。KongではAPIキーを登録するユーザーをConsumerという単位で管理します。Consumerをユーザー名takiponeで登録します。

$ curl -i -X POST \
  --url http://192.168.59.103:8001/consumers/ \
  --data 'username=takipone'
HTTP/1.1 201 Created
Date: Tue, 23 Jun 2015 09:22:54 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: kong/0.3.2

{"username":"takipone","created_at":1435051374000,"id":"6e9eb3a9-f0cc-4295-c2d4-59f37078fb58"}
$

続いてAPIキー(今回は1234567890qwerty)をConsumer takiponeに登録します。

$ curl -i -X POST \
  --url http://192.168.59.103:8001/consumers/takipone/keyauth/ \
  --data 'key=1234567890qwerty'
HTTP/1.1 201 Created
Date: Tue, 23 Jun 2015 09:24:31 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: kong/0.3.2

{"created_at":1435051471000,"consumer_id":"6e9eb3a9-f0cc-4295-c2d4-59f37078fb58","key":"1234567890qwerty","id":"83f4473c-3178-46bf-c6c5-2645999409ff"}
$

これでOKです。では、登録したAPIをリクエスト(apikeyヘッダ)に含めて、再度リバースプロキシポートにアクセスしてみます。

$ curl -i -X GET \
  --url http://192.168.59.103:8000 \
  --header 'Host: mockbin.com' \
  --header 'apikey: 1234567890qwerty'
HTTP/1.1 200 OK
Date: Tue, 23 Jun 2015 09:25:30 GMT
Content-Type: text/html; charset=utf-8
  (略)
$

認証が通り、レスポンスが取得できました! APIキーの値を誤って指定すると、以下のように403エラーになります。

$ curl -i -X GET \
  --url http://192.168.59.103:8000 \
  --header 'Host: mockbin.com' \
  --header 'apikey: 1234567890qwert'
HTTP/1.1 403 Forbidden
Date: Tue, 23 Jun 2015 14:02:08 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: kong/0.3.2

{"message":"Invalid authentication credentials"}
$

きちんと認証が動作していますね。

まとめ

APIアグリゲータのKongをご紹介しました。複数のマイクロサービスを統合し、Kong自体もRest APIで動的に設定を変更、Cassandraで全ノードで同じ設定が共有されるという感じで、なかなか使い勝手がよさそうですよね。他のプラグインもどのように使えるのか気になるところです。

リバースプロキシで認証を備えるプロダクトとしては、OAuth2が使えるgateがありますし、技術的にはngx_mrubyなどmrubyで実装することもできそうです。 さらに、毛色は異なりますが、最近は以下のようなCDNサービスをAPIサーバーの手前に置いてセキュリティ機能を利用する構成もあります。(MashapeもCDN機能を持っています)

Web APIの手前に被せるサービスの選択肢が広がってきていると思いますので、目的に合わせてうまく使い分けたいところですね。