じゃあ、おうちで学べる

本能を呼び覚ますこのコードに、君は抗えるか

インフラエンジニアが学ぶと良さそうなgRPCサーバーについて

3-shake にはSreake共有会 という毎週、火曜日と木曜日に担当者が現場で得た知見などを発表する社内勉強会が開催されています。こちらのブログはそれらを変更修正しております。 syu-m-5151.hatenablog.com

元々しようとしていたの話

  1. Go 1.18 の最新情報←Generics深い話とかはもう既出すぎて気になる人は読んでる
  2. Go でのTDD(が実は20周年なので)←書いてる途中で自分が言うべきことなんてないことに気付く
  3. 今後、案件で増えるであろう gRPC についてインフラエンジニアが知っておいても良いと思ったという話 ← 今ここ

TL;DR

  • protobuf (Protocol Buffers) はデータフォーマットで、JSONの役割を置き換えるものです。一方 gRPC は通信プロトコルで、HTTPの役割を置き換えるものです。
  • gRPC をライブラリやツール、トレンドなどを通してgRPCを知る

grpc.io

RPCとは

gRPC がこの世の中に急に爆誕したわけではない。そもそも、サービス間での情報のやり取りをどのように行うかというのは古くからある課題の1つです。その中で利用されているのがRPCがあります。

RPCとは、Remote Procedure Callの略で遠隔手続き呼出しと訳されます。すなわち、別の場所にあるプログラムを呼び出そうというのを目的としています。違うアプリケーションロジックをあたかも自分のアプリケーションの処理と同じように扱えることができるというのも特徴です。

クライアントはサーバーに対し実行する処理を指定するパラメータや引数として与えるデータを送信し、それに対しサーバーはパラメータに応じた処理を実行してその結果をクライアントに返す、というのがRPCの基本的な流れになります。

ちなみに、gRPC以外にもJSON-RPC、XML-RPCなどがあります。

gRPCの歴史

gRPCは、Googleが開発したRPC技術がベースとなっている。Googleでは多数のコンポーネントを組み合わせてサービスを実現しています。

いわゆる、マイクロサービスアーキテクチャでシステムを構築していることで知られるが、これらのサービス間で通信を行うためにgRPCの前身Stubbyと呼ばれる技術が開発されました。

ただ、StubbyはGoogleのインフラ以外での利用は想定しておらず、独自の仕様が多く、Stubby で使用されていた技術とコンセプトが近いHTTP/2 などの技術が登場したことから、GoogleはStubbyにこれらの技術を取り入れてオープン化することを決め、それがgRPCです。

なお、現在ではgRPCはオープンソースで公開されており、現在はLinux Foundation傘下のCloud Native Computing Foundation(CNCF)によって開発が進められています。

grpc.io

gRPC について

gRPCではほかのRPCと同様、クライアントがサーバーに対しリクエストを送信し、サーバーはそれに応じた処理を実行してその結果を返すという、クライアント−サーバーモデルを採用している。gRPCでは以下のような特徴があります。

HTTP/2 による高速な通信

  • バイナリにシリアライズされて送られてくる
  • 小さな容量で転送できる
  • 一つのコネクションで複数のres/reqが可能(柔軟なストリーミング形式)

ミドルウェアの設定でハマった時にはHTTP/2 の問題なのかgRPCの問題なのか切り分ける必要があると思います。

Protocol buffers

gRPCではProtocol Buffersのサービス定義ファイルからサーバーおよびクライアント向けのコードを自動的に生成するツールが提供されており、これを利用することで簡単にサーバーおよびクライアントを実装できるようになっている。

そのため、クライアントとサーバーが異なる言語で実装されていても、問題なく通信を行うことができるようになっている。

クライアント・サーバー間の通信に使用するプロトコル(トランスポート)や、やり取りするデータの表現およびシリアライズ方法については置き換えが可能な設計になっているが、デフォルトではトランスポートにHTTP/2が、データのシリアライズにはProtocol Buffersという技術を使用するようになっており、これをそのまま使用するのが一般的です。

