gRPC & Go & Ruby SDK 作った
目的
現在属している会社では、UGCのサービスを運用していて、大きなDBに、主要な情報が詰まってる。
そのDBに対して、複数のアプリケーションから情報を登録していたが、主要なデータのインサートについては、専用のアプリケーションに任せたいとなった。
そこで、Go lang で、gRPCサーバ作って、DB操作ができるようにしてみた。
やったこと
gRPCサーバ側
proto ファイル作成
protoファイルを例をもとに作成した。
protoファイルから、go や ruby への変換は、下の docker ファイルを利用した。 github.com
環境構築
fresh を利用して、hot reloadできるようにした。
Dockerfile
FROM golang:1.13 WORKDIR /src RUN go get github.com/pilu/fresh ENV GO111MODULE=on EXPOSE 50051 ADD . /src CMD ["fresh"]
docker-compose.yml
version: '3' services: app: build: . volumes: - .:/src ports: - "50051:50051"
go.mod
module main go 1.13
main.go
package main import "fmt" func main() { fmt.Println("test") }
起動
$ docker-compose up --build
go ファイル作成
main.go をこんな感じに書き換えて、あとは必要なファイルを作っていった。
package main import ( "database/sql" _ "github.com/go-sql-driver/mysql" "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/jinzhu/gorm" "google.golang.org/grpc" health "google.golang.org/grpc/health" healthpb "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/reflection" "log" "main/infrastructure" "main/middleware" test1 "main/proto/go/test1" test2 "main/proto/go/test2" "main/service" "net" ) const ( port = ":50051" ) func main() { log.Printf("process start") infrastructure.EnvLoad() lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } // ORMは一旦Gormを使った。 var db *gorm.DB db = infrastructure.RoomclipDb() defer db.Close() // middlewareをここで設定 s := grpc.NewServer(grpc.UnaryInterceptor( grpc_middleware.ChainUnaryServer( middleware.NewZap(), middleware.NewRecovery(), )), ) // health check healthpb.RegisterHealthServer(s, health.NewServer()) // こんな感じにサービス複数持てる test1Service := &service.Test1Service{Db: db} test1.RegisterFolderServer(s, test1Service) test2Service := &service.Test2Service{Db: db} test2.RegisterLikeServer(s, test2Service) // Register reflection service on gRPC server. reflection.Register(s) log.Printf("grpc server running") if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
localで確認
こちらがすごく便利だった。 github.com
こんな感じで確認できる。
$ grpcurl -plaintext localhost:50051 list $ grpcurl -plaintext localhost:50051 list test1.Test $ grpcurl -plaintext localhost:50051 test1.Test.test $ grpcurl -d '{ "name": "hogehoge" }' -plaintext localhost:50051 test1.Test.test
デプロイ
デプロイは、普段通り github => CodePipeline => CodeBuild(ECRへイメージpush)=> ECS
とした。
実際の環境はVPC内部のアプリケーションから呼ばれる想定なので、Service Discovery を用いてルーティングを一旦行うことにした。
ただ、社内で使うtest環境は、Service Discovery を用いて、VPC外からアクセスする方法ができなさそうだったので、CodePipelineの最後に、Route53のAレコードを書き換えるようにしてアクセスするようにした。
当初は、AppMesh使いたいなーと思っていたが、まだサポートしてないっぽいので、一旦諦めた。
呼び出し側
sdk作成
今回は、一旦rubyのみ作成した。
サンプル通り作れば思った以上に簡単にできました。
sdk利用するアプリケーション
Gemfileに書いて、Docker上で試そうとするとエラーが発生した...
alpine linux の imageを使ってたから発生したらしい。
Error loading shared library ld-linux-x86-64.so.2
というエラーが起きるから、
こちらのサイト通りに追加してみたら、/lib64/ld-linux-x86-64.so.2
ってファイルが追加されたけど、うまくいかなかった。
AlpineコンテナでNode.jsからgRPCを利用する場合、libc6-compatが必要 · tehepero note(・ω<) 2.0
最終的に、bundle install 時に、環境変数を渡したら上手くいった。
https://github.com/protocolbuffers/protobuf/issues/4460
BUNDLE_FORCE_RUBY_PLATFORM=1としたら、以下のようになるらしい。
現在のマシンのプラットフォームを無視し、Rubyプラットフォームのgemのみをインストールします。その結果、ネイティブ拡張を持つgemはソースからコンパイルされます。
一方、CFLAGS オプションの意味がよくわからないので、わかる人いらっしゃったらお教えいただけると...
# BUNDLE_FORCE_RUBY_PLATFORM を利用しないと、protobufで、segmentation faultが発生する。 # しかし、BUNDLE_FORCE_RUBY_PLATFORMを利用すると、grpc gem がインストールできない。 # CFLAGSをつけると、問題なくインストールできるようになった。 RUN CFLAGS="-Wno-cast-function-type" \ BUNDLE_FORCE_RUBY_PLATFORM=1 \ bundle install --jobs=4
終わり
初めてgo でサービス作ったが、違和感なく実装はできた。
最近、typescript触ってて、型のある言語になれてたからかも。
DBにインサートする処理がほとんどのサービスを、goで作る必要はあったのかと言われると、Active Recordの方が簡単だとは思うので、変更はあるかも。
一方で、gRPCというか、protoファイルによる定義は戻り値とか明確に定義できていいなーと思った。
今後は、ログ周りとか整備して、高負荷環境に耐えうるようにしていきたい。
個人的な面では、DB & gRPC周りのテストをどうやって書いたらいいのかを勉強しなきゃなと思った。