👏

Envoyを使ってgRPCをREST APIクライアントから叩かせる

2024/09/21に公開

やりたいこと

SPAからgRPCのメソッドを呼び出したいが、SPA側をgRPCに対応させるのは面倒。
よって、gRPCをREST APIに変換するプロキシを立てたい。
grpc-webやgrpc-gatewayでも実現できるらしいが、勉強のためEnvoyを使う

前提

  • localhost:8080でリッスンしているgRPCサーバーがある
  • gRPCサーバーのメソッドの構成は以下の通り
    • パッケージ: articlespb
    • サービス: ArticlesService
    • メソッド: GetArticles
      articlespb.ArticlesService.GetArticles という形式で呼び出す
  • localhost:51010/articlesでREST APIとして受け入れられるようにする
  • 一連の設定をdocker composeで連携させる

手順

1. protoファイルにREST APIのパスを追加

articles.api.proto
+
+import "google/api/annotations.proto";
+import "google/api/httpbody.proto";
+
service ArticlesService {
  rpc GetArticles(GetArticlesRequest) returns (GetArticlesResponse) {
+    option (google.api.http) = {
+      get: "/articles"
+    };
  };
}

2. buf.yamlに依存関係を追加

buf.yaml
+ deps:
+   - buf.build/googleapis/googleapis

3. protoファイルをビルドしてpbファイルを作成

buf build articlespb/articles.api.proto -o articlespb/articles.api.pb

4. Envoyを立てる

EnvoyのgRPC-JSON transcoderを使って、gRPCサーバへのリクエストをRESTに変換する。
上記のドキュメントを参考にすると、設定ファイルは以下の通り。

admin:
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9901

static_resources:
  listeners:
  - name: listener1
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 51051        # REST APIはポート番号51051でリッスンする
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: grpc_json
          codec_type: AUTO
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: /articlespb.ArticlesService # <パッケージ>/<サービス>の構成にする
                route:
                  cluster: grpc
                  timeout: 60s

          http_filters:
          - name: envoy.filters.http.grpc_json_transcoder
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder
              proto_descriptor: /etc/envoy/protos/articles.api.pb # protobufのディスクリプターのパスを指定
              services:
              - articlespb.ArticlesService
              print_options:
                preserve_proto_field_names: false
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
  - name: grpc
    type: LOGICAL_DNS
    lb_policy: ROUND_ROBIN
    dns_lookup_family: V4_ONLY
    connect_timeout: 60s
    typed_extension_protocol_options:
      envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
        "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
        explicit_http_config:
          http2_protocol_options: {}
    load_assignment:
      cluster_name: grpc
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: app     # gRPCサーバーのホスト名
                port_value: 8080 # gRPCサーバーのポート

5. compose.yamlを用意する

services:
  app:
    build:
      context: .
      dockerfile: docker/Dockerfile
    container_name: app_container
    ports:
      - "8080:8080"
  envoy:
    image: envoyproxy/envoy:dev-d6120f3c769e70c988ddcc5c7e9cbc2737b5f63c
    ports:
      - "51051:51051"
      - "9901:9901"
    volumes:
      - ./envoy.yaml:/etc/envoy/envoy.yaml
      - ./articles/articlespb/articles.api.pb:/etc/envoy/protos/articles.api.pb # envoy.yamlで指定したパスにarticles.api.pbを配置

6. 実行

docker compose up -d --build

無事にlocalhost:51051/articlesを呼び出せた。

Discussion