nginx に実装された gRPC サポートを試してみる

[Announcing gRPC Support in NGINX](https://www.nginx.com/blog/nginx-1-13-10-grpc/) ということで、nginx 1.13.9 で gRPC サポートが入り、HTTP と同じように gRPC ストリームを扱えるようになるようです。めでたい！

grpc_pass ディレクティブが新規に実装され、grpc:// と grpcs:// なバックエンドに対してリバースプロキシを行えるようになるようです。これを使って、

- TLS 終端を nginx にやってもらったり
- 複数のバックエンドを置いて柔軟にロードバランスしてもらったり
- 同一のエンドポイントに複数 gRPC service を設定して、nginx にルーティングしてもらったり

などの設定をすることが可能になるようです。

まだ正式にリリースされているわけではないので、今回は HEAD を持ってきて、リリースに載っている例を試してみます。

## 下準備
今回は適当に EC2 で Ubuntu 16.04 インスタンスを立ち上げました。必要なパッケージをインストールし、https://hg.nginx.org/ から HEAD の tar.gz を持ってきて、ビルドします。

特に configure にオプションを渡さなければ、すべてのファイルが /usr/local/nginx 以下に配置されます。

```
$ sudo apt install build-essential libssl-dev libpcre3-dev
$ curl -O https://hg.nginx.org/nginx/archive/tip.tar.gz
$ tar xvf tip.tar.gz
$ cd nginx-c2a0a838c40f
$ ./auto/configure --with-http_ssl_module --with-http_v2_module
$ make
$ sudo make install

# nginx を foreground で起動
sudo ./nginx -g 'daemon off;'
```

また、実験用の gRPC サーバー・クライアントを準備します。今回は [grpc/grpc の examples](https://github.com/grpc/grpc/tree/master/examples) にある、Python 版を使います。いつもの greeter のやつですね。

```
$ sudo apt install python-pip3
$ sudo pip3 install grpcio-tools
$ git clone https://github.com/grpc/grpc.git

# 試しに動かしてみる
$ cd ~/grpc/examples/python/helloworld
$ python3 greeter_server.py
$ python3 greeter_client.py
2018/03/18 14:46:12 Greeting: Hello world
```

## 普通にリバースプロキシ
```
http {
    server {
        listen 80 http2;  // http2 必須

        location / {
          grpc_pass grpc://localhost:50051;
        }
    }
}
```

greeter_client.py を書き換えて、 `localhost:80` を向くようにし、実行。

```
$ python3 greeter_client.py
Greeter client received: Hello, you!

$ tail /usr/local/nginx/logs/access.log
127.0.0.1 - - [18/Mar/2018:16:46:09 +0000] "POST /helloworld.Greeter/SayHello HTTP/2.0" 200 18 "-" "grpc-python/1.10.0 grpc-c/6.0.0 (manylinux; chttp2; glamorous)"
```

あっさりリバースプロキシできましたね。


## 普通に TLS 終端してもらう
```
server {
    listen 443 ssl http2;
 
    ssl_certificate ssl/cert.pem;
    ssl_certificate_key ssl/key.pem;
}
```

今回は証明書を用意するのが面倒だったので試さなかったが、これだけで普通に grpcs:// を受けられるようになる、とのこと。外部のクライアントとの間は grpcs で通信したいが、内部の Microservice 同士は平文でいい…… といったケースで便利?


## 複数 gRPC Service へのルーティング + ロードバランス + REST API との共存例
別の Service ("DaininkiService") を提供する gRPC サーバーを立ててみて、ロードバランスさせてみます。

```
upstream daininki_service_servers {
    # helloworld.Daininki を提供するサーバーたち
    server localhost:50052;
    server localhost:50053;
}
 
server {
    listen 80 http2;

    location /helloworld.Greeter {
        grpc_pass grpc://localhost:50051;
    }

    location /helloworld.Daininki {
        # HTTP と同じようにロードバランス
        grpc_pass grpc://daininki_service_servers;

        error_page 502 = /error502grpc;
    }
 
    # バックエンドが unavailable だった場合は gRPC 形式でエラーを返す
    location = /error502grpc {
        internal;
        default_type application/grpc;
        add_header grpc-status 14;
        add_header grpc-message "unavailable";
        return 204;
    }

    location / {
        proxy_pass http://rest_api_server;
    }
}
```

大人気である DaininkiService は大人気なので、2つのサーバーを構えて nginx にロードバランスしてもらっていることが分かると思います。また、バックエンドのいずれもが応答できなかったときは、nginx 自身に application/grpc 形式の応答を返してもらうことも出来るようです。

アナウンスのページには REST API のような non-gRPC サービスも同じ endpoint に混ぜることができる、と書いてありますが、この例のように `location /` を記述しても上手く動作しませんでした……。上手く使えた場合、REST API を提供していたアプリケーションを拡張して gRPC も話せるようにするようなケースでは便利ではないでしょうか。


## 今後
gRPC 関連機能の実装は[メーリングリスト](http://mailman.nginx.org/mailman/listinfo/nginx?_ga=2.8044530.1999494643.1521363339-1799080350.1521363339)でのフィードバックを受け、今後の nginx のリリースに含まれるようです。期待ですね！