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 さんです。みなさんお楽しみに。