フラミナル

考え方や調べたことを書き殴ります。IT技術系記事多め

【動かして理解する】PythonでgRPCのQuickStartをやってみよう

この記事ではgRPC公式サイトのQuickStartを実践しながらgRPCの紹介を行なっていきます。

環境

名前 バージョン
OS CentOS Linux release 7.6.1810 (Core)
Python v2.7.5

前提

  • 環境で提示したLinux環境を用意してください

【ハンズオン】gRPCの起動

pipのインストール

まずはPythonのライブラリを管理するpipツールをいれましょう。

curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
 chmod +x get-pip.py 
./get-pip.py 
python -m pip install --upgrade pip

gRPCライブラリのインストール

つづいてgRPCを使うためのライブラリをいれます。

python -m pip install grpcio

gitのインストール

yum -y install git

grpcのサンプルのダウンロード

git clone -b v1.28.1 https://github.com/grpc/grpc

gRPCアプリの起動

通信をするために仮想端末(Teratermなど)を2つ用意し、以下のコマンドで移動してください。

cd grpc/examples/python/helloworld

  • Server側
python greeter_server.py
  • Client側
python greeter_client.py

成功するとClient側にて以下のような表示になります。

client# python greeter_client.py
Greeter client received: Hello, you!

ここまできたらServer側のプロセスをCtrl+cで終了してください。

まだ何がおきたのかわからなくて大丈夫です。次に行きましょう。

【ハンズオン】gRPCの更新

gRPCのprotoの修正

続いてgRPCで呼び出される内容を更新していきます。protoと呼ばれるファイルで規定されていますので修正しましょう。


まずはexamples/protos/helloworld.protoを以下のように変更してください。

〜
// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
+ rpc SayHelloAgain (HelloRequest) returns (HelloReply) {} 
}
〜

protoファイルの更新反映

続いて今変更したprotoファイルを反映するためにコマンドを実行します。

examples/python/helloworldに移動して以下のコマンドを叩いてください。

python -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/helloworld.proto

先ほど作ったhelloworld.protoが読み込まれ、リクエスト・レスポンスのクラスを含むhelloworld_pb2.pyとクライアント・サーバクラスを含むhelloworld_pb2_grpc.pyが再作成されました。

図で表すとこんなことをしています。

f:id:lirlia:20200417015240p:plain

サーバとクライアントコードの修正

再作成によりコードが変わったのでgreeter_client.pygreeter_server.pyも修正が必要です。ここでは新しく増えたSayHelloAgainを呼び出す修正を行なっています。


greeter_server.pyの更新

class Greeter(helloworld_pb2_grpc.GreeterServicer):

    def SayHello(self, request, context):
        return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name)

+   def SayHelloAgain(self, request, context):
+       return helloworld_pb2.HelloReply(message='Hello again, %s!' % request.name)

greeter_client.pyの更新

def run():
    # NOTE(gRPC Python Team): .close() is possible on a channel and should be
    # used in circumstances in which the with statement does not fit the needs
    # of the code.
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = helloworld_pb2_grpc.GreeterStub(channel)
        response = stub.SayHello(helloworld_pb2.HelloRequest(name='you'))
        print("Greeter client received: " + response.message)
+       response = stub.SayHelloAgain(helloworld_pb2.HelloRequest(name='you'))
+       print("Greeter client received: " + response.message)

gRPCアプリの起動

先ほどと同様に起動してみましょう。通信をするために仮想端末(Teratermなど)を2つ用意し、以下のコマンドで移動してください。

cd grpc/examples/python/helloworld

  • Server側
python greeter_server.py
  • Client側
python greeter_client.py

成功するとClient側にて以下のような表示になります。

client# python greeter_client.py
Greeter client received: Hello, you!
Greeter client received: Hello again, you!

ここではこのような通信が行われています。

f:id:lirlia:20200417015455p:plain

gRPCを使ってできること

