Docker × Go で gRPC を動かしてみた

grpc エンジニア
記事内に広告が含まれています。

はじめに

この記事ではgo × gRPCで簡単なリクエスト送信・レスポンスの表示を説明していきます。

大きく分けて、下記の3つを順番に実行していきます。

  • protoファイルからコードを自動生成
  • goでgRPCサーバーを起動
  • goでクライアントを実行してリクエスト送信・レスポンス表示

最初はローカルで実行して、次に同じことをDocker環境でも実行します。

 

この記事を書くにあたり、下記を参考にさせていただきました。

参考: 作ってわかる! はじめてのgRPC

 

今回実装したリポジトリです。

全体のコードはこちらをご覧くだしさい。

GitHub - shungo0525/grpc
Contribute to shungo0525/grpc development by creating an account on GitHub.

ローカル環境で実行する場合

パッケージインストール

まずは必要なパッケージをインストールします。

$ brew install protobuf
$ go get -u google.golang.org/grpc
$ go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc

パッケージの依存関係を管理するためのgo.modファイルを生成します。

 

$ cd study_grpc # 作業ディレクトリ
$ go mod init mygrpc

ディレクトリ構成

今回は下記のようなディレクトリ構成にしました。

./study_grpc # 作業ルートディレクトリ
├─ api
│   └─ hello.proto # protoファイル
├─ cmd
│   ├─ server
│   │ └─ main.go
│   └─ client
│        └─ main.go
├─ pkg
│   └─ grpc # ここにコードを自動生成させる
│         ├─ hello.pb.go
│         └─ hello_grpc.pb.go
├─ go.mod
└─ go.sum

 

protoファイル作成

api/hello.proto

// protoのバージョンの宣言
syntax= "proto3";

// protoファイルから自動生成させるGoのコードの置き先
// (詳細は4章にて)
option go_package = "pkg/grpc";

// packageの宣言
package myapp;

// サービスの定義
service GreetingService {
   // サービスが持つメソッドの定義
  rpc Hello (HelloRequest) returns (HelloResponse);
}

// 型の定義
message HelloRequest {
   stringname = 1;
}

message HelloResponse {
   stringmessage = 1;
}

 

protocコマンドでコードを自動生成

下記のコマンドを実行するとpkg/grpcディレクトリ直下に、2つのファイルが生成されます。

$  cd api
$ protoc --go_out=../pkg/grpc --go_opt=paths=source_relative \
         --go-grpc_out=../pkg/grpc --go-grpc_opt=paths=source_relative \
         hello.proto

 

--go_opt=paths=source_relativeは相対パスを指定するオプションです。

./study_grpc/apiでコマンドを実行したため、相対パスでファイルの保存先を指定しています。

 

ちなみに--go_opt=paths=source_relativeを使わず、./study_grpc/から実行する場合は、

下記のコマンドになります。

$ protoc --go_out=./ --go-grpc_out=./ api/hello.proto

 

gRPCサーバーを作成

cmd/server/main.go

package main

import (
  "fmt"
  "net"
  "log"
  "os"
  "context"
  "os/signal"
  "google.golang.org/grpc"
  "google.golang.org/grpc/reflection"
  hellopb "mygrpc/pkg/grpc"
)

func NewMyServer() *myServer {
  return &myServer{}
}

func main() {
  // 1. 8080番portのLisnterを作成
  port := 8080
  listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
  if err != nil {
    panic(err)
  }

  // 2. gRPCサーバーを作成
  s := grpc.NewServer()

  // 3. gRPCサーバーにGreetingServiceを登録
  hellopb.RegisterGreetingServiceServer(s, NewMyServer())

  // 4. サーバーリフレクションの設定
  reflection.Register(s)

  // 5. 作成したgRPCサーバーを、8080番ポートで稼働させる
  go func() {
    log.Printf("start gRPC server port: %v", port)
    s.Serve(listener)
  }()

  // 6.Ctrl+Cが入力されたらGraceful shutdownされるようにする
  quit := make(chan os.Signal, 1)
  signal.Notify(quit, os.Interrupt)
  <-quit
  log.Println("stopping gRPC server...")
  s.GracefulStop()
}

type myServer struct {
  hellopb.UnimplementedGreetingServiceServer
}

