envoy の gRPC proxy に関する便利機能

Adventarを支える技術 Advent Calendar 2019 の6日目です。

今日は envoy の gRPC に関する便利機能について紹介しようと思います。

gRPC-Web proxy

4日目の記事でも書きましたが、今回は gRPC-Web の proxy レイヤーとして envoy を利用しています。envoy で gRPC-Web の機能を有効するのは簡単で、HTTP filters に envoy.grpc_web を書いて、ヘッダの設定をするだけです。

これだけで gRPC-Web を受けて、upstream のクラスタに gRPC でリクエストするようになります。超簡単。他に書くことがありません。

gRPC-JSON transcoder

5日目の記事に書きましたが、Adventar では、gRPC と JSON API の両方を envoy によって実現しています。JSON API で受けて、upstream の gRPC に流すのは、envoy の gRPC-JSON transcoder という機能を使っています。

https://www.envoyproxy.io/docs/envoy/latest/api-v2/config/filter/http/transcoder/v2/transcoder.proto

これは grpc-gateway と同じようなものです。Protocol Buffers の定義から生成したスキーマを envoy が読んで、gRPC をバックエンドにした JSON API を提供します。例えば、次のような Protocol Buffers を定義します。

syntax = "proto3";

package adventar.v1;

import "google/api/annotations.proto";

message GetCalendarRequest {
  int64 id = 1;
}

message Calendar {
  int64 id = 1;
  string title = 2;
  ...
}

service Adventar {
  rpc GetCalendar(GetCalendarRequest) returns (Calendar) {
    option (google.api.http) = {
      get : "/v1/calendars/"
    }
  }
}

optionで指定しているのは、grpc-gateway でも使われているもので、 gRPC と JSON APIマッピングを定義するためのものです。

envoy 側の設定は簡単で、envoy.grpc_json_transcoderを設定するだけです。

https://github.com/adventar/adventar/blob/f580de20510f9debe6356a5ad193c4532d8f6a0d/api-server/envoy/envoy-prod.yaml#L46-L49

ここで指定ているproto.pbは以下のようなコマンドで出力しています。

$ protoc \
  --include_imports \
  --include_source_info \
  --descriptor_set_out=../api-server/envoy/proto.pb \
  adventar/v1/*.proto

https://github.com/adventar/adventar/blob/f580de20510f9debe6356a5ad193c4532d8f6a0d/protobuf/protoc.sh#L6-L10

これで envoy と gRPC サーバーを起動すると、以下のようにアクセスすることが可能になります。

$ curl http://localhot:8080/v1/calendars?id=1 | jq
{
  "id": 1,
  "title": "xxx",
  ...
}

クライアント側が gRPC を使えなくても、普通の HTTP で通信できるので、様々な場面で便利な機能だと思います。

auto_mapping

gRPC-JSON transcoder は便利なのですが、Protocol Buffers の定義に

option (google.api.http) = {
  get : "/v1/calendars/"
}

のようなアノテーションを書かないといけないのが面倒です。これを解消するのに、envoy v1.11.0 から auto_mapping という機能が追加されました。

https://github.com/envoyproxy/envoy/pull/6731

これはgoogle.api.httpの定義を書かなくても、POST /<package>.<service>/<method>という URL でアクセス可能になるという機能です。

設定は簡単でauto_mapping: trueと書くだけです。

https://github.com/adventar/adventar/blob/f580de20510f9debe6356a5ad193c4532d8f6a0d/api-server/envoy/envoy-prod.yaml#L50

これで以下のようにアクセスできます。

$ curl -X POST -d '{"calendar_id":1}' https://localhost:8080/adventar.v1.Adventar/GetCalendar | jq
{
  "id": 1,
  "title": "xxx",
  ...
}

Adventar ではこの機能を使っているので、google.api.httpの定義は書いていません。

https://github.com/adventar/adventar/blob/f580de20510f9debe6356a5ad193c4532d8f6a0d/protobuf/adventar/v1/adventar.proto

(余談)google.api.http を捨てられていない理由

google.api.httpの定義は書いていません、と言いつつ、実は一番下にgoogle.api.httpの記述があるのに気づいたと思いますが、これは gRPC サーバーの前に立てている AWS Application Load Balancer(ALB) のヘルスチェックが GET でしかできず、auto_mapping は POST にしか対応してないので、しかたなく書いています。

ちなみに ALB は今現在 HTTP/2 の pass through に対応していないので、普通は gRPC の前段に置くことはできなくて色々と面倒なのですが、今回は gRPC-Web と JSON API しか通しておらず、直接 ALB が gRPC をしゃべる必要がないので ALB が利用できて便利です。このあたりのインフラの概要については後日書く予定でいます。

明日は Firebase Authentication について書く予定です。