gRPC ちょっと理解した
gRPC に入門した。favicon のマスコット?めっちゃかわいいですね。。
きっかけは 3rd Party tool をきっかけに Terraform のソースコードを少し嗜んだ話 · the world as code という記事で触れたように、 Terraform 0.12 で Terraform Core と Provider が gRPC で通信するようになったため。知ってなてくは Terraform を使えないわけでも Provider を書けないわけでもないのだが、昨今よく聴く単語だし、 microservices などにも必要な要素技術なので入門してみた。
教材
ちょうど WEB+DB PRESS の最新号で特集されていたので、主にこれを使った。
技術評論社
売り上げランキング: 13,606
あとは公式のドキュメント。英語で簡単な Tutorial が書かれているほか、 gRPC のレポジトリ内にある examples というフォルダに言語別の実装例が書かれていて参考になった。
学んだこと
写経
gRPC とは何か、などとここで改めてまとめても仕方ない感があるので、それについては割愛する。 WEB+DB PRESS を読もう。
当該号の特集では、 gRPC の4種類の通信方式(Unary, Server streaming, Client streaming, Bidirectional streaming)それぞれの簡単な実装サンプルと、実践例として gRPC を用いたタスク管理サービスの作り方が掲載されており、これを適宜写経しながら進めた。コードはいずれも GitHub で公開されている。
Ruby での再実装
単に写すだけというのもつまらないので、ちょっとした応用もやってみた。 gRPC には遣り取りするデータのシリアライズフォーマットを定義した Protocol Buffers (protobuf) を元として、サーバ / クライアントの実装を様々な言語で生成することができるという特徴がある。そこで、誌面のサンプルはサーバ / クライアントともに Go で書かれていたが、クライアント側を Ruby で書いてみることにした。
対象にしたのは Server streaming gRPC のサンプルコード。主に公式の Ruby 向け Tutorial を見つつ、先の examples 内の実際のコードも見ながら進めたが、以下の記事も参考にした。
まず、必要な gRPC 関連の gem をインストール。
$ gem install grpc
$ gem install grpc-tools
続いて protobuf から Ruby 用のクライアントコードを生成しようと思ったのだが、サンプルコードでは protobuf の package が file
という名前で定義されており、これをそのまま Ruby のコードに変換すると、 File class と衝突する形になってしまった。そのため package 名を filedl
という名前に変更してからコード生成を実行している。ネーミングセンスは気にしないことにした。なお、このコマンドは元のサンプルコードの downloader
というフォルダ内に ruby
フォルダを掘り、その中で実行している。
$ mkdir lib
$ grpc_tools_ruby_protoc -I ../proto --ruby_out=lib --grpc_out=lib ../proto/filedl.proto
Ruby のコードは以下のようになった。元々の Go によるサンプルコード(go-grpc-basics/main.go at master · vvatanabe/go-grpc-basics)とだいたい同じ動きをするようにしている。
this_dir = File.expand_path(File.dirname(__FILE__))
lib_dir = File.join(this_dir, 'lib')
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
require 'grpc'
require 'filedl_services_pb'
def main
stub = Filedl::FileService::Stub.new('localhost:50051', :this_channel_is_insecure)
filename = ARGV[0]
resps = stub.download(Filedl::FileRequest.new(name: filename))
blob = ""
resps.each do |r|
blob << r.data
end
p "done " + blob.size.to_s(10) + " bytes"
file = File.open(filename,"w")
file.puts blob
file.close
end
main
これで ruby client.rb test
などと実行することで無事に動作が確認できた。異なる言語間でシリアライズされたデータの遣り取りがだいぶ簡単に実装できてなるほどねぇという感じはした。あと Ruby を3年ぶりぐらいに書いたのでところどころ文法にビビったりした。 <<
演算子とか。
成果物はすべて chroju/learning_grpc に上げておいた。
Terraform と gRPC
一通りドキュメントを見てはみたが、正直そんなにガッツリ gRPC 使ってどうこうみたいなことは書いていないような気がする。 RPC 使ってますよ(これは Terraform 0.11 以前から同様)という話と、 "Although technically possible to write a plugin in another language, almost all Terraform plugins are written in Go." という一文が Writing Custom Providers - Guides - Terraform by HashiCorp にあるぐらい。将来的に Go 以外でも書けるようにするんですかね。どうなんですかね。
コードで言えば terraform/docs/plugin-protocol · hashicorp/terraform に protobuf の定義ファイルがある。また、 Provider は terraform/plugin
の Serve
という関数を main.go
に書く必要があり、この関数が Terraform Core に Provider を渡しているようなのだが、 この実装 を見ると、現時点では gRPC 向けに既存のメソッドを変換したりする処理が入っているみたい。 Provider を書く際には、 gRPC を意識する必要は基本的にはなさそう。