LiBz Tech Blog

LiBの開発者ブログ

GoとDockerでLet's try gRPC

はじめに

こんにちは!エンジニアの渡邊です。早いもので、11月でLiBに入社して丸1年がたちました。 このブログへの投稿も4回目になります。

前回の
とってもRailsライクなサーバーレスフレームワーク「Ruby on Jets」を本番環境に導入した話
では、jetsの開発者であるtongueroo氏や、Rubyの生みの親まつもとゆきひろ氏をはじめ、多くの方にシェアをしていただき大変励みになりました!みなさんありがとうございました。

今回はGoogleが開発したRPCフレームワークgRPCについて書こうと思います。

f:id:kkwatanabe:20191106144030j:plain:w500

gRPCとは

Google社内で使用されているstubbyというRPC(Remote Procedure Call)フレームワークをオープンソース化したものです。

gRPCの特徴

  • Protocol Buffers(xml,jsonのようなインターフェース記述言語)を利用した高速かつ低用量、異なる言語間でも型保証された通信
  • サーバ/クライアント用ソースコードの自動生成
  • 多言語対応(C++, Java, Go, Python, Ruby, Node.js, C#, Objective-C, PHP, etc..)
  • 通信プロトコルHTTP/2による通信
    • Streamingを用いた双方向通信(従来の通信は1Request-1Response)

上記のような特徴から、gRPCはマイクロサービス化されたシステム間の接続やデータ転送についての課題を解決することができ、効率良く通信を行うことができます。

f:id:kkwatanabe:20191106164527p:plain:w500

gRPCが解決するマイクロサービスの課題

  • 複数のシステム間でインターフェースが統一されている必要がある

    • システムは頻繁に変更があるものなので全システム間の整合性をとることが難しい

      -> ProtocolBuffer(.protoファイル)からサーバー/クライアントのソースコードを自動で生成でき、言語も複数選択できる

    • 複数のシステムの仕様を把握するためのドキュメント(仕様書, wiki, Swagger, etc..)更新漏れ、記載ミス

      -> サービス間の通信はprotoファイルから生成されるため、protoファイルが正しい仕様になる(ソースコードはprotoファイルを元に生成されるため、漏れやミスが発生しない)

  • APIリクエストが頻発するためパフォーマンスが劣化しやすくなる

    • 複数のシステムに分散されたリソースをそれぞれ取得しに行く必要があるためどうしてもリクエスト回数が増える

      -> HTTP/2では一度のTCPのコネクションで複数のリクエスト, レスポンスを取扱うことができる

    • HTTP/1では一度のコネクションで取得できるリソースは一つという仕様(ブラウザ側で複数のTCPを同時に貼るとしてもコネクション数に制限がある)

      -> HTTP/2での通信のため、コネクションは捨てられることなく初回接続時のコネクションを使い続けられる

f:id:kkwatanabe:20191106164702p:plain:w200

gRPCの課題

当然メリットばかりではなくデメリットもあります。 通信規格がHTTP/2ということは、対応していないブラウザやロードバランサはリクエストを受けることが出来ないということです。

ブラウザに関してはgrpc-gatewaygrpc-webなどのリバースプロキシを行ってくれるライブラリを利用すれば問題ないのですが、
サービス間通信はHTTP2プロトコルで、ブラウザとサーバ間通信のみHTTP1プロトコルになってしまうので混在することに違和感を覚える人もいるのではないでしょうか。

gRPCサーバーの動作確認もやや面倒です。 従来のAPIのような動作確認はcurlで行えましたが、gRPCではcurlは使えません。
(最近はgRPCでもcurlライクに動作確認ができるgRPCurlや、gRPC用のGUIクライアントであるgRPC UIというツールがあるようです)

f:id:kkwatanabe:20190711145732p:plain:w200

Let's try gRPC

※コード周りは下記の記事のものを利用させていただきました!

Goで始めるgRPC入門
GolangでgRPCを試してみる

f:id:kkwatanabe:20191106164848p:plain:w200

1. 準備

  • gRPC
  • protoc(.protoファイルからコード生成をするコンパイラ)
  • protoc-gen-go(protocのGo用プラグイン)

上記の3つをインストールします。

gRPC

$ go get -u google.golang.org/grpc

protoc

OSによって異なります。こちらからインストールしてください。

protoc-gen-go

$ go get -u github.com/golang/protobuf/protoc-gen-go

Dockerfile

今回はDockerを使用しますのでDockerfileとdocker-compose.ymlも用意します。

FROM golang:1.13.1  
  
RUN apt-get update && apt-get install -y unzip  
  
# Install protobuf  
# @see https://github.com/yoshi42662/go-grpc/blob/master/server/Dockerfile  
RUN mkdir -p /tmp/protoc && \  
  curl -L https://github.com/protocolbuffers/protobuf/releases/download/v3.10.0/protoc-3.10.0-linux-x86_64.zip > /tmp/protoc/protoc.zip && \  
  cd /tmp/protoc && \  
  unzip protoc.zip && \  
  cp /tmp/protoc/bin/protoc /usr/local/bin && \  
  chmod go+rx /usr/local/bin/protoc && \  
  cd /tmp && \  
  rm -r /tmp/protoc  
  
WORKDIR /study-grpc  
COPY . /study-grpc  
  
RUN go get -u google.golang.org/grpc  
RUN go get -u github.com/golang/protobuf/protoc-gen-go

docker-compose.yml

とりあえずコンテナが起動していれば良いのでcommand: bashにしています

version: '3.7'  
  
services:  
  study-grpc:  
    build: .  
    container_name: "study-grpc"  
    ports:  
      - 1234:1234  
    volumes:  
      - .:/study-grpc  
    command: bash
    tty: true

2. protoファイルの作成

protoファイルにインターフェースを定義しコードを生成します。
pb/cat.proto を作成しました。

syntax = "proto3";  
  
service Cat {  
    rpc GetMyCat (GetMyCatMessage) returns (MyCatResponse) {}  
}  
  
message GetMyCatMessage {  
    string target_cat = 1;  
}  
  
message MyCatResponse {  
    string name = 1;  
    string kind = 2;  
}

syntax = "proto3" を記載し忘れるとproto2として解釈されるので注意が必要です。
gRPCでは一般的にproto3を利用します。proto2とproto3の違いはこちらの記事がわかりやすかったです。

string name = 1 の数字の部分はタグナンバーです。タグナンバーはフィールドを区別する際に利用されます。一度採番したら変えない方がよいとされているので変更がある場合は新たに採番します。

次にコンテナ内に入りprotoファイルをコンパイルし、ソースコードを生成します。

# コンテナ起動
$ docker-compose up
# コンテナの中に入る
$ docker exec -it study-grpc bash
# protocコマンド実行
$ protoc --go_out=plugins=grpc:. ./pb/cat.proto

pb/cat.pb.goが生成さていればOKです。

3. server側の処理

server.goを作成します。

package main  
  
import (  
    "context"  
    "errors"
    "google.golang.org/grpc"
    "log"
    "net"
    cat "study-grpc/pb"  
)  
  
type myCatService struct{}  
  
func (s *myCatService) GetMyCat(ctx context.Context, message *cat.GetMyCatMessage) (*cat.MyCatResponse, error) {  
    switch message.TargetCat {  
    case "tama":  
        return &cat.MyCatResponse{  
            Name: "tama",  
            Kind: "Maine Coon",  
        }, nil  
    case "mike":  
        return &cat.MyCatResponse{  
            Name: "mike",  
            Kind: "Norwegian Forest Cat",  
        }, nil  
    default:  
        return nil, errors.New("Not Found YourCat..")  
    }  
}  
  
func main() {  
    port, err := net.Listen("tcp", ":1234")  
    if err != nil {  
        log.Println(err.Error())  
        return  
    }
    s := grpc.NewServer()  
    cat.RegisterCatServer(s, &myCatService{})  
    s.Serve(port)  
}

4. client側(リクエスト)の処理

client.goを作成します。

package main  
  
import (  
    "context"  
    "fmt"
    "google.golang.org/grpc"
    "log"
    cat "study-grpc/pb"  
)  
  
func main() {  
    conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())  
    if err != nil {  
        log.Fatal("connection error:", err)  
    }  
    defer conn.Close()  
  
    client := cat.NewCatClient(conn)  
    message := &cat.GetMyCatMessage{TargetCat: "mike"}  
    res, err := client.GetMyCat(context.Background(), message)  
    if err != nil {  
        log.Fatal(err)  
    }  
    fmt.Printf("result:%s\n", res)  
}

5. buildして実行

作成したserver.goclient.goをbuildして実行してみましょう。

# コンテナの中に入る
$ docker exec -it study-grpc bash

# server.goをbuild & 実行
$ go build server.go
$ ./server

# client.goをbuild & 実行
$ go build client.go
$ ./client

# buildするのが面倒な場合はgo runでもokです
$ go run server.go
$ go run client.go

実行結果

result:name:"mike" kind:"Norwegian Forest Cat"

最後に

いかがでしたでしょうか?

名前を聞いただけでは「gRPC? ProtocolBuffer? なんだそれ難しそう、、」と思ってしまいますが実際に手を動かしてみると想像していたものよりは簡単だったのではないでしょうか。

Googleだけでなくいくつもの大手企業が採用している実績がありますし、日本の企業での事例もどんどん増えてきています。

f:id:kkwatanabe:20191106171413p:plain:w600

マイクロサービス化への課題はいくつもありますが通信部分を解決してくれるgRPCは選択肢のひとつとして、ぜひ覚えておきたいですね。

今回使用したコードはこちらのリポジトリにまとめました。

github.com