gRPCって何だったっけ?

奥山裕也氏(以下、奥山):LINEの開発4センター、 Official Account開発室の奥山が、「OA DevにおけるgRPC」についてお話しします。

まず簡単に自己紹介しますと、私は2019年の4月に新卒で入社しました。Official Account開発室というチームで、LINE公式アカウント関連の新規サービスを主に開発しています。コードは、サーバサイドで主にKotlinを書いています。また、line-bot-sdk-pythonというMessaging API SDKのメンテナーもしています。

今日は、最初に「gRPCって何だったっけ?」という話をして、そのあとOA Devチームにおいて、gRPCをどう使っているかという話をしていきます。

それではまずgRPCについてですが、たぶんこの中でも、実際にgRPCを使ったことがあるという人や実際のサービスで使ったことがある人もいるかもしれません。gRPCというのはGoogleが開発したRPCフレームワークで、現在はCloud Native Computing FoundationというCNCFのプロジェクトになっています。

gRPCと言ったときに一番初めに思い浮かべることは、Protocol Buffersを使っていることかなと思います。このIDLを使うことによって、スキーマファーストな開発ができます。protoファイルを見れば、仕様があるという状態になっていて、また、特定の言語に依存しないメッセージ、サービスを定義できます。

これによって、JavaやGo、Python、JavaScriptといった他の言語のクライアントを簡単に生成でき、他の言語を使っているチームとのやり取りが簡単にできるといったメリットがあります。

あとは、JSONとの相互互換がちゃんと定義されている点や、前方互換性/後方互換性を保つ変更の仕方が明確に決まっているので、メッセージの型を変えるとかメッセージのフィールドを増やすときに、ちゃんと互換性を保ちながら変更することができます。

JSONやXML、MessagePackなど、いろいろあると思いますが、そういったものよりもコンパクトで効率的なコーディングができます。また、HTTP/2でデータを送受信していたり双方向通信をしていたりなどもありますが、たぶんおそらく多くの人にとって一番特徴的なのがProtocol Buffersを使うことだと思います。

LINEはRPCをよく使う会社

それでは、実際にLINEではgRPCをどう使っているかというと、私たちが今新規開発しているサービスや、既にLINE公式アカウントのバックエンドでも使われています。

あとは、去年のLINE DEVELOPER DAYで発表があったLINE LIVE Commerce、LINE MUSICやLINE Financialなどサーバ間の通信で使われることが増えてきました。

また言語としては、Javaの会社なので、Java、Kotlin、Scalaでの使用が多いです。GoやPythonを使っている事例もあります。JavaScriptでgRPC-Webを使っている事例もあります。

Thriftは聞きなれない方も多いかもしれませんが、これはFacebook製のRPCフレームワークで、ほぼgRPCのようなものです。実際に、既に社内の多くの箇所で利用されており、そういう意味では、LINEというのはRPCをよく使う会社です。

実際にgRPCのアプリケーションをデプロイするにあたって、どういうことが必要かなと考えてみると、たぶんこの辺のことが最低限必要かと思います。ログやメトリクスや分散トレーシングはマイクロサービスでは必要になってきますし、サービス間通信でのサーキットブレーカーやリトライ、ロードバランシングも必要です。

gRPCでは、「ロードバランシングはどうするの?」がよく話題になります。また、gRPCだとcurlが使えません。そうなると「デバッグツールはどうするの?」となります。他にも「gRPCで他のサービスとのエラーの伝播、エラーハンドリングはどうするの?」「ドキュメントをどうやって作って他のチームと共有するの?」といったことは気になると思います。そういうわけで今回は、この辺の話をしていこうと思います。

実際にgRPCをどう使っているのか

私のチームで、gRPCをどう使っているのかを紹介します。これが、私たちのシステムの本当にざっくりとしたオーバービューです。私たちが今作っているこの新規サービスのアーキテクチャで、BtoBサービスということだけしか言えないんですけど、BtoBなので、パートナーのサーバからリクエストが来たりとか、普通にブラウザからリクエストが来ることもあります。

あとは社内の他のチームからリクエストが来ることもあり、しかもRESTに限らず他のチームや外部のサーバからgRPCで来ることもあるので、RESTとgRPCの両方をちゃんと受けられるようになっています。

最初にRESTとgRPCに関係なく、L4LBで受け取って、それをNginxに流して、Nginxの部分でリクエストのパスを見て、gRPCかRESTかを流すような感じになっています。そのあとに、それぞれにプロキシが2つあるんですけど、こちらがgRPCを受けてgRPCを流すというプロキシで、こっちはRESTを受けてgRPCに変換するプロキシです。それぞれのプロキシでロードバランシングをしています。最後に、このアップストリームのgRPCサーバに流していると。

