GoでTracing
分散トレーシングとは 複数のサービスが連携して動作するようなシステムの場合、ログが一箇所にまとまっていなかったりリクエスト単位でサービスを横断して追跡できなかったりして解析が困難になることがあります。 分残トレーシングはサービスをまたがって解析できるようにする仕組みです。
トレーシングシステムとしては、Zipkin やJaeger 、GCPのCloud Trace などがあります。
OpenTelemetryとは OpenTelemetryは、ZipkinやJaegerといったプロダクト(ベンダー)に依存せずにトレーシングシステムを利用できるようにする標準的な規格です(大体あっているはず)。
これにより、ある製品をオンプレミスで実行するするときはZipkinを使い、クラウドで実行するときはCloud Traceを使い、ローカルで開発するときはJaegerを使う、といったようなことができるようになります。
Goでの実装例 SDKであるopentelemetry-go を使います。
まずはシンプルな例として、サービスをまたがらずに1つだけの場合です。内容は以下の通り。
関数a
が100msかかる処理をしたあと関数b
を呼ぶ
関数b
が200msかかる処理をしたあと関数c
を呼ぶ
関数c
が300msかかる処理をする
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 package mainimport ( "context" "fmt" "io" "log" "os" "time" gcpexporter "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/jaeger" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.7.0" )var tracer = otel.Tracer("github.com/pecolynx/go-otel/main" )func initTracerExporter (exporterType, jaegerEndPoint string ) (sdktrace.SpanExporter, error ) { switch exporterType { case "jaeger" : return jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(jaegerEndPoint))) case "gcp" : projectID := os.Getenv("GOOGLE_CLOUD_PROJECT" ) return gcpexporter.New(gcpexporter.WithProjectID(projectID)) case "stdout" : return stdouttrace.New( stdouttrace.WithPrettyPrint(), stdouttrace.WithWriter(os.Stderr), ) case "none" : return stdouttrace.New( stdouttrace.WithPrettyPrint(), stdouttrace.WithWriter(io.Discard), ) default : return nil , fmt.Errorf("unsupported exporter: %s" , exporterType) } }func initTracerProvider (exporter sdktrace.SpanExporter, serviceName string ) (*sdktrace.TracerProvider, error ) { tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter), sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String(serviceName), )), ) return tp, nil }func a (ctx context.Context) { ctx, span := tracer.Start(ctx, "a" ) defer span.End() time.Sleep(100 * time.Millisecond) b(ctx) }func b (ctx context.Context) { ctx, span := tracer.Start(ctx, "b" ) defer span.End() time.Sleep(200 * time.Millisecond) c(ctx) }func c (ctx context.Context) { _, span := tracer.Start(ctx, "c" ) defer span.End() time.Sleep(300 * time.Millisecond) }func main () { exporterType := "jaeger" jaegerEndpoint := "http://localhost:14268/api/traces" ctx := context.Background() exp, err := initTracerExporter(exporterType, jaegerEndpoint) if err != nil { log.Fatal(err) } tp, err := initTracerProvider(exp, "go-otel" ) otel.SetTracerProvider(tp) if err != nil { log.Fatal(err) } otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) a(ctx) defer tp.ForceFlush(ctx) }
トレーシングシステムとしてはJaegerを選択しました。 Jaegerをローカルで実行する方法はこちら です。
Jaegerを立ち上げたあと、上記のプログラムを実行します。 その後、ブラウザでhttp://localhost:16686
にアクセスし、Find Traces
をクリックしてリクエストを選択します。 以下のような結果を確認できます。
関数a
の実行に600ms、関数b
の実行に500ms、関数c
の実行に300msかかっていることがわかります。