Node.jsアプリケーションのCloud TraceへのTrace送信でハマった記録
最近新しく作っているアプリケーションで、Cloud Traceへの送信でかなりハマったので、試行錯誤の記録を残します。
構成
以下のようなシステム構成です。
環境: Cloud Run Service
言語/Runtime: Node.js v22
Webサーバーフレームワーク: Fastify v5
データベースライブラリ: drizzle-orm v0.42
, pg 8.15
関連ライブラリ: @opentelemetry/{api,core,sdk-node,sdk-trace-node}
, @sentry/{node,opentelemetry}@9.5
なお、 opentelemetry/sdk-node
などは 0.57系を使っており、0.200系にはしていません。これはSentryのライブラリがまだ0.200系に対応していないためです。
エラー収集にSentryを使っています。
前提
今回の記事では自動計装やCloud Traceに送信するにあたり困ったところを書きたいので、O11yの基本的な事項やCloud LoggingとCloud Traceの関連付けなどについては記述しません。
以下のようにGoogle Cloudのドキュメントにも多く書かれているので、そちらを見ていただければ。
ポイント
実現するにあたり、以下のあたりにハマったり困ったりしました。
- SentryとOtel NodeSDKを組み合わせるとSpanが記録されなくなる
- 計装設定
- Fastify計装の選択肢
- DrizzleORMの計装が実装されていない
- ローカルではSpanが記録されるのにCloudTraceに記録されない
Sentry と OpenTelemetry NodeSDK を組み合わせると Span が記録されなくなる
これが1番の問題点でした。
Sentryの設定をすると、内部で自動的に OpenTelemetry のクライアントが初期化される仕組みになっています。
一方で、自前で OpenTelemetry の NodeSDK
をセットアップして併用すると、全体として span が記録されなくなる現象が発生しました。
詳細な原因まではOpenTelemetry周りの知識不足で追えていませんが、Sentryで設定しているProviderとOtel NodeSDKで設定しているProviderで、contextやprocessorが競合しているのかなと思います。
・・・と、最後まで書いて読み返したら気づいたけど、Sentryの設定で openTelemetrySpanProcessors
を設定してないから何も処理されていないっぽかった😇 試しにConsoleSpanProcessorを設定したらちゃんと記録されました。まじか…
とはいえ、Sentryの内部でTracerProviderが設定されているということを知っておかないとたどり着きづらいし、NodeSDKの設定の方が有効にならない(先勝ち?)挙動は見れたので、このまま記事としては残しておこうかなと。
Sentryの初期化時に skipOpenTelemetrySetup: true
を指定し、Sentry 側の OpenTelemetry 初期化を無効化して、自前の OpenTelemetry SDK のみを使用するようにしました。
自前のものを扱うようにしたのは、Sentry内部のものを使うよりもTracing周りの設定が柔軟にできるためです。
計装設定
今回の構成では、自動計装の設定には以下の2通りの方法があります:
- Sentry が提供するIntegration (
fastifyIntegration
,postgresIntegration
など)を使う - OpenTelemetry SDK に
@opentelemetry/instrumentation-*
を設定する
実装を確認したところ、Sentry の Integration は内部で OpenTelemetry の instrumentation を呼び出しているものも多そうだったため、Sentry側の都合も聞きつつ計装できるように、SentryのIntegrationを経由して計装する方針にしました。
Fastify計装の選択肢
Fastify を OpenTelemetry で計装する方法には複数の選択肢があり、どれを使うべきか判断に迷いました。
- OpenTelemetry 公式:
@opentelemetry/instrumentation-fastify
- Sentry の
fastifyIntegration
- Fastify 公式:
@fastify/otel
本来であれば公式の @fastify/otel
を使うべきでしたが、他のライブラリと比べて設定方法が大きく異なり、扱いづらい印象でした。さらに、実装時点ではESM未対応(v0.6.0 で対応済)だったので、あまり使いたくないなという印象でした。
いろいろ試したものの、OpenTelemetry のものはv5に対応していない、SentryのIntegrationも "多分v5でも動くよ" というコメントがついていて怪しかったので、諦めてオフィシャルのものを使うことにしました。
Drizzle ORM の計装が実装されていない
drizzle-orm
v0.42.0 時点では、OpenTelemetry 計装のためのコードが存在していますが、該当部分はコメントアウトされており計装されません。
履歴を辿ると、実行環境により top-level await の問題が起きることが理由のようです。
DBクエリの計装はSentryの postgresIntegration
経由で node-postgres に対して計装するようになっていたので、そちらで賄うようにしました。
ローカルでは Span が記録されるのに Cloud Trace で記録されない
ローカル環境では ConsoleSpanExporter
や Jaeger に対して span を送信しており、計装は正常に機能しているように見えました。
しかし、Cloud Run 上では Cloud Trace にまったく記録されず、トレースが見えない状態になりました。
これの原因は単純で、Cloud Run を実行していたサービスアカウントに telemetry.traces.write
権限が無かったため、Cloud Trace への書き込みが失敗していたようです。
とはいえ何もログも出ずに失敗されるので中々気付けませんでした。
サービスアカウントに Cloud Trace Agent
ロール (roles/cloudtrace.agent
) を付与したことで、Cloud Trace に記録されるようになりました。
おわりに
Cloud Traceで問題なく集計して、Cloud LoggingからTraceを見るところまで持っていけました。
まとめてみるとあっさりした感じになりましたが、既存の多少のコードがあるものの、何を設定しているか分からないし動かない、という状態からだったので、中々大変でした。
たまたまSentryのSDKの内部でTracerProviderの設定をしている箇所を見つけてなぜか計装されない状態の解決に持っていけたり、SentryのIntegrationやOtelの自動計装のコードがどのように実現されているのか、などいろんなコードを読んでなんとかなりました。
これから似たような構成でObservabilityに取り組もうとしている人の参考になれば幸いです。
Discussion