本篇教大家如何每隔一段時間驗證 gRPC 服務是否存活,如果想了解什麼是 gRPC 可以參考 這篇『REST 的另一個選擇:gRPC』,這邊就不多介紹 gRPC 了,未來將會是容器的時代, 那該如何檢查容器 Container 是否存活。如果是用 Kubernetes 呢?該如何來撰寫 gRPC 接口搭配 livenessProbe
設定。底下是在 Dockerfile 內可以設定 HEALTHCHECK
來 達到檢查容器是否存活。詳細說明可以參考此連結。
1
2
| HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
|
建立 Health Check proto 接口
打開您的 *.proto
檔案,並且寫入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| message HealthCheckRequest {
string service = 1;
}
message HealthCheckResponse {
enum ServingStatus {
UNKNOWN = 0;
SERVING = 1;
NOT_SERVING = 2;
}
ServingStatus status = 1;
}
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
}
|
存檔後重新產生 Go 程式碼: 檔案存放在 rpc/proto
目錄
1
| $ protoc -I rpc/proto rpc/proto/gorush.proto --go_out=plugins=grpc:rpc/proto
|
或者在 Makefile 內驗證 proto 檔案是否變動才執行:
1
2
3
| rpc/proto/gorush.pb.go: rpc/proto/gorush.proto
protoc -I rpc/proto rpc/proto/gorush.proto \
--go_out=plugins=grpc:rpc/proto
|
建立 Health Interface
如果還有其他接口需要驗證,這就必須建立一個 Health Interface 讓你的服務可以驗證多種 protocol, 建立 health.go
1
2
3
4
5
6
7
8
9
10
11
| package rpc
import (
"context"
)
// Health defines a health-check connection.
type Health interface {
// Check returns if server is healthy or not
Check(c context.Context) (bool, error)
}
|
建立 gRPC 服務
首先要定義一個 Server 結構來實現 Check
接口
1
2
3
4
5
6
7
8
9
10
11
12
| type Server struct {
mu sync.Mutex
// statusMap stores the serving status of the services this Server monitors.
statusMap map[string]proto.HealthCheckResponse_ServingStatus
}
// NewServer returns a new Server.
func NewServer() *Server {
return &Server{
statusMap: make(map[string]proto.HealthCheckResponse_ServingStatus),
}
}
|
這邊可以看到,gRPC 的狀態可以從 proto 產生的 Go 檔案拿到,打開 *.pb.go
,可以找到如下
1
2
3
4
5
6
7
| type HealthCheckResponse_ServingStatus int32
const (
HealthCheckResponse_UNKNOWN HealthCheckResponse_ServingStatus = 0
HealthCheckResponse_SERVING HealthCheckResponse_ServingStatus = 1
HealthCheckResponse_NOT_SERVING HealthCheckResponse_ServingStatus = 2
)
|
接著來實現 Check 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // Check implements `service Health`.
func (s *Server) Check(ctx context.Context, in *proto.HealthCheckRequest) (*proto.HealthCheckResponse, error) {
s.mu.Lock()
defer s.mu.Unlock()
if in.Service == "" {
// check the server overall health status.
return &proto.HealthCheckResponse{
Status: proto.HealthCheckResponse_SERVING,
}, nil
}
if status, ok := s.statusMap[in.Service]; ok {
return &proto.HealthCheckResponse{
Status: status,
}, nil
}
return nil, status.Error(codes.NotFound, "unknown service")
}
|
上面可以看到透過帶入 proto.HealthCheckRequest
得到 gRPC 的回覆,這邊通常都是帶空值, gRPC 會自動回 1
,最後在啟動 gRPC 服務前把 Health Service 註冊上去
1
2
3
4
5
| s := grpc.NewServer()
srv := NewServer()
proto.RegisterHealthServer(s, srv)
// Register reflection service on gRPC server.
reflection.Register(s)
|
這樣大致上完成了 gRPC 伺服器端實作
建立 Client 套件
一樣可以透過 proto 產生的程式碼來撰寫 Client 驗證,建立 client.go
裡面寫入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| package rpc
import (
"context"
"github.com/appleboy/gorush/rpc/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
// generate protobuffs
// protoc --go_out=plugins=grpc,import_path=proto:. *.proto
type healthClient struct {
client proto.HealthClient
conn *grpc.ClientConn
}
// NewGrpcHealthClient returns a new grpc Client.
func NewGrpcHealthClient(conn *grpc.ClientConn) Health {
client := new(healthClient)
client.client = proto.NewHealthClient(conn)
client.conn = conn
return client
}
func (c *healthClient) Close() error {
return c.conn.Close()
}
func (c *healthClient) Check(ctx context.Context) (bool, error) {
var res *proto.HealthCheckResponse
var err error
req := new(proto.HealthCheckRequest)
res, err = c.client.Check(ctx, req)
if err == nil {
if res.GetStatus() == proto.HealthCheckResponse_SERVING {
return true, nil
}
return false, nil
}
switch grpc.Code(err) {
case
codes.Aborted,
codes.DataLoss,
codes.DeadlineExceeded,
codes.Internal,
codes.Unavailable:
// non-fatal errors
default:
return false, err
}
return false, err
}
|
驗證 gRPC 服務是否存活
上述 Client 寫好後,其他開發者可以直接 import 此 package,就可以直接使用。再建立 一個檔案取名叫 check.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| package main
import (
"context"
"log"
"time"
"github.com/go-training/grpc-health-check/rpc"
"google.golang.org/grpc"
)
const (
address = "localhost:9000"
)
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := rpc.NewGrpcHealthClient(conn)
for {
ok, err := client.Check(context.Background())
if !ok || err != nil {
log.Printf("can't connect grpc server: %v, code: %v\n", err, grpc.Code(err))
} else {
log.Println("connect the grpc server successfully")
}
<-time.After(time.Second)
}
}
|
結論
所有程式碼都可以在這邊找到,假設團隊的 gRPC 服務跟 Web 服務器 綁在同一個 Go 程式的話,可以透過撰寫 /healthz
來同時處理 gRPC 及 Http 服務的驗證。在 Kubernetes 內就可以透過設定 livenessProbe
來驗證 Container 是否存活。
1
2
3
4
5
6
| livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 3
periodSeconds: 3
|
See also