APIアグリゲータのKongでマイクロサービスを統合管理する
ども、大瀧です。
「これからのWeb開発はマイクロサービスアーキテクチャだ!」とそこかしこで言われる昨今ですが、実際にマイクロサービスでAPIサーバーを実装しようとすると面倒な部分がいろいろ出てきます。例えば以下のような。
- サービス毎にAPIが異なっていて統一性が無い
- サービス毎に認証機構を実装するのがつらい
- サービス毎にログの形式が異なる、アクセスログが残らないものがある
- DoS攻撃が来たらどうしよう
これらWeb APIサービスに求められる課題をまとめて解決してくれるのが、APIアグリゲータです。(下図はAPIアグリゲータなしとAPIアグリゲータとしてKongを用いる場合)
Kongとは
Kongは、商用APIアグリゲータのMashapeのOSS版としてGitHubで開発が進めらているAPIアグリゲータです。
Empireのブログエントリーを書くときに、Ericのブログで"外部向けリバースプロキシとしてKongとかも将来使えるように"とKongが紹介されていて知った次第です。
APIアグリケータと聞くと仕組みがイメージしずらいですが、要はWebアクセスを他のホストに転送する高機能なリバースプロキシと説明することができます。KongはNginx + Luaモジュールという実装、クラスタを組むためのデータストアにCassandraを採用することで高パフォーマンス、高スケーラビリティを謳っています。
現在は以下の機能をプラグイン形式で備えます。
- セキュリティ
- 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でサンプルの構成を作成してみます。
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機能を持っています)
- Amazon CloudFront CDN | アマゾン ウェブ サービス(AWS 日本語)
- CDN, Website Security, DDoS Protection, Load Balancing | Incapsula
- Home | CloudFlare | The web performance & security company
Web APIの手前に被せるサービスの選択肢が広がってきていると思いますので、目的に合わせてうまく使い分けたいところですね。