Protocol BuffersはGoogleが開発したデータフォーマットで、バイナリデータを含むデータでも効率的に扱えるのが特徴です。このProtocol Buffersについても、さまざまなプラットフォーム・プログラミング言語から利用できるライブラリが提供されている。

柔軟なストリーミング形式

単方向/双方向ストリーミングRPCに対応している。ちなみに私はこの仕様をきちんと把握してなくて2度辛い思いをしているので記憶の片隅においておいてください。

Unary RPC

1つのリクエストに対して一つのレスポンスを返す一般的な通信です。誤解を恐れぬ言い方をするとREST API のような挙動。

Server streaming RPC

クライアントから送られてきた一つのリクエストに対して、複数回に分けてレスポンスを返す通信方式です。最後のレスポンスを返した後も任意にサーバーの情報を変更に応じてクライアントにその情報を送ることができます。

Client streaming RPC

クライアントからリクエストを分割して送ってサーバーはすべてのリクエストを受け取ってからレスポンスを返します。大きなデータをPOSTしたいときに便利です。

Bidirectional streaming RPC

クライアントからリクエストが送られてきたときにサーバーとクライアントは一つのコネクションを確立しお互いに任意のタイミングでリクエストとレスポンスを送りあうことが可能になります。

他のプロトコルとの違いと連携

Web サービスやマイクロサービスで使われるプロトコルの代表格は HTTP/HTTPS と、それを利用した REST API です。 HTTP は非常に柔軟ですが、渡すデータのスキーマが標準化されていないため、異なる言語間の RPC を実装するのは面倒です。 OpenAPI という REST API 用の IDL もありますが、Protocol Buffers と比較すると記述量が多いです。また、JSONとprotobufの重要な違いとして、protobufはフォーマットがスキーマに依存するという点があります。JSONスキーマがなくても完全なシリアライズ・デシリアライズが可能ですが、protobufのデータをシリアライズ・デシリアライズするにはスキーマ情報が必要です。gRPCは技術的には必ずしもスキーマ依存ではありませんが、実装上はスキーマなしで実装するのは困難です。この技術的制約によりスキーマファースト開発が強制されるのが protobuf + gRPC の強みのひとつです。

よく言われるのが、GraphQL です。GraphQL は Facebook が開発したプロトコルで、HTTP 上で処理されますが REST API とは異なり GET/POST などのメソッドやステータスコードに意味を持たせていません。 特徴はスキーマはデータ構造を定義するもので、標準化されたクエリにより任意のデータを取得可能な仕組みになっていることです。

gRPC がどのようなものか?

gRPC Motivation and Design Principles によればgRPCの基本的なコンセプトとして次のものが挙げられている。

  • サービスはオブジェクトではなく、メッセージはリファレンス(参照)ではない
  • 適切な適用範囲とシンプルさ
  • フリーかつオープン
  • 相互運用性があり、一般的なインターネットインフラ内で利用できる
  • 汎用性がありながら、専用のものと比べてパフォーマンス面で一般に劣らない
  • アプリケーションレイヤーと分離された構造
  • ペイロードを問わない
  • ストリーミングでの情報伝達に対応
  • 同期・非同期の両方に対応
  • 通信の中断やタイムアウトをサポート
  • 確立された通信を処理しつつ新規接続を止めるようなシャットダウンのサポート
  • データ流量のコントロール機能
  • デフォルト実装に対して後からさまざまな機能を追加可能
  • APIによる機能拡張が可能
  • メタデータの交換をサポート
  • 標準化されたステータスコード

このようなものを頭に叩き込んでいると様々な場面でgRPCの設計がどのような思考でそのようになされているか分かる。

gRPC Ecosystem

github.com

gRPCを補完するgRPCエコシステムとして各種サービスが紹介されている。ヘルスチェックやPrometheus での設定などがこちらに紹介されている

gRPC-Web

gRPC-WebによってgRPC通信をWebでも使うことができる。

HTTPサーバーが仲介者として機能することなく、WebアプリがgRPCバックエンドサービスと直接通信できるようになるものです。

またクライアントもバックエンドもgRPCでの実装なので完全なエンドツーエンドのgRPCサービスアーキテクチャを作成できることが利点です。

protoファイルに記述したらあとは、お互い実装ができるので開発も進められやすいです。