最初のgRPCのハンズオンでは以下のような動きをしていました。(最初はprotoからの生成は行なっていませんが)

f:id:lirlia:20200417014619p:plain

このようにgRPCを用いるとprotoファイルにて規定した内容でリクエスト・レスポンスが行えるようになります。

まだちょっとよくわからないなという方のためにこんなgRPCを用意してみます。

【ハンズオン】BMIを測定するgPRCを用意してみる

これを実装していきたいと思います。

f:id:lirlia:20200417021814p:plain

まずはexamples/protos/helloworld.protoを以下のように変更してください。リクエストとして身長と体重を受け入れて、レスポンスとしてBMIを返却するようにデータ構造を定義しています。

〜
// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {} 
+ rpc CalcBMI (BMIRequest) returns (BMIReply) {}
}
  
+message BMIRequest {
+  int32 height = 1;
+  int32 weight = 2;
+}
  
+message BMIReply {
+  float bmi = 1;
+}

続いて先ほどと同様にprotoファイルからコードを生成します。

python -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/helloworld.proto

そうしたらgreeter_server.pygreeter_client.pyを修正しましょう。

greeter_server.pyはこちら

    def SayHelloAgain(self, request, context):
        return helloworld_pb2.HelloReply(message='Hello again, %s!' % request.name)
+   def CalcBMI(self, request, context):
+       height = float(request.height) / 100
+       weight = float(request.weight)
+       bmi = weight / (height ** 2)
+       return helloworld_pb2.BMIReply(bmi = bmi)

greeter_client.pyはこちら。今回はpython greeter_client.py 160 60でコマンドを実行すると「身長160、体重60」でBMIを計算するようにします。

def run():
    # NOTE(gRPC Python Team): .close() is possible on a channel and should be
    # used in circumstances in which the with statement does not fit the needs
    # of the code.
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = helloworld_pb2_grpc.GreeterStub(channel)
        response = stub.SayHello(helloworld_pb2.HelloRequest(name='you'))
        print("Greeter client received: " + response.message)
       response = stub.SayHelloAgain(helloworld_pb2.HelloRequest(name='you'))
       print("Greeter client received: " + response.message)
+      import sys
+      height = int(sys.argv[1])
+      weight = int(sys.argv[2])
+      response = stub.CalcBMI(helloworld_pb2.BMIRequest(height=height, weight=weight))
+      print("BMI: " + str(response.bmi))

ここまでできたら

先ほどと同様に起動してみましょう。通信をするために仮想端末(Teratermなど)を2つ用意し、以下のコマンドで移動してください。

cd grpc/examples/python/helloworld

  • Server側
python greeter_server.py
  • Client側
python greeter_client.py

成功するとClient側にて以下のような表示になります。

client# python greeter_client.py 160 60
Greeter client received: Hello, you!
Greeter client received: Hello again, you!
BMI: 23.4375
client# 
client# python greeter_client.py 180 60
Greeter client received: Hello, you!
Greeter client received: Hello again, you!
BMI: 18.5185184479

実際にこれが実装できましたね。イメージつきましたでしょうか。

f:id:lirlia:20200417021814p:plain

まとめ

このようにprotoファイルを作成することで「何をパラメータとして渡せば良いのか、その結果何が帰ってくるのか」を示すことができるのがgRPCの特徴です。

REST APIではAPIを作成する行為と「何をパラメータとして渡せば良いのか、その結果何が帰ってくるのか」というドキュメントを作成する行為は別だったためAPIの更新に手間取ることがありました。

gRPCを利用する場合は必ずprotoファイルを先に作る必要がありますので、protoファイルがドキュメントとなりそのような問題は発生しません。

また、今回はpythonで試しましたが同じprotoファイルから様々な言語向けのコードを生成することができるため、言語を超えて通信を簡単にすることができるようになります。

f:id:lirlia:20200417025230p:plain