go-grpc-prometheusでgRPCのmetricsをPrometeusとGrafanaでモニタリングしてみた


gRPC Ecosystemの1つにgo-grpc-prometheusがあります。今回は「gRPC Ecosystemgo-grpc-prometheusを試してみました」エントリです。

go-grpc-prometheus

go-grpc-prometheusはgRPCのmetricsをPrometheusでモニタリングできるログ出力をサポートするインターセプターを提供します。

取得できるmetricsはレポジトリのREADMEにまとまっています。
GitHub - grpc-ecosystem/go-grpc-prometheus: Prometheus monitoring for your gRPC Go servers.

gRPC Goはインターセプターをサポートしていますので次のようにClientとServerそれぞれに設定します。

PrometheusでモニタリングしたmetricsをGrafanaでもモニタリングしてみる

go-grpc-prometheusでgRPCのmetricsが取得できるようになります。Prometheusを起動すればmetricsをモニタリングできるようになります。合わせてPrometheusでモニタリングしているmetricsをGrafanaでもモニタリングしてみます。

シンプルなEchoサービスを作る

unary RPCsを利用してシンプルなEchoサービスを作ります。

proto

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
syntax = "proto3";

option go_package = "protobuf";
package proto;

service EchoService {
  rpc EchoService (Message) returns (Message) {}
}

message Message {
  string message = 1;
}

Server Side

Server sideはgRPCのClientからのリクエストに応えるServer-side of gRPCの役割とPrometeusのためのMetricsを出力する役割の2つが必要です。
1つのPortでHTTP/2 (gRPC)HTTP/1.1のリクエストを解釈する必要があるのでsoheilhy/cmuxを使います。

ほぼ素の使い方ですがServer-sideのソースは次のようになりました。(コード抜粋。詳細はnsoushi/go-grpc-prometheus-demoにあります。)

 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
func main() {

    // Create the main listener.
    s, err := net.Listen("tcp", fmt.Sprintf(":%s", os.Getenv("GRPC_SERVER_PORT")))
    if err != nil {
        log.Fatal(err)
    }

    // Create a cmux.
    m := cmux.New(s)

    // Match connections in order:
    grpcL := m.Match(cmux.HTTP2HeaderField("content-type", "application/grpc"))
    httpL := m.Match(cmux.HTTP1Fast())

    // gRPC server
    grpcS := grpc.NewServer(
        grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
        grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
    )
    pb.RegisterEchoServiceServer(grpcS, newGrpcServer())

    // prometheus metrics server
    grpc_prometheus.Register(grpcS)
    httpS := &http.Server{
        Handler: promhttp.Handler(),
    }

    go grpcS.Serve(grpcL)
    go httpS.Serve(httpL)

    m.Serve()
}

unary RPCsのみなのでgrpc.StreamInterceptorは必要ないですがデモのため入れています。

Client Side

Client Sideはブラウザからリクエストを受け取りgRPCのServer-sideへリクエストを送ってくれるエンドポイントとPrometeusのためのmetricsを出力するエンドポイントの2つを用意します。

Client-sideのソースは次のようになりました。(コード抜粋。詳細はnsoushi/go-grpc-prometheus-demoにあります。)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
    //gRPC connection
    var err error
    conn, err = grpc.Dial(
        fmt.Sprintf("%s:%s", os.Getenv("GRPC_SERVER_HOST"), os.Getenv("GRPC_SERVER_PORT")),
        grpc.WithInsecure(),
        grpc.WithBackoffMaxDelay(time.Second),
        grpc.WithUnaryInterceptor(grpc_prometheus.UnaryClientInterceptor),
        grpc.WithStreamInterceptor(grpc_prometheus.StreamClientInterceptor),
    )
    if err != nil {
        log.Error("Connection error: %v", err)
    }
    defer conn.Close()

    // handle http
    http.Handle("/metrics", promhttp.Handler())
    http.HandleFunc("/echo", echoHandler)
    http.HandleFunc("/", indexHandler)

    // serve http
    http.ListenAndServe(fmt.Sprintf(":%s", os.Getenv("GRPC_CLIENT_PORT")), nil)
}

Prometheusでmetricsを確認する

PrometheusはDockerで起動しました。Dockerで起動するとprometheus.ymlのtargetsにlocalhostとしてもgRPCのServer-sideとClient-sideのホストへはアクセスできないのでdocker-composeを使いコンテナ構成をまとめてホスト解決を行います。

 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
version: "3"

services:
  grpcserver:
    container_name: grpcserver
    build: ./server
    ports:
      - 8080:8080
    environment:
      GRPC_SERVER_HOST: grpcserver
      GRPC_SERVER_PORT: 8080

  grpcclient:
    container_name: grpcclient
    build: ./client
    ports:
      - 8081:8081
    environment:
      GRPC_SERVER_HOST: grpcserver
      GRPC_SERVER_PORT: 8080
      GRPC_CLIENT_HOST: grpcclient
      GRPC_CLIENT_PORT: 8081

  prometheus:
    container_name: prometheus
    build: ./prometheus
    ports:
      - 9090:9090
    depends_on:
      - grpcserver
      - grpcclient

  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
    depends_on:
      - prometheus
      - grpcserver
      - grpcclient

Prometheusのコンテナを起動してhttp://localhost:9090/graphへアクセスするとgRPCのmetricsが insert metric at cursorのメニューに追加されているのが確認できます。

Grafanaでmetricsを確認する

Grafanaのコンテナもdocker-composeに入れましたのでhttp://localhost:3000/loginへアクセスするとGrafanaのダッシュボードを確認できます。Data Sourceにprometheusを追加してDashboardを作成します。

次のようなServer-sideのダッシュボードを作成しました。

gRPCのServer-sideのレスポンス送信数、クライアントからの受信数をGrafanaに設定しました。

nsoushi/go-grpc-prometheus-demografanaフォルダにServer-sideとClient-sideのダッシュボード設定をエクスポートしたJSONがあります。このJSONをインポートするとダッシュボードが簡単に作れます。詳細はレポジトリのREADMEを参照してください。

まとめ

コードを公開しています

コード全体はgitbubで確認できます。