めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

Building microservices with go-micro on GCP(パート1)

go-micro について

github.com

go-micro は、microservice 用のサーバーを構築するための Go 言語のフレームワークです。gRPC による(同期型)API サーバー、および、Cloud Pub/Sub などのメッセージブローカーから受信したイベントを処理する(非同期型)API サーバーを簡単に作成することができます。ここでは、GCP の以下のサービスと組み合わせて、go-micro を利用する手順を紹介します。

Google Kubernetes Engine (GKE) : go-micro で作ったサービスを GKE の Pod としてデプロイします。

Cloud Pub/Sub : メッセージブローカーとして Cloud Pub/Sub を使用します。

go-micro はさまざまな機能のバックエンドをプラグインで選択できるようになっており、ここでは、Service Registory 機能として、Pod の Annotation を使用する Kubernetes Registry Plugin と、メッセージブローカーとして Cloud Pub/Sub を使用する googlepubsub Plugin を組み合わせて利用します。

事前準備

まずは、GCP のプロジェクトを作成します。ここでは、プロジェクト ID は「go-micro-test」とします。次に GKE で Kubernetes のクラスターを作成しますが、Pod から Cloud Pub/Sub などの外部サービスを利用できるよう、Access scopes には「Allow full access to all Cloud APIs」を指定してください。

この後の作業は、Cloud Shell からも行うことができますが、ここでは、GCE の VM を使って、開発用サーバーを用意しておくことにします。(Cloud Shell を使う場合は、ここは飛ばして、次のセクションに進んでください。)はじめに、適当なサイズの VM インスタンスを起動します。OS はデフォルトの Debian です。Kubernetes と同様に、Access scopes には「Allow full access to all Cloud APIs」を指定してください。

インスタンスが起動したら、SSH でログインして、Protocol buffer のコンパイラと Go 言語の開発環境を用意します。

sudo apt update
sudo apt -y upgrade
sudo apt install -y build-essential git protobuf-compiler
wget https://golang.org/dl/go1.14.6.linux-amd64.tar.gz
tar -xvzf go1.14.6.linux-amd64.tar.gz
sudo mv go /usr/local

cat <<'EOF' >>~/.bashrc
export GOROOT=/usr/local/go
export GOPATH=~/go
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH
EOF

コンテナイメージをビルドするために Docker をインストールします。

sudo apt install -y apt-transport-https ca-certificates curl software-properties-common gnupg2
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
sudo apt update
sudo apt install -y docker-ce
sudo usermod -aG docker $USER

ここで一度、サーバーを再起動しておきます。

sudo reboot

再起動後に、再度、SSH でログインして、環境が用意できたことを確認します。

$ go version
go version go1.14.6 linux/amd64

$ protoc --version
libprotoc 3.0.0

$ docker version
Client: Docker Engine - Community
 Version:           19.03.12
 API version:       1.40
 Go version:        go1.13.10
 Git commit:        48a66213fe
 Built:             Mon Jun 22 15:45:52 2020
 OS/Arch:           linux/amd64
 Experimental:      false
Server: Docker Engine - Community
 Engine:
  Version:          19.03.12
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.10
  Git commit:       48a66213fe
  Built:            Mon Jun 22 15:44:23 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

go-micro のインストールと動作確認

必須ではありませんが、開発中に便利なので、goimports コマンドを入れておきます。

go get golang.org/x/tools/cmd/goimports

go-micro の実行に必要なモジュールをインストールします。

go get github.com/golang/protobuf/{proto,protoc-gen-go}
GO111MODULE=on go get github.com/micro/micro/v2
GO111MODULE=on go get github.com/micro/protoc-gen-micro/v2

サンプルサービスを生成して、go-micro が正しくインストールされていることを確認します。--namespace オプションは、サービス名を World Wide でユニークにするために、FQDN 逆順命名規約で Namespace を指定します。

cd $GOPATH/src
micro new --namespace=com.example --gopath=false sample
cd sample

$GOPATH/src/sample 以下に次のようなテンプレートが生成されています。

$ tree .
.
├── Dockerfile
├── generate.go
├── go.mod
├── handler
│   └── sample.go
├── main.go
├── Makefile
├── plugin.go
├── proto
│   └── sample
│       └── sample.proto
├── README.md
└── subscriber
    └── sample.go

この時、デフォルトで用意された go.mod の内容に注意してください。

go.mod

module sample

go 1.13

// This can be removed once etcd becomes go gettable, version 3.4 and 3.5 is not,
// see https://github.com/etcd-io/etcd/issues/11154 and https://github.com/etcd-io/etcd/issues/11931.
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0

etcd と grpc のモジュールの非互換依存問題があるため、それを回避するための replace エントリが記載されています。自動生成されるテンプレートを使わないと、こういった点にはまる事があるので注意してください。

また、Makefile も用意されているので、次のコマンドでバイナリーのビルドができます。(この時、proto ファイルのコンパイルも行われます。)

make build

出来上がったバイナリーは、ローカルで実行することもできます。

$ ./sample-service 
2020-08-06 11:50:28  file=v2@v2.9.1/service.go:200 level=info Starting [service] com.example.service.sample
2020-08-06 11:50:28  file=grpc/grpc.go:864 level=info Server [grpc] Listening on [::]:39755
2020-08-06 11:50:28  file=grpc/grpc.go:881 level=info Broker [http] Connected to 127.0.0.1:39849
2020-08-06 11:50:28  file=grpc/grpc.go:697 level=info Registry [mdns] Registering node: com.example.service.sample-a764a88f-35ea-4bc5-a603-a5a1e133fff2
2020-08-06 11:50:28  file=grpc/grpc.go:730 level=info Subscribing to topic: com.example.service.sample

gRPC の Listen ポートはランダムに割り当てられますが、Service Discovery 機能があるので気にしなくても構いません。(デフォルトでは、mDNS による discovery 機能が提供されます。)

別のターミナルから SSH ログインして、micro コマンドでサービスの状態を確認してみます。

$ micro list services
com.example.service.sample
micro.http.broker

$ micro get service com.example.service.sample
Endpoint: Sample.Call
Request: {
        message_state MessageState {
                no_unkeyed_literals NoUnkeyedLiterals
                do_not_compare DoNotCompare
                do_not_copy DoNotCopy
                message_info MessageInfo
        }
        int32 int32
        unknown_fields []uint8
        name string
}
Response: {
        message_state MessageState {
                no_unkeyed_literals NoUnkeyedLiterals
                do_not_compare DoNotCompare
                do_not_copy DoNotCopy
                message_info MessageInfo
        }
        int32 int32
        unknown_fields []uint8
        msg string
}
...

Sample.Call というエンドポイントの API があることがわかります。次のように、サービス名とエンドポイント名を指定して、gRPC のリクエストを投げることもできます。

$ micro call com.example.service.sample Sample.Call '{"name": "world!"}'
{
        "msg": "Hello world!"
}

proto ファイルが読める方であれば、proto/sample/sample.proto を見ると API の仕様がわかります。

これで、基本的な動作確認ができました。先ほど起動したサービスは、Ctrl+C で停止しておきます。

次のパートでは、GKE 上にサービスをデプロイします。

enakai00.hatenablog.com

おまけ

proto ファイルの読み方がわからない、という方にはこちらの入門書がお勧めです。

www.amazon.co.jp