github.com

gRPC-Gateway

protoファイルに書かれたサービスの定義を理解し、REST APIに変換できます 。gRPC-GatewayだけでRESTfulなAPIを受け取れます。また、protoファイルからswagger.jsonを自動出力してくれる機能も備わっており、ドキュメント生成に関しても十分です。

grpc-ecosystem.github.io

envoy

gRPC-GatewayとenvoyはどちらもJsonをgRPCに変換してくれる機能を持ち合わせています。JSONを変換してくれるだけよくGolangでの実装だったら、gRPC-Gatewayでいいのかなと思いますがそれ以外にはEnvoy にはさまざまな機能があるので一気に全部やってしまいたい方にはEnvoyの利用を考えても良いのかな?と思います。

www.envoyproxy.io

gRPC をライブラリやツールについて

インフラエンジニアがgRPC に関わる時は開発というより運用や保守に関してだろう。なので、今回、紹介するツールもそれらに沿って紹介したい。ツールの使い方を調べれば自ずとgRPCの輪郭が見えてくるかと思います。Awesome gRPC はgRPC に 関するキュレーションを行うリポジトリ。大体のツールはここを確認すれば良い。

https://github.com/grpc-ecosystem/awesome-grpc

grpc_cli

grpc/command_line_tool.md at master · grpc/grpc

gRPC の公式リポジトリに同梱されている grpc_cli は公式の gRPC クライアントツールといえますが、最低限の機能しか備えていません。例えば他の gRPC クライアントツールではほぼ実装されているメタデータの送信ができない、JSON 形式でのリクエスト内容の記述を受け付けられないといった問題があります。また、インストールするためにはソースコードからビルドする必要があり煩雑なのであまり、使われていません。

gRPCurl

https://github.com/fullstorydev/grpcurl

最も使われている gRPC クライアントツールです。現在も活発にメンテナンスされています。機能面でもたいていのユースケースは網羅されており、機能の不足で困るようなことはほとんどないでしょう。

prototool

https://github.com/uber/prototool

PrototoolUber Technologies によって開発された Protocol Buffers のユーティリティツールです。Prototool には gRPC のエンドポイントを呼び出せるサブコマンドが付属しています。ただし、このサブコマンドは fullstorydev/grpcurl に大きく依存しており、実質 gRPCurl のサブセットとなっています。現在は Protocol Buffers のユーティリティツールとして Buf を推奨するしています。

Buf

https://github.com/bufbuild/buf

Protocol Buffers のユーティリティツール 戦争に勝ち抜いたと言っても良い buf は自動ファイル検出、正確なlintとbreaking checkersの構成を選択することができたり、エラー出力はどのエディターでも簡単に解析可能(vs Code はさまざまなツールが動くが、vim はこれぐらいしか、プラグインがうまく動かない)、コンパイルの高速化、protocのプロトコルプラグインとして使用する。

gRPCUI

https://github.com/fullstorydev/grpcui

grpcuiは、ブラウザ経由でgRPCサーバと対話するためのコマンドラインツールです。Postman のようなものですが、REST ではなく gRPC API のためのものです。

evans

gRPC クライアントツールです。REPL モードで手軽に手動テストを行えますのでデバッグの時にあるとめちゃくちゃ便利です。

https://github.com/ktr0731/evans

JSON-to-Proto

JSONを即座にProtobufに変換してくれるツールになります。

JSON-to-Proto

次回予告:gRPC を使ったアプリケーション開発の流れ

それでは、gRPCを使ったアプリケーション開発を行う場合、実際にどのような手順を踏めば良いかを紹介していこう。この場合の基本的な流れは次のようになる。

  1. Protocol Buffersを使ったサービスの定義
  2. サービス定義ファイルからのコードの生成
  3. 生成したコードに独自の自前の実装を追加する

上記に関してはハンズオンなどで実施していきたいと思います。 また、2022年4月27日に「Protocol Buffers/gRPC を安全に書き進めるためのエトセトラ」と題してOWASP Fukuoka Meeting #6にて登壇いたしますー

owasp-kyushu.connpass.com

死霊👻 はこちらです

speakerdeck.com

参考文献