はじめに
メタップスアドベントカレンダー20日目の記事です。
弊社では一部マイクロサービスになっており、その中でRuby on Railsのアプリケーションが動いています。
RailsアプリはクライアントとしてgRPCとProtoBufを使ってgoのサーバーと通信しています。
今回はRails on DockerのサービスにgRPCを導入する大まかな手順と運用をしていく中での知見を2点、紹介します。
注意
- 「gRPCとは何か」や「ProtoBufの書き方」は紹介しません
- RailsアプリがDocker(docker comopse)上で動いているものとします
- コードは全てサンプルなので参考程度にとどめてください
Rails on Docker へのgRPCの導入
protoファイルの作成
syntax = "proto3";
package sample;
service SampleService {
rpc RequestSomething(SampleRequest) returns (SampleResponse) {}
}
message SampleRequest {
string sampleStringRequest = 1;
}
message SampleResponse {
string sampleStringResponse = 1;
}
上記は文字列を送ったら文字列が返ってくるServiceを作成するサンプルです。
ProtoBufのおかげでインターフェイスがわかりやすい!
stubファイルの作成
こちらのgemを使えばprotoを元にstubファイルを作成できます。
protoファイルを/proto配下に置いて、生成されるstubファイルを/sevices配下に置く場合は以下のような感じ。
FROM ruby:3.1.4
WORKDIR /usr/local/src
RUN gem install grpc grpc-tools
COPY sample/setup.sh /usr/local/bin
RUN chmod +x /usr/local/bin/setup.sh
CMD ["/usr/local/bin/setup.sh"]
#!/usr/bin/env bash
set -eu
grpc_tools_ruby_protoc --ruby_out=./services --grpc_out=./services proto/sample.proto
services:
rails-app:
# 中略
protoc-sample:
platform: linux/x86_64 # Appleシリコンの場合
build:
dockerfile: ./sample/Dockerfile.protoc
volumes:
- .:/usr/local/src
コンテナ内で必要なgemをインストールしてsetup.shでコマンドを実行しているだけですね。
$ docker compose run --rm protoc-sample
そうするとこんな感じになります。
sample
├── Dockerfile.protoc
└── setup.sh
services
└── proto
├── sample_pb.rb
└── sample_services_pb.rb
proto
└── sample.proto
compose.yml
自動でstubファイルが生成されました!
initializers配下の修正
require './services/proto/sample_services_pb.rb'
こうすることでRails内でstubファイルで定義されたクラスが使えます。
stub = Sample::SampleService::Stub.new({{gRPCサーバーのエンドポイント}}, :this_channel_is_insecure)
stub.request_something("hogehoge")
これでクライアントとしてgRPCサーバーを呼び出せるようになりました!
モックについて
マイクロサービスで大変なのはテストですよね。
一機能をテストするたびにローカルでさまざまなアプリケーションを立ち上げてたら大変。
それもDockerで動いていたらメモリが足りません。
そんなときgRPCのツールが提供してくれる機能は非常に役に立ちます。
先ほど作成したstubファイルからgRPCサーバーを立てることができます。
公式tutorialを見ながらやればそんなに難しくない!
require './proto/sample_services_pb.rb'
class ServerImpl < Sample::SampleService::Service
def sample_request(req, call)
Sample::SampleResponse.new(sampleStringResponse: "fugafuga")
end
end
server = GRPC::RpcServer.new
server.add_http2_port('0.0.0.0:50051', :this_port_is_insecure)
server.handle(ServerImpl.new)
server.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT'])
こんな感じで書けばlocalhost:50051で立ち上がります。
今回はレスポンスを"fugafuga"にしています。
Dockerを使っている場合はこいつをRailsアプリと一緒に立ち上げたいですよね。
その場合はcompose.ymlに追記。
services:
rails-app:
# 中略
protoc-sample:
# 中略
sample_mock:
command: bundle exec ruby ./mock/sample_server.rb
ports:
- 50051:50051
これでdocker compose upでgRPCのモックサーバーが立ち上がります!
stub = Sample::SampleService::Stub.new('sample_mock:50051', :this_channel_is_insecure)
stub.request_something("hogehoge")
これで他のマイクロサービスを立ち上げなくてもいい!
CIで自動テストする際も楽になるかと思います。
protoファイルの管理
これも悩ましい問題ですよね。
上記ではクライアント側でもprotoファイルを持っています。
このままではインターフェイスが変わるたびにサーバー側もクライアント側もどちらも変更しなくてはいけません。(絶対漏れる)
調べた中でも色々と方法があるようです。
protoファイルのみを管理するリポジトリを作成する方法もあるみたいですね。
ただ弊社ではそこまで人数が多くないことと日々のコミュニケーションが活発なのでここまではしていません。
基本的にはサーバー側でprotoファイルを管理しています。
そしてクライアント側ではprotoファイルを持たずにdocker compose upのタイミングでstubファイルを更新するようにしています。
具体的にはどうしているかというと以下のような感じです。
FROM ruby:3.1.4
WORKDIR /usr/local/src
ARG GITHUB_OAUTH_TOKEN
ARG BRANCH
ARG REPOSITORY
ENV REPOSITORY ${REPOSITORY}
RUN gem install grpc grpc-tools
RUN git clone https://$GITHUB_OAUTH_TOKEN:x-oauth-basic@/${REPOSITORY} \
&& cd ${REPOSITORY} \
&& git checkout ${BRANCH}
COPY etc/docker/sample/setup.sh /usr/local/bin
RUN chmod +x /usr/local/bin/setup.sh
WORKDIR {{ clone先のルートディレクトリ }}
CMD ["/usr/local/bin/setup.sh"]
#!/usr/bin/env bash
set -eu
# 更新
cd /usr/local/src/${REPOSITORY}
echo '$ git pull'
git pull
echo "grpc_tools_ruby_protoc --ruby_out=./proto --grpc_out=./proto /usr/local/src/${REPOSITORY}/proto/${REPOSITORY}.proto"
grpc_tools_ruby_protoc --ruby_out=./proto --grpc_out=./proto /usr/local/src/${REPOSITORY}/proto/${REPOSITORY}.proto
services:
rails-app:
# 中略
sample_mock:
# 中略
protoc-sample:
platform: linux/x86_64
build:
dockerfile: ./sample/Dockerfile.protoc
args:
- GITHUB_OAUTH_TOKEN=${GITHUB_OAUTH_TOKEN}
- REPOSITORY=${REPOSITORY}
- BRANCH=${BRANCH}
volumes:
- .:/usr/local/src
新たな環境変数にREPOSITORY
,GITHUB_OAUTH_TOKEN
, BRANCH
が追加されました。
大まかな流れとしては、サーバー側のリポジトリをclone→protoからstubファイルを作成→ホストにバインドという流れです。
使い方はこんな感じ
1 https://github.com/settings/tokens でtokenを発行する
2 環境変数に設定する
GITHUB_OAUTH_TOKEN={{ 取得したtoken }}
REPOSITORY={{ リポジトリのパス }}
BRANCH={{ ブランチ }}
3 docker compose upするだけ
docker compose up
いい感じですね。
ただupしっぱなしの時は更新されないので注意は必要です。
またマイクロサービス間でディレクトリ構成がある程度共通していることが前提です。
もっとマイクロサービスや開発者が増えたら管理方法を変更していく必要はあるかもしれません。
以上Rails ✖️ Docker ✖️ gRPCの導入から運用のTipsでした!