前提とか
- goの実行環境
- dockerの実行環境
- mac os
まずはGoを使ってgRPCサーバを立ててみる
##1. 必要なものをinstall
gRPCxGoは色々なところで書かれているので簡単に。
GoのgRPCライブラリをinstall
go get -u google.golang.org/grpc
Protocol Buffersをインストール
- IDL(インターフェイス定義言語)で書かれたファイルをコンパイルするのに必要。
brew install protobuf
Protocol Buffersのプラグインをインストール
- IDLに書かれた定義をGo言語へ変換するのに必要。インターフェイスをGo言語で自動生成する。
go get github.com/golang/protobuf/protoc-gen-go
protoc-gen-goはPATHが通った場所に置く必要があるので、.bash_profile
やらに、以下を記述してPATHを通してあげる。自分は$GOPATH/bin
をPATHに通しているのでprotoc-gen-go
の実行ファイルをそこに配置した。
export PATH="$GOPATH/bin:$PATH"
type protoc-gen-go -> "protoc-gen-go is ~/go/bin/protoc-gen-go"
※gRPCミドルウェアのインストール
dockerで動かしている時に、通信が確立されているのかを確認するため、サーバ側のアクセスログを取得したかった。なのでそれらのミドルウェアをインストールする。
go get -u github.com/grpc-ecosystem/go-grpc-middleware
go get -u github.com/sirupsen/logrus
2. サーバの実装
ディレクトリ構成は以下のようにする想定。go.modは空でいいので作っておく。
server
|
---pb--increment.pb.go
|
---proto--increment.proto
|
---service--increment.go
|
---main.go
---go.mod
最初にIDLを書いてインターフェイスを定義する。
syntax = "proto3";
package increment;
message IncrementRequest {
int32 number = 1;
}
message IncrementResponse {
int32 number = 1;
}
service IncrementService {
rpc GetAndIncrement (IncrementRequest)
returns (IncrementResponse);
}
Goのインターフェイスを生成する。
# プロジェクトのトップにいることを確認
$ pwd #~/go/src/github.com/TakeruTakeru/server
# protoをコンパイルしてGoのインターフェイスを作成
$ protoc -I ./proto --go_out=plugins=grpc:./pb increment.proto
するとpb
配下にincrement.pb.go
が作られているので確認する。
生成されたインターフェイスの実装を行う。
service
配下にincrement.go
を作成し、以下のように実装する。
package service
import (
"context"
pb "github.com/TakeruTakeru/gserver/pb"
)
type IncrementService struct {
cacheNum int32
}
func (s *IncrementService) Increment(ctx context.Context, req *pb.IncrementRequest) (*pb.IncrementResponse, error) {
n := req.GetNumber() + 1
return &pb.IncrementResponse{Number: n}, nil
}
func (s *IncrementService) GetAndIncrement(ctx context.Context, req *pb.IncrementRequest) (*pb.IncrementResponse, error) {
if s.cacheNum != 0 {
s.cacheNum = s.cacheNum + 1
} else {
s.cacheNum = req.GetNumber()
}
return &pb.IncrementResponse{Number: s.cacheNum}, nil
}
func NewIncrementService() *IncrementService {
return &IncrementService{}
}
サービスロジックに関わる部分は以上の手続きで十分である。
今度はサーバの実装をする。
ミドルウェアを適用しない場合はこちらを参考にして実装してください。
また、net.Listen
するさいに、第二引数はポート指定のみにしてください*IPを指定すると、そのIPのみをlistenするので、例えばループバックアドレスを指定しているとIPからの接続のみ受け付けるので、開発環境ではlocalhostから繋いでたから上手くいってたのに、dockerでポートフォワードしてるのに繋がらないみたいなことになる。
package main
import (
"log"
"net"
"os"
"time"
pb "github.com/TakeruTakeru/gserver/pb"
service "github.com/TakeruTakeru/gserver/service"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
)
func main() {
listen, err := net.Listen("tcp", ":5555")
if err != nil {
log.Fatalln(err)
}
logrus.SetLevel(logrus.DebugLevel)
logrus.SetOutput(os.Stdout)
logrus.SetFormatter(&logrus.JSONFormatter{})
logger := logrus.WithFields(logrus.Fields{})
opts := []grpc_logrus.Option{
grpc_logrus.WithDurationField(func(duration time.Duration) (key string, value interface{}) {
return "grpc.time_ns", duration.Nanoseconds()
}),
}
grpc_logrus.ReplaceGrpcLogger(logger)
server := grpc.NewServer(
grpc_middleware.WithUnaryServerChain(
grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
grpc_logrus.UnaryServerInterceptor(logger, opts...),
),
)
service := service.NewIncrementService()
pb.RegisterIncrementServiceServer(server, service)
if err := server.Serve(listen); err != nil {
panic(err)
}
}
3. gRPC Clientの実装
次にクライアントの実装をぱぱっと作る。
package main
import (
"context"
"fmt"
"log"
"time"
pb "github.com/TakeruTakeru/gserver/pb"
"google.golang.org/grpc"
)
func main() {
connection, err := grpc.Dial("localhost:5555", grpc.WithInsecure())
if err != nil {
log.Fatalln("did not connect: %s", err)
}
defer connection.Close()
client := pb.NewIncrementServiceClient(connection)
context, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
var number int32 = 0
response, err := client.GetAndIncrement(context, &pb.IncrementRequest{Number: number})
if err != nil {
log.Println(err)
}
fmt.Println(response.GetNumber())
response1, err := client.GetAndIncrement(context, &pb.IncrementRequest{Number: number})
if err != nil {
log.Println(err)
}
fmt.Println(response1.GetNumber())
response2, err := client.GetAndIncrement(context, &pb.IncrementRequest{Number: number})
if err != nil {
log.Println(err)
}
fmt.Println(response2.GetNumber()) // 1001
}
これで一通り必要なものができたので、実際にgo run
して動作を確認する。
ls # main.go pb proto service go.mod
go run main.go
プロセスが立ち上がり、ポート5555でtcp通信を受け付けるようになったので、先ほど作ったclient.go
もgo run
する。
go run client.go # 1, 2, 3
これでローカル環境でgRPCの動作確認が完了した。
今回はHeroku上でdocker環境のアプリケーションを動かしたいので、dockerイメージにビルドしたファイルも含めるようにする。(ここら辺のことはあまり調べきってないので別のやり方があるのなら教えていただけると助かります)
Docker build
まず最初にinstallしたgrpcなどの依存モジュールをまとめる。
以下のコマンドを実行するとgo.mod
に依存モジュールの情報がまとめられる。
ls # main.go pb proto service go.mod
vgo list
今回のdockerイメージは以下のように定義する。
main.goのディレクトリを全てコピーし、依存関係を解決してくれるようにvgoをインストールしてビルドする流れ。
FROM golang:latest as gobase
ENV PATH=$PATH:$GOPATH/bin
ENV GOARCH="amd64"
ENV GOOS="linux"
WORKDIR /go/src/github.com/TakeruTakeru
COPY . .
RUN cd ../; go get -u golang.org/x/vgo
RUN go build -o $GOPATH/bin/grpc_server
docker build. -t grpc_exeample
すると、、、
Step 8/8 : RUN go build -o $GOPATH/bin main.go
---> Running in 6109eb2d49aa
go: downloading github.com/sirupsen/logrus v1.4.2
go: downloading google.golang.org/grpc v1.24.0
go: downloading github.com/golang/protobuf v1.3.2
go: downloading github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
go: extracting github.com/sirupsen/logrus v1.4.2
go: downloading golang.org/x/sys v0.0.0-20190422165155-953cdadca894
go: extracting github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
go: extracting github.com/golang/protobuf v1.3.2
go: extracting google.golang.org/grpc v1.24.0
go: downloading google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55
go: downloading golang.org/x/net v0.0.0-20190311183353-d8887717615a
go: extracting golang.org/x/sys v0.0.0-20190422165155-953cdadca894
go: extracting golang.org/x/net v0.0.0-20190311183353-d8887717615a
go: downloading golang.org/x/text v0.3.0
go: extracting golang.org/x/text v0.3.0
go: extracting google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55
go: finding github.com/golang/protobuf v1.3.2
go: finding google.golang.org/grpc v1.24.0
go: finding golang.org/x/net v0.0.0-20190311183353-d8887717615a
go: finding google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55
go: finding golang.org/x/sys v0.0.0-20190422165155-953cdadca894
go: finding golang.org/x/text v0.3.0
go: finding github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
go: finding github.com/sirupsen/logrus v1.4.2
勝手にモジュールを落としてきてくれていることがわかる。
Docker run & 動作確認
実際にdocker run
してみる。この時、ローカルでサーバを立ち上げているならば忘れずにkillしておく。docker containerのポートフォワーディングと被り、コンテナがエラーを起こしてしまうためだ。
docker run --name grpc_exeample -p 5555:5555 -it grpc_exeample bash
コンテナ内に入り、ビルドできていれば/go/bin/grpc_server
というバイナリがあるので、実際に動かす(./grpc_server
)。
別のシェルプロセスを立ち上げて先ほどのclient.go
を実行する。
下記のレスポンスがサーバ、クライアント共に出力されていれば完了!
お疲れ様でした!
root@6962b46dce49:/go/bin# ./grpc_server
{"grpc.code":"OK","grpc.method":"GetAndIncrement","grpc.request.deadline":"2019-11-04T02:48:37Z","grpc.service":"increment.IncrementService","grpc.start_time":"2019-11-04T02:48:36Z","grpc.time_ns":167200,"level":"info","msg":"finished unary call with code OK","peer.address":"172.17.0.1:56182","span.kind":"server","system":"grpc","time":"2019-11-04T02:48:36Z"}
{"grpc.code":"OK","grpc.method":"GetAndIncrement","grpc.request.deadline":"2019-11-04T02:48:37Z","grpc.service":"increment.IncrementService","grpc.start_time":"2019-11-04T02:48:36Z","grpc.time_ns":55600,"level":"info","msg":"finished unary call with code OK","peer.address":"172.17.0.1:56182","span.kind":"server","system":"grpc","time":"2019-11-04T02:48:36Z"}
{"grpc.code":"OK","grpc.method":"GetAndIncrement","grpc.request.deadline":"2019-11-04T02:48:37Z","grpc.service":"increment.IncrementService","grpc.start_time":"2019-11-04T02:48:36Z","grpc.time_ns":48800,"level":"info","msg":"finished unary call with code OK","peer.address":"172.17.0.1:56182","span.kind":"server","system":"grpc","time":"2019-11-04T02:48:36Z"}
{"level":"info","msg":"transport: loopyWriter.run returning. connection error: desc = \"transport is closing\"","system":"system","time":"2019-11-04T02:48:36Z"}
1
2
3
所感
gRPCに関する情報は結構転がっているので割りかし困ることはなかった。
覚えることがたくさんあるが、それにしてもリターンが沢山あるのでgRPCもっと知りたいなと。
参考にさせていただいたページ
go-grpc-middlewareを一通り試してみる
Goで書いたサーバーをHerokuにDocker Deployする
gRPC に Go言語 で入門する方法(環境構築から通信まで)