1つ特徴的と思えるかもしれないのが、ここのREST-gRPCのプロキシの部分は、私たちはEnvoyとかgrpc-gatewayは使わずに実装することにしました。

というのも、社外から呼ばれるということで、やっぱりEnvoyやGoはまだ運用に不慣れなところがあり、それでかつ柔軟な要望に応えるようにしたいので、今回はSpringというJavaのフレームワークを使って実装することになりました。システム全体のオーバービューでした。

それではgRPCサーバをどう実装しているのかという話をしたいと思います。今回、私たちはgRPCのフレームワークとしてLINE製のマイクロサービスフレームワークであるArmeriaを使いました。Armeriaは社内のコアな部分で既に実績があり、信頼できるフレームワークです。

とくにgRPCとJavaやSpringの界隈でよく使われているライブラリとしては、LogNet/grpc-spring-boot-starterやyidongnan/grpc-spring-boot-starterがありますが、それらに比べてArmeriaのほうが開発が活発でした。

あとは社内製ということで、フィーチャーリクエストのしやすさや、コミュニケーションの取りやさがありました。決定的な理由としては、ArmeriaのgRPC実装というのは、公式のgrpc-javaのインターフェースを使って実装されており、gRPCのエコシステムから逸脱せずにgRPCサーバーを実装できるのがすごく魅力的でした。

実際にバリデーションのライブラリなど、grpc-javaを前提としたライブラリはけっこう多いんです。そういうときに困ることがないので、私たちはArmeriaを使いました。

Armeriaではクライアントサイドロードバランシング、サーキットブレーカー、リトライ、分散トレーシングといった定番の機能が簡単に使えます。またDocServiceというSwaggerのような機能もあります。これらの便利な機能が簡単に使えるので、私たちはArmeriaを使ってgRPCサーバを作ることにしました。

Armeriaによって多彩な機能が簡単に使える

実際にどんな感じでgRPCのサーバが実装できるの? という話なんですけど、この例のコードは少し省略しているのですが、基本的には自分のbindableServices(gRPCのprotoのコンパイラが出したgRPCサービスのインターフェースの実装)をBuilderに渡すだけです。

あとはここの部分でシリアライゼーション、Content-typeに応じてJSONで返すとか、そういうおもしろい特徴があったり、アクセスログをちゃんと取りたかったらここで設定をしたり、分散トレーシングの設定も本当に1行でできてしまいます。こんなふうに、本当に簡単にログやメトリクス、分散トレーシングの設定ができます。

gRPC クライアントも簡単に実装できます。今回私たちはクライアントサイドでロードバランシングをしたのですが、ロードバランスするときのサービスディスカバリーについては、DNSで行いました。builderの引数にDNSレコードを与えるだけで設定できます。

さらにロギングやZipkin、メトリクスの設定も1行で書けてしまいます。またサービスディスカバリーについては、ZooKeeperやEurekaでも行えるようになっています。

もう1点、ArmeriaにはDocServiceという、Swagger UIのような機能があると話したと思うのですが、これを使うことによってProtocol Buffersで定義したサービスやメッセージを一覧できます。

例えばHelloServiceというサービスがあって、それのHello()やBidiHello()、こういうメソッドが見られるとか、そのメソッドのリクエストの型とかを簡単にチェックできます。

さらにDocServiceにはデバッグ機能もあります。これは実際にgRPCのリクエストをサーバに投げてそのレスポンスが返ってきているという例なんですけど、ブラウザからgRPCのデバッグができるというのが本当に魅力的でした。このようにしてロギングからメトリクス、分散トレーシングとかリトライ、ロードバランシング、デバッグはArmeriaで簡単に解決することができました。

エラーハンドリングについて

では残りのエラーハンドリングやドキュメンテーションはどうやっているかというと、もしかしたらgRPCをちょっと使っただけだと気付かない話かもしれないのですが、gRPCはRESTみたいにエラーのペイロードをHTTPボディに含めて返すことができません。なので、これら4つの方法のどれをとるかで悩みました。

エラーを返したいとき、一番基本的なのは1個目のgrpc-statusとgrpc-messageだけで返す方法があります。これでもいいんですけど、当然他のチームとのやり取りが出てくると、ちゃんとエラーメッセージを返してあげないといけないですよね。そうなってくると1個目の方法は却下となります。