func (s *myServer) Hello(ctx context.Context, req *hellopb.HelloRequest) (*hellopb.HelloResponse, error) {
  // リクエストからnameフィールドを取り出して
  // "Hello, [名前]!"というレスポンスを返す
  return &hellopb.HelloResponse{
    Message: fmt.Sprintf("Hello, %s!", req.GetName()),
  }, nil
}

 

gRPCurlをインストール

gPRCはcurlで動作確認できませんが、gPRCurlを使うことで,

ローカル環境でも動作確認することができます。

$ brew  install grpcurl

 

gRPCサーバーを起動

これでgRPCサーバーを起動する準備ができました!

下記コマンドを実行してgRPCが起動すれば成功です!

$ go cmd/server/run main.go
2023/02/13 10:01:38 start gRPC server port: 8080

 

gRPCクライアントを作成

cmd/server/main.go

 

package main

import (
  "bufio"
  "fmt"
  "os"
  "log"
  "context"
  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials/insecure"
  hellopb "mygrpc/pkg/grpc"
)

var (
  scanner *bufio.Scanner
  client  hellopb.GreetingServiceClient
)

func main() {
  fmt.Println("start gRPC Client.")

  // 1. 標準入力から文字列を受け取るスキャナを用意
  scanner = bufio.NewScanner(os.Stdin)

  // 2. gRPCサーバーとのコネクションを確立
  address := "localhost:8080"
  conn, err := grpc.Dial(
    address,

    grpc.WithTransportCredentials(insecure.NewCredentials()),
    grpc.WithBlock(),
  )
  if err != nil {
    log.Fatal("Connection failed.")
    return
  }
  defer conn.Close()

  // 3. gRPCクライアントを生成
  client = hellopb.NewGreetingServiceClient(conn)

  for {
    fmt.Println("1: send Request")
    fmt.Println("2: exit")
    fmt.Print("please enter >")

    scanner.Scan()
    in := scanner.Text()

    switch in {
    case "1":
      Hello()

    case "2":
      fmt.Println("bye.")
      goto M
    }
  }
M:
}

func Hello() {
  fmt.Println("Please enter your name.")
  scanner.Scan()
  name := scanner.Text()

  req := &hellopb.HelloRequest{
    Name: name,
  }
  res, err := client.Hello(context.Background(), req)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(res.GetMessage())
  }
}

 

クライアントを実行して動作確認

gRPCサーバーとは別のターミナルを開いて、下記を実行することで、

gPRCサーバーに対して、リクエスト送信・レスポンス表示をすることができます。

$ go run cmd/client/main.go
start gRPC Client.

1: Hello
2: exit please enter >1

Please enter your name.
shungo
Hello, shungo!

1: Hello
2: exit please enter >2
bye.

このように、リクエスト送信・レスポンスの表示ができれば成功です!

 

Docker環境で実行する場合

これまではローカル環境にパッケージをインストールして、ローカル環境でgRPC用のコードを生成していました。

gRPC入門の記事を見るとローカルで実行するものが多いですが、バージョン管理などを考慮したらやはりDocker環境で実行したいですよね。

そこで、これまでやってきたことを今後はDocker環境で実行してみようと思います。

 

Dockerfile

FROM golang:1.19.5-buster

ENV PROTOBUF_VERSION3.17.3

RUN apt-getupdate \
    && apt-getinstall -y protobuf-compiler unzip --no-install-recommends \
    && apt-getclean \
    && rm-rf /var/lib/apt/lists/*

WORKDIR /grpc
COPY. /grpc

## build時のみ使いたいので、runで実行。
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
RUN go get google.golang.org/grpc/internal/channelz@v1.53.0

 

docker-compose.yml

version:'3'

services:
  grpc:
    build: .
    volumes:
      - .:/grpc
    ports:
      - "8080:8080"
   tty: true

 

Docker環境で実行する

1つ目のターミナルでdocker-composeを起動します。

$ docker-compose up

2つ目のターミナルでコンテナに入り、gRPCサーバーを起動します。

$ docker-compose exec grpc bash
> go run cmd/server/main.go
2023/02/13 10:01:38 start gRPC server port: 8080

3つ目のターミナルでコンテナに入り、クライアントを動かします。

 

$ docker-compose exec grpc bash
> go run cmd/client/main.go
1: Hello
2:exit
please enter>1

Please enter your name.
shungo
Hello, shungo!

1: Hello
2:exit
please enter>2

bye.

これでDocker環境でもgRPCを動かせることを確認できました!

上記では実行していませんが、コンテナ内で同じようにprotocコマンドでコードを生成することも可能です。

コメント