gRPC の Stackdriver Trace を Go言語 で使ってみた

## 動機
話題のO11y(詳しくわかってない)を勉強するために最近使っているgRPCをStackdriver Trace を使ってトレーシングしてみます。<br>
まだ理解しきれていない部分が多々あるのでご指摘などありましたらぜひお願いします!<br>

ちなみに分散トレーシングについてはわかりやすく説明してくださっているスライドが公開されています。<br>
[shared 20190115 Advanced Distributed Tracing with Stackdriver Trace - Google スライド](https://docs.google.com/presentation/d/e/2PACX-1vQmh4CkMSzcEnarvmzNBapcmYq8dE0-bNm3HLkBfrAPDK7pXucZZQZTVNMraN34FC3Yc3Fek4Xlpn-q/pub?start=false&loop=false&delayms=3000&slide=id.g405a9dc47b_0_588)<br>

## 調べる
Stackdriver Trace のGo言語用設定を行う<br>
[Go 用の Stackdriver Trace の設定  |  Stackdriver Trace  |  Google Cloud](https://cloud.google.com/trace/docs/setup/go?authuser=3&hl=ja)<br>
[OpenCensus Quickstart](https://opencensus.io/quickstart/go/tracing/)<br>
OpenCensus の stackdriver 用の exporter を設定し、Tracing自体はOpencensusuに従うみたい<br>

以下のような感じで Exporterを設定するみたい

```golang
import (
        "log"
        "os"

        "contrib.go.opencensus.io/exporter/stackdriver"
        "go.opencensus.io/trace"
)

func main() {
        // Create and register a OpenCensus Stackdriver Trace exporter.
        exporter, err := stackdriver.NewExporter(stackdriver.Options{
                ProjectID: os.Getenv("GOOGLE_CLOUD_PROJECT"),
        })
        if err != nil {
                log.Fatal(err)
        }
        trace.RegisterExporter(exporter)
}
```

[OpenCensus Guides gRPC Go](https://opencensus.io/guides/grpc/go/)<br>
上記の設定をしてこのリンクに沿ってコードを変更すればトレースができる。<br>

#### やってく

ローカルでサーバーを立ててアクセスしてみたが、ログが正常に出なかったのでGCEでサーバーを立てて検証してみる。
`protoc` とか `gRPC` についてや GCE の Go 環境についてはわかりやすい記事が沢山あるので適宜参照してください!

```環境
(Mac側)
$ go version
go version go1.11 darwin/amd64
 
(サーバー用 Linux側)
$ go version
go version go1.11.4 linux/amd64
```

gRPC の Helloworld を改変していく感じで利用させていただきます。
[grpc-go/examples/helloworld at master · grpc/grpc-go](https://github.com/grpc/grpc-go/tree/master/examples/helloworld)<br>

ディレクトリ構成が自前のサブモジュールを使っているので少し本家と違いますが適宜置き換えてください(わかりにくくてすみません)<br>

```ディレクトリ構成
go-grpc-template/
├── README.md
├── client
│   └── main.go
├── go.mod
├── go.sum
├── grpc-gen-circleci-template  # コンパイルされた proto コードが入った submodule です
│   ├── doc
│   │   └── readme.md
│   └── helloworld.pb.go
└── server
    └── main.go
```   

go get する

```commands
$ go get go.opencensus.io/*
$ go get contrib.go.opencensus.io/exporter/stackdriver 
``` 



変更後 コード

```golang:server/main.go
package main
 
 import (
 	"context"
 	"contrib.go.opencensus.io/exporter/stackdriver"
 	pb "go-grpc-template/grpc-gen-circleci-template"
 	"go.opencensus.io/plugin/ocgrpc"
 	"go.opencensus.io/trace"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/reflection"
 	"log"
 	"net"
 	"os"
 	"time"
 )
 
 const (
 	port = "0.0.0.0:50051"
 )
 
 // server is used to implement helloworld.GreeterServer.
 type server struct{}
 
 // SayHello implements helloworld.GreeterServer
 func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
 	log.Printf("ctx: %v", ctx)
 	ctx, span := trace.StartSpan(ctx, "grpc-template.server")
 	defer span.End()
 	log.Printf("Received: %v", in.Name)
 	time.Sleep(80 * time.Millisecond)
 	return &pb.HelloReply{Message: "Hello " + in.Name}, nil
 }
 
 func main() {
 	// Create and register a OpenCensus Stackdriver Trace exporter.
 	exporter, err := stackdriver.NewExporter(stackdriver.Options{
 		ProjectID: os.Getenv("GOOGLE_CLOUD_PROJECT"),
 	})
 	if err != nil {
 		log.Fatal(err)
 	}
 	trace.RegisterExporter(exporter)
 
 	/*
 	// Register the views to collect server request count.
 	if err := view.Register(ocgrpc.DefaultServerViews...); err != nil {
 		log.Fatal(err)
 	}
 	*/
 	// Configure 100% sample rate, otherwise, few traces will be sampled.
 	trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
 
 	lis, err := net.Listen("tcp", port)
 	if err != nil {
 		log.Fatalf("failed to listen: %v", err)
 	}
 
 	// Set up a new server with the OpenCensus
 	// stats handler to enable stats and tracing.
 	s := grpc.NewServer(grpc.StatsHandler(new(ocgrpc.ServerHandler)))
 	pb.RegisterGreeterServer(s, &server{})
 
 	// Register reflection service on gRPC server.
 	reflection.Register(s)
 	if err := s.Serve(lis); err != nil {
 		log.Fatalf("failed to serve: %v", err)
 	}
 }
```

`grpc.StatsHandler(new(ocgrpc.ServerHandler))`がないと`TraceID`が伝搬されていかないので注意<br>

(全く本質ではないですがServer側のアクセス制限に気をつけてください。自分はちょいハマりしました。)<br>

```golang:client/main.go
package main
 
import (
	"context"
        "contrib.go.opencensus.io/exporter/stackdriver"
	pb "go-grpc-template/grpc-gen-circleci-template"
	"go.opencensus.io/plugin/ocgrpc"
	"go.opencensus.io/trace"
	"google.golang.org/grpc"
	"log"
	"os"
	"time"
)
 
const (
	address = "<VMの外部IP>:50051"
	defaultName = "world"
)
 
func main() {
 	// Create and register a OpenCensus Stackdriver Trace exporter.
 	exporter, err := stackdriver.NewExporter(stackdriver.Options{
 		ProjectID: os.Getenv("GOOGLE_CLOUD_PROJECT"),
 	})
 	if err != nil {
 		log.Fatal(err)
 	}
 	trace.RegisterExporter(exporter)
 
 	// Configure 100% sample rate, otherwise, few traces will be sampled.
 	trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
 
 	// Set up a connection to the server.
 	conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))
 	if err != nil {
 		log.Fatalf("did not connect: %v", err)
 	}
 	defer conn.Close()
 	c := pb.NewGreeterClient(conn)
 
 	// Contact the server and print out its response.
 	name := defaultName
 	if len(os.Args) > 1 {
 		name = os.Args[1]
 	}
 
 	for i := 0; i < 10; i++ {
 		// Create a span with the background context, making this the parent span.
 		// A span must be closed.
 		ctx, span := trace.StartSpan(context.Background(), "grpc-template.client")
 		time.Sleep(80 * time.Millisecond)
 		r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
 		if err != nil {
 			log.Fatalf("could not greet: %v", err)
 		}
 		log.Printf("trace id: %v", ctx)
 		log.Printf("Greeting: %s", r.Message)
 		span.End()
	}
}
```

こちらも同様に`grpc.WithStatsHandler(&ocgrpc.ClientHandler{})`を忘れると伝搬がなされないので注意!<br>

実行する

```commands
(client, server それぞれのディレクトリで)
$ export GOOGLE_CLOUD_PROJECT="<YOUR_PROJECT_ID>"
$ go run main.go
``` 

コンソールを確認する ([Google Cloud Platform Stackdriver Trace](https://console.cloud.google.com/traces/traces))<br>

[![Image](https://gyazo.com/af8d36fc81ab29a9faa5f908bff27121/thumb/1000)](https://gyazo.com/af8d36fc81ab29a9faa5f908bff27121)<br>

すごいいいいいいいい!!
僕の知識不足で詰まるところはいくつかあったもののこれだけ簡単にトレースできるのは素晴らしすぎる…<br>

次は gRPC Stream とかでも試してみたい<br>