次は2個目の方法で、すべてのレスポンスのフィールドにエラーのフィールドを追加する。これはすごくシンプルでわかりやすいのですが、すべてのレスポンスにエラーのフィールドを追加しないといけなくて、ちょっと煩わしかったり、あとは0から15まであるgRPCのステータスを常に0で返さないといけなくなってしまうので、あまりよくないですね。

そういうことで、私たちは下の、とくに一番下の自分で定義したメッセージをメタデータに含めて返すという方針を取りました。これはどうやるかというと、最初に自分で返したいエラーの定義を作ります。

今回はこのErrorであって、そこにErrorDetailのリストを返すかたちなんですけど、こんな感じで好きなエラーを定義して、サーバでエラーが起こった場合は、メタデータを作ってメタデータに自分が返したいエラーのペイロードをputして返す。このときはProtoBufでバイナリエンコーディングしたものをメタデータに含めて送り返しています。

ただ、この方法の1点面倒くさいところは、クライアントで受け取るときは自分でデシリアライズしてあげないといけなくて、これは仕方がないのかなと思います。この辺はたぶんあまり言語によらず、Goで出てきても同じような方法になるかと思います。エラーハンドリングについてはこんな感じです。

protoc-gen-docsでドキュメントを生成

最後に、私たちが実際にドキュメンテーションをどうしているか。protoc-gen-docsというツールがあったので、これを使って社内の他のチームとかに共有するドキュメントを生成していました。こんな感じで、gRPCのコメントだったり、オプションとコメントを読み取って。ここの部分がコメントで、ここがオプションですかね。

ここがRPCのリクエストとレスポンスの型を表示したり、という感じで、protoから簡単にドキュメントを生成できました。

こんなふうに私たちのチームでは、ロギングだったりデバッグするこの辺の一連のArmeriaを使うことで解決させました。エラーハンドリングについてはメタデータにエラーペイロードを含めるという手法をとったり、ドキュメンテーションについてはprotoc-gen-docsというツールを使ってやっていきました。

スキーマを定義するとレビューも楽

実際にgRPCを使ってどうだったかという、ざっくりとした感想なんですけど、最初に実装ではなくてスキーマを定義してからやっていくので、やっぱりコードレビューがしやすかったです。ただ1点不満があるとしたら、protoファイルで関数名やフィールド名を変更したとき、それがIDが自動で変更を反映してくれなくて、ソースコードのほうも名前を変えるのは面倒くさかったりしました。

HTTP/2を使っているということで、社内ではまだ新しい技術だと思うのですが、その分社内LBとかの設定がちょっと面倒くさかったりしました。あとはエコシステムとしましては、CLIからリクエストしたいときは、gRPCurlというツールがあったり、ドキュメント生成ツールがあったりとか、最低限なものは揃っているかなという感じはありました。

ただGUI Clientがまだまだリッチなものがないねとか、まだまだなところもありました。

最後に覚えておいてほしいのは、LINEでも少しずつgRPCがプロダクションで使われだしています。とくにJavaユーザーの方であれば、Armeriaを使えばプロダクションレディなgRPCサーバが簡単に作れます。あとはgRPCを使っていくにあたって、エラーハンドリングはどうするかなとなったとき、RESTより少し面倒くさいということは覚えておいてください。

スキーマを定義するというのは、レビューも楽だし、ドキュメントの生成もすごく楽だという話でした。以上です。

質疑応答

司会者:どうもありがとうございます。ここからは質疑応答のセッションです。1つ目ですね。「gRPCのツールにいろいろな機能をもたせると便利だけど、それぞれの機能が密結合してしまって、後で変更しにくくなりませんか?」ということですが、いかがですか?

奥山:ツールというのはたぶんフレームワークのことかと思うのですが、それは確かに一理あるというか、ArmeriaというgRPCサーバを作っているのですが、実際はgRPCサーバだけじゃなくて、RESTのサーバとかも別に作っていたりして、それぞれでいちいちロギングだったり諸々の設定をしないといけないのが、三度手間だったりするのはありますね。

たぶんこれは、「Envoyとか、そういうプロキシを置いて、それにいろいろ任せちゃえばいいじゃん」みたいな話かと思いますが、それは確かにそうなのですが、Envoyを運用するコストと、やりやすい方法どちらを取るかという話なのかなと思います。

司会者:はい、ありがとうございます。次に「サーバープッシュなどのstreamingのRPCは利用していますか?」。

奥山:今は使っていないし、将来的にも使う要求は出ないと思っています。

司会者:なるほど。はい、ありがとうございました。