この記事は Go5 Advent Calendar 2019 20日目の記事です。
はじめに
以前Kubernetes環境でのGoのアプリケーションのトレーシングという記事を書きました。
この記事を書いた際にはOpenCensusのJaeger用のexpoterを使ってJaegerにデータを送信してアプリケーションのトレーシングをするということを試しました。
今回はOpenCensusとOpenTracingの2つのプロジェクトが統合されたOpenTelemetryというOSSを使ってアプリケーションのトレーシングをしていきたいと思います。
やってみる
Jaegerの起動
まず最初にJaegerを起動します。
今回は検証用にローカル環境で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のUIが表示されるのが確認できるかと思います。
アプリケーション側の設定
OpenTelemetryのリポジトリのsampleを参考にGoのアプリケーションをトレーシングする設定を入れて行きます。
コードの全体はGitの下記のレポジトリにあげています。
https://github.com/amber-lamp/opentelemetry-sample/blob/master/main.go
最初にinitTracer関数でexporterとsamplerの設定をします。
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のアプリケーションのトレーシングという記事と同様におみくじの結果を返すようなアプリケーションを書いたので、リクエストに対しておみくじの結果を返すようになっています。
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の設定をしています。
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
Jaegerにして検索をするとアプリケーションの情報が取れているのがわかると思います。
今回はシンプルなアプリケーションなのでメリットがわかりずらいですが、マイクロサービスアーキテクチャで作られているようなサービスの場合にはOpenTelemetryのような技術を使うことでサービス全体がどうなっているか把握しやすくなるのではないかと思います。
まとめ
OpenTelemetryを使ってGoのアプリケーションのトレーシングをしてみました。
今回はOpenTelemetryのサンプルをもとに動かしてみたというだけですので、機会があればOpenTelemetryについてもう少し深堀りして行ければと思っています。