gRPCを用いたマイクロサービス間の通信環境を構築したのでその手順をメモしておきます。
今回はPHPクライアントとGoサーバー間でgRPC通信を行っています。
目次
達成出来ること
- Protocol Buffer からGoのサーバーコードを生成する
- Protocol Buffer からPHPのクライアントコードを生成し、Goサーバーと通信を行う
環境構築
Protocol Buffer の定義ファイルを作成する前に、まずは各言語のコンパイル環境を構築する必要があります。
今回はPHPコードへのコンパイル環境とGoコードへのコンパイル環境をDockerにて作成しました。
Go環境
# Dockerfile
FROM golang:1.11.2
# Install basics
RUN apt-get update \
&& apt-get install -y vim git zip wget
# Install grpc, protoc-gen-go
RUN go get -u google.golang.org/grpc \
&& go get -u github.com/golang/protobuf/protoc-gen-go
# Install protoc
RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v3.6.1/protoc-3.6.1-linux-x86_64.zip \
&& unzip protoc-3.6.1-linux-x86_64.zip -d protoc \
&& cd protoc \
&& mv bin/protoc /usr/bin
ENV PATH $PATH:$GOPATH/bin
COPY ./services.sh /etc/services.sh
RUN chmod a+x /etc/services.sh
EXPOSE 50051
RUN sed -i -e '9,13s:^#::' /root/.bashrc
grpc, protoc-gen-go, protoc をインストールし、パスを通します。
また、Goサーバーはポート50051
で待機させる予定なので、50051
をEXPOSEします。
COPYにて指定しているservices.sh
の内容は下記の通りです。
#!/bin/sh
while true
do
sleep 10
done
これはコンテナ起動後にコンテナを生存させ続けるためのentrypoint として用います。
PHP環境
# Dockerfile
FROM php:7.2-cli
# Install basics, grpc, protobuf
RUN apt-get update \
&& apt-get install -y vim git zip wget automake libtool libz-dev \
&& pecl install grpc \
&& pecl install protobuf
# Install protoc
RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v3.6.1/protoc-3.6.1-linux-x86_64.zip \
&& unzip protoc-3.6.1-linux-x86_64.zip -d protoc \
&& cd protoc \
&& mv bin/protoc /usr/bin
# Install grpc_php_plugin
RUN git clone -b $(curl -L https://grpc.io/release) https://github.com/grpc/grpc \
&& cd grpc \
&& git submodule update --init \
&& make grpc_php_plugin
# Install composer
RUN curl -sS https://getcomposer.org/installer | php \
&& mv composer.phar /usr/bin/composer
COPY ./php.ini /usr/local/etc/php/php.ini
COPY ./services.sh /etc/services.sh
RUN chmod a+x /etc/services.sh
RUN sed -i -e '9,13s:^#::' /root/.bashrc
grpc, protobuf, protoc, grpc_php_plugin をインストールします。また後ほど用いるためcomposer もインストールしています。
COPYにて指定しているservices.sh
はGo環境の時のものと同様です。php.ini
の内容は下記のとおりです。
extension=grpc.so
extension=protobuf.so
PHP環境ではini ファイル内にてextension を読み込ませる必要があります。
コンテナを立ち上げる
それぞれのコンテナを個々でビルド、起動、検証と進めても良いですが、今回はコンテナ間通信を簡略化するため Docker Compose を用います。
次のようなdocker-compose.yml
を作成します。
version: '3'
services:
# PHPコード生成用コンテナ
grpc_php:
build: ./grpc-php
volumes:
- ../:/grpc-training
entrypoint: /etc/services.sh
# Golangコード生成用コンテナ
grpc_go:
build: ./grpc-go
volumes:
- ../:/go/src/grpc-training/
entrypoint: /etc/services.sh
下記のようなディレクトリ構造を想定しています。
grpc-training
├── docker
│ ├── docker-compose.yml
│ ├── grpc-go
│ │ ├── Dockerfile
│ │ └── services.sh
│ └── grpc-php
│ ├── Dockerfile
│ ├── php.ini
│ └── services.sh
└── example // 後で利用します
docker-compose.yml
と同階層にて下記コマンドを実行します。これでコンテナのビルド、起動、コンテナ間通信用network の生成が同時に実行されます。
docker-compose up -d
Protocol Buffer の定義ファイルを作成する
example
ディレクトリにexample.proto
を下記内容で作成します。
syntax = "proto3";
package example;
service Example {
rpc Echo (EchoRequest) returns (EchoResponse) {}
}
message EchoRequest {
string message = 1;
}
message EchoResponse {
string message = 1;
}
サービスのインターフェース、リクエストの内容、及びレスポンスの内容を定義しています。
この場合、Example
サービスはEcho
メソッドを持っており、その引数の型はEchoRequest
、返却値の型はEchoResponse
です。
EchoRequest
、EchoResponse
はstring
型の変数message
を保持してる、といった具合です。
定義ファイルを各言語へコンパイルする
Goコードの生成
先の項目にて作成、起動したGo環境のコンテナに接続します。
docker exec -it [container name or hash] bash
/go/src/grpc-training/example
に移動します。
cd /go/src/grpc-training/example
Goコードの出力先ディレクトリを作成します。
mkdir go
Goコードを生成します。
protoc example.proto --go_out=plugins=grpc:./go
これでgo
ディレクトリ内にexample.pb.go
が生成されます。
PHPコードの生成
PHP環境のコンテナに接続します。
docker exec -it [container name or hash] bash
/grpc-training/example
に移動します。
cd /grpc-training/example
PHPコードの出力先ディレクトリを作成します。
mkdir php
PHPコードを生成します。
protoc example.proto \
--php_out=./php \
--grpc_out=./php \
--plugin=protoc-gen-grpc=/grpc/bins/opt/grpc_php_plugin
これでphp
ディレクトリ内にGPBMetadata
、Example
ディレクトリが生成され、その中に生成されたコードが格納されます。
現時点でのディレクトリ構造
ここまでの作業にて、ディレクトリ構造、及びファイル配置は下記の通りになっています。
grpc-training
├── docker
│ ├── docker-compose.yml
│ ├── grpc-go
│ │ ├── Dockerfile
│ │ └── services.sh
│ └── grpc-php
│ ├── Dockerfile
│ ├── php.ini
│ └── services.sh
└── example
├── example.proto
├── go
│ └── example.pb.go
└── php
├── Example
│ ├── EchoRequest.php
│ ├── EchoResponse.php
│ └── ExampleClient.php
└── GPBMetadata
└── Example.php
Goサーバー側のコードを作成する
example/go
ディレクトリ内にserver
ディレクトリを作成します。
cd example/go
mkdir server
作成したserver
ディレクトリ内にserver.go
として下記内容のファイルを作成します。
# server.go
package main
import (
pb "grpc-training/example/go"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc"
"context"
"log"
"net"
)
const (
PORT = ":50051"
)
type server struct{}
func (s *server) Echo(ctx context.Context, in *pb.EchoRequest) (*pb.EchoResponse, error) {
return &pb.EchoResponse{Message: "Hello " + in.Message}, nil
}
func main() {
lis, err := net.Listen("tcp", PORT)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterExampleServer(s, &server{})
// Register reflection service on gRPC server.
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
ファイルが作成できたら、Go環境のコンテナに接続し、サーバーを起動します。
docker exec -it [container name or hash] bash
cd /go/src/grpc-training/example/go/server
go run server.go
PHPクライアント側のコードを作成する
example/php
ディレクトリ内にclient
ディレクトリを作成します。
cd example/php
mkdir client
作成したclient
ディレクトリ内にcomposer.json
として下記内容のファイルを作成します。
{
"require": {
"grpc/grpc": "^1.15",
"google/protobuf": "^3.6"
},
"autoload": {
"psr-4": {
"GPBMetadata\\" : "../GPBMetadata/",
"Example\\" : "../Example/"
}
}
}
PHP環境のコンテナに接続し、composer install
を行います。
docker exec -it [container name or hash] bash
cd /go/src/grpc-training/example/php/client
composer install
client
ディレクトリ内にclient.php
として下記内容のファイルを作成します。
<?php
# client.php
require_once 'vendor/autoload.php';
use Example\ExampleClient;
use Example\EchoRequest;
use Example\EchoResponse;
function request(string $requestMessage)
{
$client = new ExampleClient('grpc_go:50051', [
'credentials' => Grpc\ChannelCredentials::createInsecure(),
]);
$request = new EchoRequest();
$request->setMessage($requestMessage);
list($reply, $status) = $client->Echo($request)->wait();
/** @var EchoResponse $reply */
return $reply->getMessage();
}
echo request('World.')."\n";
クライアントコードを起動する
最後に、PHP環境のコンテナ内から前項にて作成したclient.php
を起動すれば、gRPCを用いたExample サービスの通信が確認出来ます。
docker exec -it [container name or hash] bash
cd /go/src/grpc-training/example/php/client
php client.php
root@3cdadee14548:/grpc-training/example/php/client# php client.php
Hello World.
おわりに
Protocol Buffer を用いたコード生成により、サーバー/言語の全く異なるサービス間でも型堅牢な通信を行うことが出来るため、非常に使い勝手が良いです。
またgRPCはHTTP/2を標準でサポートしているため、サーバー/スタブ間で高速な通信が可能です。マイクロサービス同士のオーケストレーションにとても有用ですね。