概要
gRPCはHTTP/2プロトコルをベースとしたRPCです。
なのでコネクションが永続化され、その中で複数のストリームがリクエスト・レスポンスを取り扱うため負荷分散に注意する必要があります。
今回はEnvoyというgRPCに向いたproxyを使うことでそれを実現します。
環境
- envoy 1.7.0
成果物
今回のソースはこちらにあります。
AWSでの問題点
AWSのロードバランサーは主にCLB、ALB、NLBがあります。
しかしながらそれぞれ
- NLBはL4のロードバランサであるため、コネクションを分散することはできてもリクエストレベル(ストリーム)で負荷分散ができない
- CLBのL4モードも同様
- ALBはL7だが、HTTP/2の転送がそもそもできない
という問題を持つため、gRPCでの負荷分散に向いていません。
そこでgRPCで通信する場合はELBを使わずにEnvoyを用いて分散する方針が良いです。
Envoy
Envoyはマイクロサービス化を進める上で複雑になるネットワークレイヤの問題点を一手に担ってくれるL7 Proxyです。
今回はgRPCの負荷分散について述べますが、それ以外にも
- Service DIscovery
- Circuit Breaker
- Tracing
- Metrics
- 自動リトライ
- 通信の暗号化
など様々なマイクロサービスの問題を扱えるため、アプリケーション側の実装がシンプルになります。
FrontProxyによる負荷分散
ドキュメントにあるイメージ図は以下です。
ちなみにこの構成が簡単に試せるdocker-composeも公式のリポジトリで提供されてます。
gRPC通信のサーバの用意
こちらで紹介したgatewayサーバを用いて検証してみます。
通信経路としては
gateway-server <-> FrontProxy(envoy) <-> grpc-server
となります。
docker-compose.yml
docker-composeを用意します。
ポイントは
networks: front-proxy: aliases: - backend
の部分です。
aliasesをつけることで、コンテナをスケールアウトしてもそのalias名でアクセスすれば内部DNSがスケールアウトした分のコンテナのIPも返してくれるので、envoyの設定を弄らずにスケールアウトに対応できます。
front-envoy.yaml
envoyのconfigを用意します。
大まかなポイントとしては
- HTTP/2通信なので
http2_protocol_options
を付ける - hostsは先程のalias名に
- 調査しやすいよう
access_log
を付ける health_checks
を付ける
です。
動作検証
起動します。
$ docker-compose up -d
管理画面
起動するとenvoyの管理画面が見れます。
こんな感じの簡素なやつです。
/clusters
このようにbackendのgrpcサーバが1台登録されていることが分かります。
IPは172.18.0.4
のようですね
疎通確認
$ curl localhost:3000/alive {"status":true
ちゃんと疎通できています。
docker-composeのenvoyのログも
front-proxy | [2018-06-27T01:01:19.585Z] "POST /gateway.AliveService/GetStatus HTTP/2" 200 - 5 7 11 8 "172.18.0.1" "grpc-go/1.13.0" "1af794e4-2cd4-4c92-b575-205d3060c057" "front-proxy:8000" "172.18.0.4:8080"
このようにリクエストをハンドリングできていることが分かります。
スケールアウトして分散されるか検証
次にbackendのgrpcサーバをスケールアウトしてみます。
docker-compose up --scale grpc=3 -d
/clusters
最初の172.18.0.4
に加えて、
- 172.18.0.5
- 172.18.0.6
が自動で追加されました。aliasによるアクセスのおかげです。
負荷分散されるか検証
abで1000リクエストほど投げてみます。
$ ab -n 1000 http://localhost:3000/alive
すると/clusters
のstats情報が更新され、以下のようにリクエストがバランシングされていることが分かります。
まとめ
Envoyを使うことでgRPCの負荷分散を実現することができました。