🐾

OpenTelemetryをDatadog APMで仲良く運用する

2023/12/18に公開

こんにちは、ymtdzzzです。Datadog Advent Calendar 2023の18日目の記事です。

仕事で運用しているサービスへの分散トレーシング導入過程において、OpenTelemetryで出力したテレメトリをDatadog APMで運用する機会がありましたので、特に印象に残っている意思決定を記事にできればと思います。

特に、Datadog APMのみで完結せずにあえてOpenTelemetryを用いるメリットや、実際のところ親和性はどうなのか といった部分に興味のある方の参考になれば幸いです。

OpenTelemetryを使う理由

Datadog APMには公式のトレーシングライブラリが存在しているため、基本的にはそれを用いることが多くなると思います。

しかし、主に2点の理由で、できればOpenTelemetryを使っていきたいと考えました。

理由1)できるだけベンダーロックインを避けたい

分散トレーシングは他のモニタリング・オブザーバビリティ系の仕組みと比較して、実装への結合度は比較的高いと考えています(言語によるものの、単にagentをサイドカーで動かすだけといったケースは少なく、クライアント差し替えやmiddlewareの実装が必要になる場合が多い)。

ライブラリやSaaSの採用時は可能な限り移植性を考慮する必要があると思いますので、そうした理由で、vendor agnosticでほぼデファクトになりつつある(と個人的に考えている)OpenTelemetryを積極的に採用する方針としました。

理由2)Datadog側のOpenTelemetry対応が充実してきた

とはいえ、OpenTelemetryで出力したトレースをDatadog側で収集して分析できなければ採用するメリットはないため、Datadog側の対応状況も重要です。

https://docs.datadoghq.com/ja/opentelemetry/guide/otel_api_tracing_interoperability/

上記の記事にも記載がある通り、DatadogはOpenTelemetryとの相互運用性についても考慮した仕組みを提供してくれています。私としても、運用を検討し始めた2022年10月頃と比べて目に見えるスピードで相互運用性が高まっていることを感じています。

以前、tracecontextとdatadog形式でトレース伝播できなかった時期にissueをあげたのですが、すぐにレスをいただき、2,3ヶ月程度で正式リリースまで持って行ってくれました(ロードマップに乗ってたというのもあるかもですが)。

※OpenTelemetry使うと言っておいてDatadogのlibraryを使っている理由については後述の採用方針参照

採用方針と基本構成

最終的な採用方針としては、主に移植性の観点で下記を落とし所としました。

  • agentやextensionなど、実装変更ほぼ無くinstrumentできる場合はDatadog APMのライブラリを用いる
  • instrumentの過程で実装への依存度が高まる場合はOpenTelemetryのライブラリを用いる
  • コンテキストのフォーマットとしてはW3C tracecontextとDatadog形式をInject, Extractできる構成とする

この方針の元、基本構成はこのようになりました。

PHP製アプリは自動計装できるためDatadog APM提供のライブラリ(extension)を使用し、Golang製アプリはクライアントの差し替えやサーバーのmiddleware調整など実装が必要のためOpenTelemetryのライブラリを使用しました。

また、トレースの収集についても、otel collectorからのexportではなくDatadog Agentを用いた方法を採用しました(otel用のportが用意されているためDatadog特有の設定はほぼ不要)。

現在の運用方法

コンテキストの受け渡し

tracecontextとDatadog、どちらの形式であっても互いにInject, Extractできないといけないため、それぞれの仕組みを利用します。

ただし、DatadogからInject時に両形式を出力する方針としているため、OpenTelemetry側でPropagatorを使う機会はあまり無かったです。

ログとの紐付け

OpenTelemetryにもLoggingの仕様はありますが、Golangを始め未対応のSDKが多いです。そのため、トレースとログの紐付けはDatadog側の仕組みを利用しています。

https://docs.datadoghq.com/ja/tracing/other_telemetry/connect_logs_and_traces/opentelemetry

