概要
マイクロサービス構成など分散システムにおいて分散トレーシングを行いたい場合は、サービス間でのContextの伝播が必要になります。
OpenTelemetryでサービス間での伝播をしたい場合の実装方法を説明します。
環境
- Go v1.20.2
- go.opentelemetry.io/otel/trace v1.14.0
今回のアーキテクチャイメージは以下です。
前提知識
用語
Contextの伝播に関わる用語は以下があります。
用語 | 説明 |
---|---|
Carrier | 伝播させるデータを載せる媒体。HTTPヘッダなど |
Inject | Carrierに値をセットする |
Extract | Carrierから値を取り出す |
イメージは以下です。
ref: https://github.com/openzipkin/b3-propagation#overall-process
現状Propagatorの種類はTextMapPropagator
1つだけです。今後バイナリデータを扱うPropagetorが追加される予定です。
Propagators Format
分散トレースはこれまで伝播に使う際のヘッダー名や値のフォーマットなどが以下のように複数存在します。
ref: Propagators API | OpenTelemetry
そして依存するサービスによっては対応しているフォーマットが異なります。
例えばIstioはB3 multi headerやW3C TraceContextをサポートしています。
実装
propagator
先に結論を述べると次のようなPropagatorの設定がクライアント側(gateway)、サーバ側(backend)の両方に必要です。
otel.SetTextMapPropagator( propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, ), )
なのでtrace用packageを用意しておき、その中のinit()関数で上記を設定すると自動的にクライアント側もサーバ側も反映されるようになります。
func init() {
otel.SetTextMapPropagator(
propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
),
)
}
gateway
middleware
前回同様http serverにotelhttp.NewHandler()というミドルウェアを入れます。下のtelemetry.NewHTTPMiddleware
はそれをラップしたものです。
mux := http.NewServeMux() mux.Handle("/", http.HandlerFunc(h.alive)) mux.Handle("/hello", http.HandlerFunc(h.hello)) http.ListenAndServe(":8000", telemetry.NewHTTPMiddleware()(mux))
grpc client interceptor
grpc clientにinterceptorを設定します。
conn, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()), ) if err != nil { log.Fatal(err) } c := pb.NewGreeterClient(conn)
backend
grpc serverにinterceptorを設定します。
s := grpc.NewServer(grpc.UnaryInterceptor(telemetry.NewUnaryServerInterceptor())) pb.RegisterGreeterServer(s, &server{}) err = s.Serve(lis) if err != nil { log.Fatal(err) }
ヘルスチェックを除いたgRPCを対象とします。
func NewUnaryServerInterceptor(opts ...otelgrpc.Option) grpc.UnaryServerInterceptor { opts = append(opts, otelgrpc.WithInterceptorFilter( filters.Not(filters.HealthCheck()), ), ) return otelgrpc.UnaryServerInterceptor(opts...) }
Filterを入れることで以下のようにサンプリング対象とならなくなります。
ref: https://github.com/openzipkin/b3-propagation#overall-process
動作検証
docker-composeで一気に立ち上げます。
version: "3" services: gateway: build: context: . dockerfile: Dockerfile args: - TARGET=gateway ports: - 8000:8000 environment: - BACKEND_ADDR=backend-grpc:8080 - EXPORTER_ENDPOINT=http://jaeger:14268/api/traces backend-grpc: build: context: . dockerfile: Dockerfile args: - TARGET=backend-grpc environment: - EXPORTER_ENDPOINT=http://jaeger:14268/api/traces jaeger: image: "jaegertracing/all-in-one:1.42" ports: - "6831:6831/udp" - "6832:6832/udp" - "5778:5778" - "16686:16686" - "4317:4317" - "4318:4318" - "14250:14250" - "14268:14268" - "14269:14269" - "9411:9411" environment: - COLLECTOR_ZIPKIN_HOST_PORT=:9411 - COLLECTOR_OTLP_ENABLED=true
backendサーバもトレースに含まれていることが確認できます。
その他
サンプルコード
今回のサンプルコードはこちらです。
まとめ
OpenTelemetryでの分散トレースの実装方法を紹介しました。
ほぼSDKで用意されているので簡単に導入ができるようになっています。