让我们用Docker x Go x gRPC来搭建一个服务器吧
前提之类的事项
-
- goの実行環境
-
- dockerの実行環境
- mac os
首先,尝试使用Go来创建一个gRPC服务器。
安装必要的东西
由于在许多地方都有关于gRPCxGo的编写,所以很简单。
安装Go的gRPC库。
go get -u google.golang.org/grpc
安装 Protocol Buffers
- IDL(インターフェイス定義言語)で書かれたファイルをコンパイルするのに必要。
brew install protobuf
安装Protocol Buffers的插件。
- IDLに書かれた定義をGo言語へ変換するのに必要。インターフェイスをGo言語で自動生成する。
go get github.com/golang/protobuf/protoc-gen-go
为了使 protoc-gen-go 工具生效,需要将它放置在已经配置好的 PATH 路径下。可以在 .bash_profile 或其他类似文件中添加以下内容,将路径加入到 PATH 变量中。我个人是将 $GOPATH/bin(我的 GOPATH 路径)加入到了 PATH 中,并将 protoc-gen-go 的可执行文件放置在这个路径下。
export PATH="$GOPATH/bin:$PATH"
type protoc-gen-go -> "protoc-gen-go is ~/go/bin/protoc-gen-go"
※安装gRPC中间件
为了确认在使用Docker时是否建立了通信连接,我想要获取服务器端的访问日志。因此,我将安装这些中间件。
go get -u github.com/grpc-ecosystem/go-grpc-middleware
go get -u github.com/sirupsen/logrus
2. 服务器的实现
假设目录结构如下所示。因为go.mod可以为空,所以请提前创建一个。
server
|
---pb--increment.pb.go
|
---proto--increment.proto
|
---service--increment.go
|
---main.go
---go.mod
首先编写IDL来定义接口。
syntax = "proto3";
package increment;
message IncrementRequest {
int32 number = 1;
}
message IncrementResponse {
int32 number = 1;
}
service IncrementService {
rpc GetAndIncrement (IncrementRequest)
returns (IncrementResponse);
}
生成Go的接口。
# プロジェクトのトップにいることを確認
$ pwd #~/go/src/github.com/TakeruTakeru/server
# protoをコンパイルしてGoのインターフェイスを作成
$ protoc -I ./proto --go_out=plugins=grpc:./pb increment.proto
然后在pb目录下创建increment.pb.go文件进行确认。
进行生成的接口实现。
在service目录下创建increment.go文件,并按以下方式进行实现。
package service
import (
"context"
pb "github.com/TakeruTakeru/gserver/pb"
)
type IncrementService struct {
cacheNum int32
}
func (s *IncrementService) Increment(ctx context.Context, req *pb.IncrementRequest) (*pb.IncrementResponse, error) {
n := req.GetNumber() + 1
return &pb.IncrementResponse{Number: n}, nil
}
func (s *IncrementService) GetAndIncrement(ctx context.Context, req *pb.IncrementRequest) (*pb.IncrementResponse, error) {
if s.cacheNum != 0 {
s.cacheNum = s.cacheNum + 1
} else {
s.cacheNum = req.GetNumber()
}
return &pb.IncrementResponse{Number: s.cacheNum}, nil
}
func NewIncrementService() *IncrementService {
return &IncrementService{}
}
以上的步骤足以涵盖与服务逻辑相关的部分。
接下来要进行服务器的实现。
如果不应用中间件,请参考这里进行实现。
另外,在net.Listen时,请将第二个参数设置为仅指定端口*指定IP则只会监听该IP上的连接,例如如果指定了回环地址,只会接受来自该IP的连接。因此,在开发环境中,成功之前是因为从localhost连接,而通过docker端口转发后就无法连接。
package main
import (
"log"
"net"
"os"
"time"
pb "github.com/TakeruTakeru/gserver/pb"
service "github.com/TakeruTakeru/gserver/service"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
)
func main() {
listen, err := net.Listen("tcp", ":5555")
if err != nil {
log.Fatalln(err)
}
logrus.SetLevel(logrus.DebugLevel)
logrus.SetOutput(os.Stdout)
logrus.SetFormatter(&logrus.JSONFormatter{})
logger := logrus.WithFields(logrus.Fields{})
opts := []grpc_logrus.Option{
grpc_logrus.WithDurationField(func(duration time.Duration) (key string, value interface{}) {
return "grpc.time_ns", duration.Nanoseconds()
}),
}
grpc_logrus.ReplaceGrpcLogger(logger)
server := grpc.NewServer(
grpc_middleware.WithUnaryServerChain(
grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
grpc_logrus.UnaryServerInterceptor(logger, opts...),
),
)
service := service.NewIncrementService()
pb.RegisterIncrementServiceServer(server, service)
if err := server.Serve(listen); err != nil {
panic(err)
}
}
3. gRPC客户端的实现
接下来迅速地创建客户端的实现。
package main
import (
"context"
"fmt"
"log"
"time"
pb "github.com/TakeruTakeru/gserver/pb"
"google.golang.org/grpc"
)
func main() {
connection, err := grpc.Dial("localhost:5555", grpc.WithInsecure())
if err != nil {
log.Fatalln("did not connect: %s", err)
}
defer connection.Close()
client := pb.NewIncrementServiceClient(connection)
context, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
var number int32 = 0
response, err := client.GetAndIncrement(context, &pb.IncrementRequest{Number: number})
if err != nil {
log.Println(err)
}
fmt.Println(response.GetNumber())
response1, err := client.GetAndIncrement(context, &pb.IncrementRequest{Number: number})
if err != nil {
log.Println(err)
}
fmt.Println(response1.GetNumber())
response2, err := client.GetAndIncrement(context, &pb.IncrementRequest{Number: number})
if err != nil {
log.Println(err)
}
fmt.Println(response2.GetNumber()) // 1001
}
由于已经完成了必要的准备工作,现在可以通过实际运行go run来确认功能是否正常。
ls # main.go pb proto service go.mod
go run main.go
进程启动后,在5555端口上开始接受TCP通讯,所以也要运行之前创建的client.go文件,执行”go run”命令。
go run client.go # 1, 2, 3
这样就在本地环境完成了对gRPC的测试确认。
由于这次想在Heroku上运行docker环境的应用程序,所以要包含构建为docker镜像的文件。(关于这个方面我没有仔细调查过,如果有其他方法,还请您告诉我,我会很感激。)
Docker构建
首先要整理安装的grpc等依赖模块。
执行以下命令将依赖模块的信息整理到go.mod文件中。
ls # main.go pb proto service go.mod
vgo list
在这次的docker镜像定义中,我们将按照以下流程进行操作:
首先将main.go文件所在的整个目录复制进来,然后安装vgo以解决依赖关系并进行构建。
FROM golang:latest as gobase
ENV PATH=$PATH:$GOPATH/bin
ENV GOARCH="amd64"
ENV GOOS="linux"
WORKDIR /go/src/github.com/TakeruTakeru
COPY . .
RUN cd ../; go get -u golang.org/x/vgo
RUN go build -o $GOPATH/bin/grpc_server
使用docker build命令构建时,如果使用-t参数并指定grpc_exeample标签的话、、、
Step 8/8 : RUN go build -o $GOPATH/bin main.go
---> Running in 6109eb2d49aa
go: downloading github.com/sirupsen/logrus v1.4.2
go: downloading google.golang.org/grpc v1.24.0
go: downloading github.com/golang/protobuf v1.3.2
go: downloading github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
go: extracting github.com/sirupsen/logrus v1.4.2
go: downloading golang.org/x/sys v0.0.0-20190422165155-953cdadca894
go: extracting github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
go: extracting github.com/golang/protobuf v1.3.2
go: extracting google.golang.org/grpc v1.24.0
go: downloading google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55
go: downloading golang.org/x/net v0.0.0-20190311183353-d8887717615a
go: extracting golang.org/x/sys v0.0.0-20190422165155-953cdadca894
go: extracting golang.org/x/net v0.0.0-20190311183353-d8887717615a
go: downloading golang.org/x/text v0.3.0
go: extracting golang.org/x/text v0.3.0
go: extracting google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55
go: finding github.com/golang/protobuf v1.3.2
go: finding google.golang.org/grpc v1.24.0
go: finding golang.org/x/net v0.0.0-20190311183353-d8887717615a
go: finding google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55
go: finding golang.org/x/sys v0.0.0-20190422165155-953cdadca894
go: finding golang.org/x/text v0.3.0
go: finding github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
go: finding github.com/sirupsen/logrus v1.4.2
我能察觉到你私下自己下载了模块。
使用Docker运行并验证操作。
试着运行Docker。这时候,如果在本地已经启动了服务器,请不要忘记将其关掉。因为这会与Docker容器的端口转发发生冲突,导致容器产生错误。
docker run --name grpc_exeample -p 5555:5555 -it grpc_exeample bash
如果进入容器并且已经成功构建,那么会有一个名为/go/bin/grpc_server的二进制文件,在此运行(./grpc_server)。
打开另一个Shell进程,并执行之前的client.go代码。
如果以下响应在服务器和客户端都输出,则任务完成!
辛苦了!
root@6962b46dce49:/go/bin# ./grpc_server
{"grpc.code":"OK","grpc.method":"GetAndIncrement","grpc.request.deadline":"2019-11-04T02:48:37Z","grpc.service":"increment.IncrementService","grpc.start_time":"2019-11-04T02:48:36Z","grpc.time_ns":167200,"level":"info","msg":"finished unary call with code OK","peer.address":"172.17.0.1:56182","span.kind":"server","system":"grpc","time":"2019-11-04T02:48:36Z"}
{"grpc.code":"OK","grpc.method":"GetAndIncrement","grpc.request.deadline":"2019-11-04T02:48:37Z","grpc.service":"increment.IncrementService","grpc.start_time":"2019-11-04T02:48:36Z","grpc.time_ns":55600,"level":"info","msg":"finished unary call with code OK","peer.address":"172.17.0.1:56182","span.kind":"server","system":"grpc","time":"2019-11-04T02:48:36Z"}
{"grpc.code":"OK","grpc.method":"GetAndIncrement","grpc.request.deadline":"2019-11-04T02:48:37Z","grpc.service":"increment.IncrementService","grpc.start_time":"2019-11-04T02:48:36Z","grpc.time_ns":48800,"level":"info","msg":"finished unary call with code OK","peer.address":"172.17.0.1:56182","span.kind":"server","system":"grpc","time":"2019-11-04T02:48:36Z"}
{"level":"info","msg":"transport: loopyWriter.run returning. connection error: desc = \"transport is closing\"","system":"system","time":"2019-11-04T02:48:36Z"}
1
2
3
我所感受到的
关于gRPC的信息很多,所以并不感到太困扰。
虽然有很多东西需要记住,但是由于有很多回报,所以我还希望更多了解gRPC。
参考的页面
尝试一遍 go-grpc-middleware
将用 Go 编写的服务器部署到 Heroku 上的 Docker
学习使用 Go 语言入门 gRPC 的方法(从环境设置到通信)