gRPCをdockerで動かしたぜんぜんわからんからメモ

gRPCを学ぼうとしているけどぜんぜんわからん

まずGo言語もわからん

プロトコルバッファーもわからん

まともに動かすのも難儀する

 

下記URLを参考にひとまず動くものを手にしようと思ってもうまくいかなかったり四苦八苦したので、Bidirectional streaming RPC を動かしたメモを残す

goでgRPCの4つの通信方式やってみた(Dockerのサンプルあり) - Qiita

 

詰まったポイントとしては、protoファイルに option go_package の記述がないことでエラーになったことの対処だったり、

Dockerにてホスト側の作業ディレクトリの親の親ディレクトリをボリュームマウントすることで不要なファイルまでマウントしてので、作業ディレクトリ配下で完結するようにしたり、

あとコンテナに入らないでコマンド実行する方法をまとめた。

 

環境

・win10 vbox Ubuntu

・docker、docker-compose インストール済

 

準備

ホスト側作業ディレクト

/var/docker/grpc-docker

 

ファイル・フォルダ構成

./docker-compose.yml

./grpc/Dockerfile

./grpc/src/proto/chat.proto

./grpc/src/server/main.go

./grpc/src/client/main.go

./grpc/src/pb/chat ※空フォルダ

 

./docker-compose.yml

version: "3.7"
services:
  go-grpc:
    build:
      context: ./grpc/
      dockerfile: Dockerfile
    container_name: "grpc"
    volumes:
      - ./grpc/src/:/go-grpc
    tty: true
    privileged: true

 

./grpc/Dockerfile

FROM golang:1.13-stretch

SHELL ["/bin/bash", "-c"]
RUN apt update && apt-get install -y vim unzip

# install protc
WORKDIR /protoc
RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v3.11.2/protoc-3.11.2-linux-x86_64.zip
RUN unzip protoc-3.11.2-linux-x86_64.zip
RUN ln -s /protoc/bin/protoc /bin/protoc

# golang
WORKDIR /go-grpc
ENV GO111MODULE on
RUN go get -u github.com/golang/protobuf/protoc-gen-go

 

./grpc/src/proto/chat.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.chat";
option java_outer_classname = "ChatProto";

package chat;

option go_package = "./"; //上記参考サイトのものに追記

service Chat {
  rpc Chat (stream ChatRequest) returns (stream ChatReply) {}
}

message ChatRequest {
  string message = 1;
}

message ChatReply {
  string message = 1;
}

 

./grpc/src/server/main.go

package main

import (
    "fmt"
    "io"
    "log"
    "net"
    "time"

    pb "grpc-docker/pb/chat"

    "github.com/pkg/errors"
    "google.golang.org/grpc"
)

const port = ":50051"

// ServerBidirectional is server
type ServerBidirectional struct {
    pb.UnimplementedChatServer
}

func request(stream pb.Chat_ChatServer, message string) error {
    reply := fmt.Sprintf("%sを受け取ったよ!ありがとう^^", message)
    return stream.Send(&pb.ChatReply{
        Message: reply,
    })
}

// Chat クライアントから受け取った言葉に、言葉を返す
func (s *ServerBidirectional) Chat(stream pb.Chat_ChatServer) error {
    for {
        in, err := stream.Recv()
        if err == io.EOF {
            return nil
        }
        if err != nil {
            return err
        }
        message := in.GetMessage()
        fmt.Println("受取:", message)

        if err := request(stream, message); err != nil {
            return err
        }
        time.Sleep(time.Second * 1)
    }
}

func set() error {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        return errors.Wrap(err, "ポート失敗")
    }
    s := grpc.NewServer()
    var server ServerBidirectional
    pb.RegisterChatServer(s, &server)
    if err := s.Serve(lis); err != nil {
        return errors.Wrap(err, "サーバ起動失敗")
    }
    return nil
}

func main() {
    fmt.Println("起動")
    if err := set(); err != nil {
        log.Fatalf("%v", err)
    }
}

 

./grpc/src/client/main.go

package main

import (
    "context"
    "io"
    "log"
    "time"

    "github.com/pkg/errors"

    pb "grpc-docker/pb/chat"

    "google.golang.org/grpc"
)

func receive(stream pb.Chat_ChatClient) error {
    waitc := make(chan struct{})
    go func() {
        for {
            in, err := stream.Recv()
            if err == io.EOF {
                close(waitc)
                return
            }
            if err != nil {
                log.Fatalf("エラー: %v", err)
            }
            log.Printf("サーバから:%s", in.Message)

            // お返し
            stream.Send(&pb.ChatRequest{
                Message: time.Now().Format("2006-01-02 15:04:05"),
            })
        }
    }()
    <-waitc
    return nil
}

func request(stream pb.Chat_ChatClient) error {
    return stream.Send(&pb.ChatRequest{
        Message: "こんにちは",
    })
}

func chat(client pb.ChatClient) error {
    stream, err := client.Chat(context.Background())
    if err != nil {
        return err
    }
    if err := request(stream); err != nil {
        return err
    }
    if err := receive(stream); err != nil {
        return err
    }
    stream.CloseSend()
    return nil
}

func exec() error {
    address := "localhost:50051"
    conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
        return errors.Wrap(err, "コネクションエラー")
    }
    defer conn.Close()
    client := pb.NewChatClient(conn)
    return chat(client)
}

func main() {
    if err := exec(); err != nil {
        log.Println(err)
    }
}

 

ファイル生成

Dockerコンテナを立ち上げる

docker-compose up -d

 

下記コマンドを実行し、go moduleの初期化を行う

docker-compose exec go-grpc go mod init grpc-docker

すると ./grpc/src/go.mod が生成される

 

下記コマンドを実行し、gRPCファイルの生成

docker-compose exec  go-grpc protoc --proto_path ./proto --go_out=plugins=grpc:./pb/chat chat.proto

すると ./grpc/src/pb/chat/chat.pb.go が生成される

 

サーバとクライアントの実行

下記コマンドを実行し、gRPCサーバを起動する

docker-compose exec go-grpc go run server/main.go

すると ./grpc/src/go.sum が生成される

 

下記コマンドを実行し、gRPCクライアントを起動する

docker-compose exec go-grpc go run client/main.go

すると下記スクショのようにサーバ側とクライアント側で出力が表示される。

f:id:ikasekai:20211118214032p:plain

サーバ側とクライアント側の出力

ようやくgRPC学習のスタートラインに立てた感

次はgRPC-webを動かす方法を調べる

暗中模索

 

PS

A Tour of Go だけじゃ全然わからんけど、

とほほのGo言語入門 - とほほのWWW入門 を併せて参考にすると理解が捗った。