Cloud Runのトレースサンプリング: OpenTelemetryとb3の活用
はじめに
前回、Cloud RunのトレースのサンプリングレートはGoogle Cloudに任せようで以下のように書きました。
基本はGoogle Cloudにサンプリングをまかせ、つぶさに観察したいリクエストは強制的にトレースするのが良さそうです。
あるいはX-Cloud-Trace-ContextというGoogle Cloudの仕様に乗っかりたくない場合は、traceparentではなくb3を用いて自前で構築したオブザーバビリティバックエンドへエクスポートする
OpenTelemetry(以下OTel)がコントロールする前にGoogle Cloudがtraceparentに介入してtrace_flagsを決定するのでサンプリングレートを自分たちでフルコントロールするのは難しいという話ですが、今回はOTel、b3、Grafana Tempoを使ってCloud Runへデプロイしたアプリケーションのトレースのサンプリングレートを自由に変えられるか試してみました。
もしGoogle Cloudの仕様から解放されたいと感じた時に役に立つ内容だと思います。
構成
今回の構成です。

コードは前回の記事と同じものをベースにしています。
Cloud Run
前回と同じくserver1とserver2の構成です。
server1のエンドポイントへリクエストするとserver2へリクエストするようにしています。
トレースバックエンド
Grafana Tempoを使いました。
無料プランでもクレカ登録が不要だったり、トレース/メトリクス/ログの容量が相当量あったりと充実しています。
OTel Collector
今回はトレースだけエクスポートするので、以下の設定でCloud Runへデプロイしました。
ちなみにサイドカーではないです。
receivers:
otlp:
protocols:
http:
processors:
batch:
exporters:
otlp:
endpoint: 'Grafana TempoのURL:443'
headers:
authorization: Basic <base64 data>
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp]
How to send traces to Grafana Cloud's Tempo service with OpenTelemetry Collectorにauthorizationは以下の出力を貼り付ける例が記載されていますが、そのままリポジトリへpushしないよう注意です。
$ echo -n "<your user id>:<your api key>" | base64
OTel SDKの設定
traceparentはGoogle Cloudがハンドルを握っているのでb3を使います。
環境変数OTEL_PROPAGATORSをb3に設定するか、SDKへ明示的に定義するなら以下のようにします。
import { B3Propagator } from '@opentelemetry/propagator-b3'
export const openTelemetrySDK = new NodeSDK({
// ... SDKの設定
textMapPropagator: new B3Propagator(),
})
https://<Cloud Runの公開URL>/1へリクエストして結果を確認します。
検証結果
HTTPヘッダーのtraceparent, x-cloud-trace-context, b3は以下のようになりました。

traceparentとx-cloud-trace-contextはGoogle Cloudの仕様でリクエストに自動付与されるので、/1のリクエストのHTTPヘッダーにすでに存在しますがb3はありません。
これは通常、最初のリクエストのHTTPヘッダーにはトレース情報が存在しないからだと思います。
サンプリングのレートも異なり、b3ではX-B3-Sampledが1と全てがサンプリングされています。
Grafana Tempoにも全て送られていました。

次にOTEL_TRACES_SAMPLERとOTEL_TRACES_SAMPLER_ARGをCloud Runの環境変数へ定義してサンプリングの変化を見てみます。サンプリング確率を0.2にしました。取りうるレンジは0~1なので0.2はおそらく20%です。

結果は以下のようにランダムにサンプリングされました。

always_offへ変更すると全くサンプリングされなかったので、Google Cloudの仕様をかいくぐって期待通りにレートをコントロールできることがわかりました。
おわりに
サンプリングの主導権を握りたいならGoogle Cloudが介入しないかつOTelが公式にサポートしているプロパゲーターb3を使うと良いということがわかりましたが、一方でGoogle Cloudの仕様に乗っかることのメリットはあるので、自由の代償として知っておきたいところです。
詳しくはヘンリーさんの事例から学ぶクラウドへのOpenTelemetry導入のハマりどころに書いてありとても参考になるのでぜひご覧ください。
私はCloud Loggingの絞り込み機能をよく使うので恩恵を受けようと思っていますが、自由になれる方法が分かったことは今回の収穫でした。自由と言っても結局何かのお世話にならないと生きていけないのですが。
余談:Cloud RunにとってOpenTelemetryを使う意義
OTelを導入しなくてもCloud Runへのリクエストは自動でトレースが生成されてCloud Traceで表示できる[1]ので、最初は導入意義がよくわかりませんでした。
attributesを自由に追加できるから?と思ったのですが、Cloud Trace APIでトレースラベルを使えばできる[2]ようなのでますますわからなくなりました。
今回の検証で、トレースをよしなにしてくれるというところに導入する意義があると思うに至りました。
試しにOTel無効の状態でデプロイしてhttps://<Cloud Runの公開URL>/1へリクエストすると、一連のリクエストにもかかわらず別々のトレースIDが付与されました。

実はOTel + b3でも同じことになりこちらの理由はよくわからず、おそらくGoogle Cloudはb3に介入しないからなのかもしれませんが、Propagatorがtraceparent or x-cloud-trace-contextの場合は同じトレースIDになります。
traceparentの場合

x-cloud-trace-contextの場合

一連のリクエストを同じトレースIDにするのはおそらくOTelがよしなにやってくれていて、だからこそCloud TraceでOpenTelemetryの使用を推奨しているのかなと思いました。[3][4]
Cloud Trace では、OpenTelemetry の使用をおすすめします。
OpenTelemetry ライブラリは、対応する Trace API の複雑さの一部を隠蔽しているため、Cloud Trace クライアント ライブラリよりも簡単に使用できます。
参考にさせていただいた記事
- 事例から学ぶクラウドへのOpenTelemetry導入のハマりどころ - 株式会社ヘンリー エンジニアブログ
- Vol. 04 Cloud RunでCloud Trace以外のAPMを使う場合の一工夫 - Sansan Tech Blog
Discussion