gRPC アプリケーションを AWS で動かすときの注意点

Posted on

freee ではプロダクトの拡大に合わせて、汎用的な機能をマイクロサービスに切り出していこうとしています。すでにマイクロサービス化されているところは REST API による通信を行なっているのですが、これから新しく作るところはデフォルトで gRPC を採用しようとしています。

freee のサービスはすべて AWS 上で動いていますが、本格的に採用する前に gRPC アプリケーションを AWS で動かすときに注意することを調べてみました。

ALB では gRPC の HTTP/2 通信を負荷分散できない

gRPC はデフォルトで HTTP/2 で通信します。 ALB (Application Load Balancer) は HTTP/2 に対応していますが、対応しているのはあくまでクライアントと ALB のフロントエンド側(リスナー)であり、ALB と EC2 のバックエンド側(ターゲット)は HTTP/1.1 のままです。

Client -> (HTTP/2) -> ALB -> (HTTP/1.1) -> EC2

EC2 側で ALB からの通信を tcpdump すれば一目瞭然で、HTTP/1.1 で通信していることがわかります。

# tcpdump -X dst port 8080 2>&1 | grep HTTP
        0x0040:  2048 5454 502f 312e 310d 0a48 6f73 743a  .HTTP/1.1..Host:
        0x0040:  2048 5454 502f 312e 310d 0a48 6f73 743a  .HTTP/1.1..Host:
        (snip)

gRPC の通信は TCP プロトコルの CLB で負荷分散する

上の記事にもあるとおり、gRPC の通信を負荷分散するには CLB (Classic Load Balancer) で TCP プロトコルを使えば可能です。 TCP プロトコルだとレイヤー 7 の通信には関与しないので、クライアントとサーバで HTTP/2 の通信ができます。

フロントエンド接続とバックエンド接続の両方に TCP (layer 4) を使用する場合、ロードバランサーはヘッダーを変更せずにバックエンドインスタンスにリクエストを転送します。ロードバランサーは、リクエストを受け取った後、リスナー設定で指定されたポートを使ってバックエンドインスタンスに対する TCP 接続を開こうと試みます。

ただし、ALB が登場してから CLB に新機能が追加されなくなっているので、今から新しく作っていくところに CLB を使いたくないという気持ちもあります。

コネクションタイムアウトに注意する

ELB はアイドル状態のコネクションをタイムアウトで切断してしまうので(最大 3600 秒まで伸ばせる)、gRPC の双方向ストリーミングで通信するときはタイムアウトしないように Heartbeat を打つなどの工夫が必要になります。

まとめ

gRPC の通信を柔軟に負荷分散したいのであれば、ELB を使わずに nginx でロードバランサを組んだ方がよい場合もありそうです。

本番環境で運用してノウハウが集まったら、またブログにまとめようと思います。