OpenTelemetryを使ったGoのアプリケーションのトレーシング


この記事は [Go5 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/go5) 20日目の記事です。

## はじめに
以前[Kubernetes環境でのGoのアプリケーションのトレーシング](https://qiita.com/yuuyamad/items/b6078c08cca46163ce64)という記事を書きました。
この記事を書いた際には[OpenCensus](https://opencensus.io/)の[Jaeger](https://www.jaegertracing.io/)用のexpoterを使って[Jaeger](https://www.jaegertracing.io/)にデータを送信してアプリケーションのトレーシングをするということを試しました。

今回は[OpenCensus](https://opencensus.io/)と[OpenTracing](https://opentracing.io/)の２つのプロジェクトが統合された[OpenTelemetry](https://opentelemetry.io/)というOSSを使ってアプリケーションのトレーシングをしていきたいと思います。

## やってみる
### Jaegerの起動
まず最初に[Jaeger](https://www.jaegertracing.io/)を起動します。
今回は検証用にローカル環境でDockerのAll-in-oneイメージを使います。起動の手順は下記を参考にしました。
https://www.jaegertracing.io/docs/1.16/getting-started/

```
$ docker run -d --name jaeger \
  -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
  -p 5775:5775/udp \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 14268:14268 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.16
```

http://localhost:16686/search にアクセスすると下記のように[Jaeger](https://www.jaegertracing.io/)のUIが表示されるのが確認できるかと思います。

<img width="1671" alt="スクリーンショット 2019-12-20 4.49.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80181/2b761874-7ed1-8cd4-47c1-2d6cc206c142.png">

### アプリケーション側の設定
[OpenTelemetry](https://opentelemetry.io/)のリポジトリの[sample](https://github.com/open-telemetry/opentelemetry-go/blob/master/example/jaeger/main.go)を参考にGoのアプリケーションをトレーシングする設定を入れて行きます。

コードの全体はGitの下記のレポジトリにあげています。
https://github.com/amber-lamp/opentelemetry-sample/blob/master/main.go

最初にinitTracer関数でexporterとsamplerの設定をします。

```go:main.go
func initTracer() func() {
	// Create Jaeger Exporter
	exporter, err := jaeger.NewExporter(
		jaeger.WithCollectorEndpoint("http://localhost:14268/api/traces"),
		jaeger.WithProcess(jaeger.Process{
			ServiceName: "opentelemetry-sample",
			Tags: []core.KeyValue{
				key.String("exporter", "jaeger"),
				key.Float64("float", 312.23),
			},
		}),
	)
	if err != nil {
		log.Fatal(err)
	}

	tp, err := sdktrace.NewProvider(
		sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
		sdktrace.WithSyncer(exporter))
	if err != nil {
		log.Fatal(err)
	}
	global.SetTraceProvider(tp)
	return func() {
		exporter.Flush()
	}
}
```

今回は検証なので、samplerの設定はアクセスした情報をすべてサンプルとして取得するように設定しています。

```
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
```
プロダクション等で利用する場合はリクエストの一部をサンプルとして取得する設定に変更したほうが良いでしょう。

次にmain関数を見てみます。
今回も[Kubernetes環境でのGoのアプリケーションのトレーシング](https://qiita.com/yuuyamad/items/b6078c08cca46163ce64)という記事と同様におみくじの結果を返すようなアプリケーションを書いたので、リクエストに対しておみくじの結果を返すようになっています。

```go:main.go
func main() {
	fn := initTracer()
	defer fn()

	tr := global.TraceProvider().Tracer("component-main")
	fortuneHandler := func(w http.ResponseWriter, req *http.Request) {
		attrs, entries, spanCtx := httptrace.Extract(req.Context(), req)

		req = req.WithContext(distributedcontext.WithMap(req.Context(), distributedcontext.NewMap(distributedcontext.MapUpdate{
			MultiKV: entries,
		})))

		ctx, span := tr.Start(
			req.Context(),
			"fortune",
			trace.WithAttributes(attrs...),
			trace.ChildOf(spanCtx),
		)
		defer span.End()

		span.AddEvent(ctx, "handling this...")
		omikuji := omikuji(ctx)

		_, _ = io.WriteString(w, "運勢は" + omikuji + "です")
	}

	http.HandleFunc("/fortune", fortuneHandler)
	err := http.ListenAndServe(":7777", nil)
	if err != nil {
		panic(err)
	}
}
```

下記のTraceProviderの部分でTracerの設定をしています。

```
tr := global.TraceProvider().Tracer("component-main")
```

fortuneHandlerの中でfortuneというSpanの設定をしています。

```
ctx, span := tr.Start(
			req.Context(),
			"fortune",
			trace.WithAttributes(attrs...),
			trace.ChildOf(spanCtx),
		)
```

最後におみくじの結果を返すomikuji関数の部分です。
こちらもmain関数と同様に関数の最初でTracerとSpanの設定をしています。

```go:main.go
func omikuji(ctx context.Context) string {
	tr := global.TraceProvider().Tracer("component-omikuji")
	_, span := tr.Start(ctx, "omikuji")
	defer span.End()

~~省略~~
}
```

アプリケーションを起動してアクセスしてみます。

```
# 起動
$ go run main.go
```

下記のURLにアクセスするとおみくじの結果が返ってきます。
http://localhost:7777/fortune

<img width="310" alt="スクリーンショット 2019-12-20 5.38.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80181/e33ec399-33f9-e1ff-f61b-f4577068e558.png">

[Jaeger](https://www.jaegertracing.io/)にして検索をするとアプリケーションの情報が取れているのがわかると思います。

<img width="1675" alt="スクリーンショット 2019-12-20 5.41.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80181/18e3527c-6d9b-2a41-022a-5228896092b1.png">

<img width="1676" alt="スクリーンショット 2019-12-20 5.41.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80181/43ebc3de-e468-c696-7df4-808bcdfc92da.png">

今回はシンプルなアプリケーションなのでメリットがわかりずらいですが、マイクロサービスアーキテクチャで作られているようなサービスの場合にはOpenTelemetryのような技術を使うことでサービス全体がどうなっているか把握しやすくなるのではないかと思います。

## まとめ
[OpenTelemetry](https://opentelemetry.io/)を使ってGoのアプリケーションのトレーシングをしてみました。
今回は[OpenTelemetry](https://opentelemetry.io/)のサンプルをもとに動かしてみたというだけですので、機会があれば[OpenTelemetry](https://opentelemetry.io/)についてもう少し深堀りして行ければと思っています。