Go + App Engine で作る API Gateway
こんにちは、むらたです。Advent Calender 初めてのチャレンジです。
自分は職務でよく、App Engine で Microservices 構成をとった開発をやります。例えばこんな感じの構成です。
DeNAでのGCP活用事例とGCP NEXTでの事例紹介 — Mobage Developers Blog
この時困るのが、各サービスのゲートウェイの設計です。App Engine の場合、各サービスにそれぞれ appspot.com ドメインが振られるので、例えば example-dot-project-id.appspot.com のようなドメインが振られます。このサービス用のドメインで直接アクセスすれば良いという話もありますが、そうも行かない場合もあります。同一のドメインで API をさばいたり、コンポーネントとパスの構成を柔軟に行いたい場合などです。
dispatcher.yaml を使う手もありますが、dispatcher.yaml はエントリー数が制限されたり、他のプロジェクトのアプリに転送ることは叶わなかったりします。
そこで本稿では App Engine のデフォルトモジュールとして API ゲートウェイを動かし、バックエンドのサービスへのリクエストを中継する構成というのを考えてみます。
ソース
以下にソースコードをアップロードしてあります。
GitHub - yuichi1004/ae-gateway: App Engine API Gateway
仕組み
仕組みはいたってシンプルで gateway.yaml に書かれた設定に従って、urlfetch を用いてバックエンドのサービスにリクエストを中継するだけです。いっけん、レイテンシーなどが気になるところですが、appspot.com のドメインに対する urlfetch は Google のネットワーク外に出ないので、同一のリージョン内の通信であれば、とても高速に動作すると仮定できます。
func handleGatewayRequest(c GatewayRoute) { http.HandleFunc(c.Pattern, func (w http.ResponseWriter, r *http.Request) { ctx := appengine.NewContext(r) client := urlfetch.Client(ctx) dstUrl := strings.Replace(r.URL.Path, c.Pattern, c.Dest, 1) log.Debugf(ctx, "request to %s %s", r.Method, dstUrl) req, err := http.NewRequest(r.Method, dstUrl, r.Body) if err != nil { log.Warningf(ctx, "failed to make request: %v", err) w.WriteHeader(http.StatusInternalServerError) return } resp, err := client.Do(req) if err != nil { log.Warningf(ctx, "failed to process request: %v", err) w.WriteHeader(http.StatusInternalServerError) return } body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Warningf(ctx, "failed to read upstream response: %v", err) w.WriteHeader(http.StatusInternalServerError) return } w.WriteHeader(resp.StatusCode) if _, err := w.Write(body); err != nil { log.Warningf(ctx, "failed to send response to downstream: %v", err) } }) }
実質的に行っている処理はこれだけ。至ってシンプルです。
もっと複雑なルーティングをしたいとか、認証・認可をはさみたいとか要望があれば、処理を追加することも簡単です。
気になるレイテンシーは
簡単な負荷試験を行ってみました。ユーザーの情報を取得する API なるものをでっち上げ、直接アクセスする場合と、この Gateway を用いて API にアクセスした場合のレイテンシーを比較してみます。
== Gateway 通す版 ==
Server Software: Google
Server Hostname: *****.appspot.com
Server Port: 443
SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-CHACHA20-POLY1305,2048,256
Document Path: /users/1
Document Length: 21 bytes
Concurrency Level: 10
Time taken for tests: 12.443 seconds
Complete requests: 1000
Failed requests: 0
Total transferred: 267008 bytes
HTML transferred: 21000 bytes
Requests per second: 80.37 [#/sec] (mean)
Time per request: 124.429 [ms] (mean)
Time per request: 12.443 [ms] (mean, across all concurrent requests)
Transfer rate: 20.96 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 36 79 20.7 79 190
Processing: 13 44 23.7 43 233
Waiting: 13 36 19.2 32 160
Total: 56 123 28.5 122 335
Percentage of the requests served within a certain time (ms)
50% 122
66% 130
75% 135
80% 140
90% 154
95% 163
98% 187
99% 236
100% 335 (longest request)
== 生 Req ==
Server Software: Google
Server Hostname: users-dot-****.appspot.com
Server Port: 443
SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-CHACHA20-POLY1305,2048,256
Document Path: /users/1
Document Length: 21 bytes
Concurrency Level: 10
Time taken for tests: 12.398 seconds
Complete requests: 1000
Failed requests: 0
Total transferred: 267008 bytes
HTML transferred: 21000 bytes
Requests per second: 80.66 [#/sec] (mean)
Time per request: 123.984 [ms] (mean)
Time per request: 12.398 [ms] (mean, across all concurrent requests)
Transfer rate: 21.03 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 34 86 22.7 85 249
Processing: 10 37 24.7 32 154
Waiting: 9 28 17.5 22 114
Total: 54 123 32.2 117 373
Percentage of the requests served within a certain time (ms)
50% 117
66% 130
75% 139
80% 144
90% 159
95% 185
98% 210
99% 227
100% 373 (longest request)
Total のレイテンシーの中央値を見ると直接 117 ms に対して Gateway を通したものは 122 ms。もちろんレイテンシーは落ちていますが、5 ms とほぼ無視してよいレベルのようにも見えます。
まとめ
このような API Gateway を用いると柔軟に API のパスを配置しつつも、Microservices 構成をとることができそうです。また、ほとんど無視できるレベルのレイテンシーで API Gateway を構築できました。またこの方式は複数のプロジェクトにまたがったリクエストのディスパッチも可能なので、より大規模なサービスにも適用できそうです。
さて、明日のエントリーは koki_cheese さんです。みなさんお楽しみに。