構造化ロギングを前提として、trace_idspan_idをattributeに含めるような実装を入れています。また、ドキュメント通りここで各IDをotel形式からdd形式に変換しています(いつからか変換不要になった予感してますが、ドキュメントにも言及が無いので誰か知ってたら教えて下さい・・・)。

func (l *MyLogger) addFields(ctx context.Context, fields []zap.Field) []zap.Field {
	spanCtx := trace.SpanFromContext(ctx).SpanContext()
	if !spanCtx.TraceID().IsValid() || !spanCtx.SpanID().IsValid() {
		return fields
	}
	return append(fields,
		zap.String("dd.trace_id", l.convertTraceID(spanCtx.TraceID().String())),
		zap.String("dd.span_id", l.convertTraceID(spanCtx.SpanID().String())),
	)
}

// convertTraceID converts OpenTelemetry formatted ID into Datadog formatted ID.
// see: https://docs.datadoghq.com/ja/tracing/other_telemetry/connect_logs_and_traces/opentelemetry/?tab=go
func (l *MyLogger) convertTraceID(id string) string {
	if len(id) < 16 {
		return ""
	}
	if len(id) > 16 {
		id = id[16:]
	}
	intValue, err := strconv.ParseUint(id, 16, 64)
	if err != nil {
		return ""
	}
	return strconv.FormatUint(intValue, 10)
}

サンプリング方針

今のところのサンプリング方針としては下記のようにしています。

  • 生成側ではパフォーマンス上の懸念が無い限りサンプリングを行わない
  • コスト最適化の観点でサンプリングを行いたい場合

後述の課題でも述べますが、複雑化を避けるためどちらかの仕組みを使うようにしています。

運用してどうだったか

良かったところ

最小限のベンダーロックイン

今の構成で、万が一Datadog以外のSaaSへの乗り換えが発生した場合、実装上の調整としてはせいぜいログ出力でのID変換部分になるため、移行コストは低くなりそうです。

※agentや自動計装しているライブラリに該当するコンポーネントやライブラリを移行先が提供している前提。もし提供していなくても、OpenTelemetryのエコシステムで構築することは可能。

Datadogの他サービスとの連携

ログとの紐付けは最たる例ですが、他にもサービスマップやSLI/SLO、RUMなどの他のDatadog提供機能との連携が行える点については非常に強いと感じます。

課題

両方の関心事を考慮する必要がある(サンプリング等)

サンプリングについてはOpenTelemetryとDatadog APMそれぞれ仕組みを持っているため、特にサービスを跨いだトレーシングを制御する場合、それぞれのサービスで好き勝手サンプリングを行ってしまうと複雑度が増すと共に本来取りたいトレースが取得できないなどトラブルも起きてきそうです。

そのため、先述しましたができるだけDatadog側の取り込み部分で制御するようにする暫定方針としています。ただ、今後Trail Samplingといった詳細なサンプリング制御が必要になった場合に見直しに迫られることがあるかもしれません。

完全に互換性がある訳ではない(リソース名等)

ほとんどの場合問題無いのですが、OpenTelemetryのinstrumentation libraryによってはデフォルト設定では上手くDatadog側で解釈してくれないこともありました。

例えばGolangのnet/httpのinstrumentation libraryで出力したSpanのResourceについて、メソッド名だけになってしまったり(Jaegerなどで表示すると{method} {path}でSpan名出てくる)。

とはいえここについてはEchoのライブラリだと問題無かったり、library側の実装依存なのと、Datadogが悪いという訳では無いです。とはいえ、Datadogのライブラリを使えばそこの調整の手間は恐らく少なく済むのかなと思います。

まとめ

Datadogの機能はありがたく使わせてもらいつつも、ベンダーロックインのリスクについても考慮した運用方針の一例として今回記事を書かせていただきました。可観測性はとても面白い分野だと思いますので、私自身引き続きキャッチアップを続けたいと思います。

この記事が誰かの役に立てば幸いです。

Discussion