ハトネコエ Web がくしゅうちょう

プログラミングやサーバー関連、チームマネジメントなど得た技術のまとめ

gRPC x Go x nginx での失敗談

この前こんな gRPC の入門記事を書いたわけですよ。

nekonenene.hatenablog.com

だからもう実際の運用も恐くないだろうな〜って思っていたんですけど、
実際にサーバーに置いて動かそうとしたらいろいろ失敗があったので書いておきます。

動かそうとしたコードはこんな感じなので、時間ある方は読んでもらえば。

失敗1: nginx と gRPC サーバー間は平文通信で大丈夫だった

接続としては

クライアント→(443番)→ nginx がプロキシ役 →(localhost:50123)→ Docker(50123:50051) → Go による gRPC サーバー

となっていて、とにかく上手く行かなかったので、
「もしやローカル通信部分も SSL/TLS 化しないと gRPC の場合は行けないのでは?」と、
自己署名証明書作ったり、無理やりサーバー証明を Docker にreadonlyでマウントしたり、けっこう迷走しました。

が、結果としては TLSで問題なかった
次に書くところに気付かなかったのが一番の問題でした。

失敗2: grpc_pass を用いる

いつも通り proxy_pass を用いて、

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name grpc-test.hatone.net;
# (中略)
    location / {
        proxy_pass http://localhost:50123;
    }
}

と nginx の設定を書いていたんですが、こうじゃないんですね!

www.nginx.com

NGINX 1.13.10 で gRPC サポートをしたよ〜!
という、上の記事に書かれていました。
(※ nginx -v で、お使いの nginx のバージョンが v1.13.10 以上かご確認ください)

proxy_pass ではなく grpc_pass 、これが答えだった・・・!

よって、nginx の設定はこうなります。

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name grpc-test.hatone.net;
# (中略)
    location / {
        grpc_pass grpc://localhost:50123;
    }
}

grpc:// 部分はオプションらしいので書かなくて大丈夫らしいんですが、なんとなく不安なので。
ちなみに localhost 部分で SSL/TLS 通信をおこなう場合は grpcs:// とするようです。(参考: IBM Blockchain Platform にアプリケーションをデプロイする

失敗3: grpcurl の使い方に失敗していた

動作確認には前回も紹介した grpcurl を使っていました。

正解から書くと、以下のようなリクエストで行けるんですが、

grpcurl -d '{"name": "Nanako", "age": 35}' grpc-test.hatone.net:443 hello.Greeter/SayHello

ここに行き着くまでかなりの失敗をしていて、
サーバー側の実装が失敗してるのかと勘違いして時間をけっこう無駄にしました。

例えば package name を helloworld から hello に変えたことを忘れて helloworld.Greeter/SayHello でリクエストしてたり、
:443 を付けずにリクエストして『missing port in address』のエラーを見たり。

grpcurl の README でも :443 を付けているんですけど、
「README ではポート番号を付けてなかったはず……」と間違った記憶をなぜか信じていました(笑)

エラーもちゃんと理解していなく、「ポート未指定の場合のデフォルトのポート番号がなにかあって、
それがファイアウォールで通信拒否されてる?」と間違った仮説を持っていました。

ファイアウォールのログを確認した結果、無く、
調べた末、ふつうに golang 側のエラーだと知りました・・・。
https://github.com/golang/go/blob/go1.11.5/src/net/ipsock.go#L157

失敗4: サーバー側の実装に失敗していた期間があった

サーバー側の実装をいろいろいじっているうちに、
ふつうにミスって動いていない状態になっていたことがありました。

他の人のコードを見てたら

lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))

となってるのあって、「あっ、localhost って付けても動くんだ」と思ったので付けたら
実際は正常動作していなかったという。

lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))

でないとダメでした。

「いろいろ変えたけど、改めてローカルで動くんだっけ……」と試して問題に気付けました。

失敗5: クライアントの実装も難しかった

grpcurl がどうしてもわからなくて、ちゃんとクライアントを実装しようとやってみたんですけど
けっこう難しかったです。

公式の実装例だとクライアント側も証明書を必要としていて、
「うーん、それは難しい……」と。

今回は実運用はまだですので、サーバー側の証明書の正当性を検証するステップはスキップするという方向の実装にしました。

InsecureSkipVerify: true がそれですね。

実運用のときは証明書の正当性の検証も入れたほうがいいとは思いますが、
Let's Encrypt で定期的に更新されるタイプの証明書の検証って運用負荷低く実現できるのか気になります。

まあ、TLS について理解していない部分が多いので、まずはそこの勉強から頑張らねばですね!

→証明書の正当性の検証について書きました

nekonenene.hatenablog.com

おわりに

上で書いた、nginx の gRPC サポート発表の記事が 2018/03/17 だったり、
gRPC が実運用として活発に使われるようになったのはここ1年だと思うのですが、
そのわりには情報が多く、けっこう助かっています。

でも上のように何度もつまずいて大変でした! 丸2日かかってしまいました。連休でよかった。
Golang 自体にまだ全然慣れていないので、gRPC 特有の型なのか Go 付属ライブラリの型なのかわからなくて、コード読むのに戸惑ったり…(笑)

次は、このサーバーに Android アプリからリクエストするような実装を作れればと思います。

→できました!!