bridge を使って OpenCensus / OpenTracing から OpenTelemetry に段階的に移行する
この投稿は OpenTelemetry Advent Calendar 2022 の3日目の記事です。2日目は @ymotongpoo の「Tail Sampling Processorを使ってAPMの使用量を効率化しよう」でした。
OpenTelemetry では OpenCensus 及び OpenTracing の SDK で取得した分散トレーシングのデータ (以降「トレース」と表記) を OpenTelemetry で収集するための手段として bridge が提供されています。この bridge を利用することで、OpenCensus 及び OpenTelemetry 向けに実装されているライブラリを OpenTelemetry でも扱えるようになります。この投稿では bridge の概要と利用方法についてコードを交えながら紹介します。
免責事項
ここで扱う情報は 2022/12/03 時点のものになります。
また OpenTelemetry はシステムの Observability を向上させるためのトレースやログ、メトリクスといった情報全般の取得及び利用をスコープとしていますが、この投稿ではトレースにのみ言及します。
利用方法の例として Go での実装を載せていますが、OpenTelemetry SDK で bridge がサポートされている Java などの他の言語でも同じことが可能です。
背景
以前分散トレーシングの標準化実装として OpenCensus と OpenTracing の2つのプロジェクトが動いていました。しかしながらこれらのプロジェクトが解決したい課題や目標が一致している部分は多いこともあって、2019 年に両方のプロジェクトが統合される形で OpenTelemetry が発足されました。
cf. https://opentelemetry.io/docs/concepts/what-is-opentelemetry/
OpenTelemetry が開始され OpenCensus 及び OpenTracing の両プロジェクトが deprecated になってからしばらく経過している一方で、OpenTelemetry の利用についてはまだ過渡期と言えるでしょう。実際に分散トレーシングの導入手段として OpenTelemetry の利用を推奨している Google Cloud の Go 言語の SDK である google-cloud-go について以前から OpenCensus による Cloud Spanner や Cloud Firestore、BigQuery などサービスの利用に関するトレースの取得がサポートされていましたが、2022/12/03 時点で OpenTelemetry への移行が完了していません。(これは Java など他の言語の SDK も同じような状況です)
cf. https://github.com/googleapis/google-cloud-go/issues/4237
また Google Cloud SDK に限らず、既存のコードベースや利用しているライブラリについて OpenCensus や OpenTracing 向けの実装はあるものの OpenTelemetry 向けのものはまだ存在しない、という状況も少なくありません。
そんな OpenTelemetry ですが、 OpenCensus と OpenTracing の両プロジェクトが統合されて生まれたという背景もあり、プロジェクトの発足当時から両方のプロジェクトとの互換性を保ちながら OpenTelemetry への移行を行えるようにするための手段の提供が計画に練り込まれていました、これが bridge です。
- Straightforward backwards compatibility with both OpenTracing and OpenCensus (via software bridges)
https://www.cncf.io/blog/2019/05/21/a-brief-history-of-opentelemetry-so-far/
この bridge は既に Go や Java を始めとしたいくつかの言語の OpenTelemetry SDK で実装が提供されています。そのためこの bridge を使うことによって、Google Cloud SDK のような OpenCensus や OpenTracing の SDK 経由でトレースを取得するライブラリを OpenTelemetry でも利用することが可能になります。
cf. https://opentelemetry.io/docs/migration/opentracing/
Go における bridge の利用例
実際に Go で bridge を用いて、OpenCensus 及び OpenTracing の SDK で取得されたトレースの情報を OpenTelemetry に取り込む例を紹介します。OpenCensus が利用されているライブラリとして Google Cloud SDK Go の Cloud Spanner の client を、また OpenTracing が利用されているライブラリとして https://github.com/go-redis/redis の OpenTracing 向けの実装を利用しました。
まずは OpenCensus の bridge の設定例です。OpenTelemetry SDK が提供している OpenCensus 向けの bridge 用の Tracer を OpenCensus の default の Tracer として設定します。この default に設定された bridge 用の Tracer は OpenCensus SDK 経由でトレースの情報が生成されたタイミングで参照され、OpenTelemetry SDK で参照できるようにデータの変換を行います。
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/bridge/opencensus"
octrace "go.opencensus.io/trace"
)
// ...
bridge := otel.GetTracerProvider().Tracer("opencensus-bridge")
octrace.DefaultTracer = opencensus.NewTracer(bridge)
- https://github.com/open-telemetry/opentelemetry-go/tree/main/bridge/opencensus
- https://pkg.go.dev/go.opentelemetry.io/otel/bridge/opencensus
OpenTracing も同様に default の Tracer として bridge 向けの Tracer を設定する形になります。OpenCensus と異なり、OpenTelemetry 側の default の Tracer も置き換える必要があります。
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/bridge/opentracing"
octrace "go.opencensus.io/trace"
)
// ...
bridge := otel.GetTracerProvider().Tracer("opentracing-bridge")
bt, wt := opentracing.NewTracerPair(bridge)
otel.SetTracerProvider(wt)
ottrace.SetGlobalTracer(bt)
- https://pkg.go.dev/go.opentelemetry.io/otel/bridge/opentracing
- https://github.com/open-telemetry/opentelemetry-go/tree/main/bridge/opentracing
bridge の設定を含めた全体のコードは次のようになります。今回はトレースの格納先 (exporter) として Cloud Trace を利用しました。
package main
import (
"context"
"log"
"net/http"
"cloud.google.com/go/spanner"
"github.com/go-redis/redis"
"github.com/gorilla/mux"
"google.golang.org/api/option"
texporter "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace"
otredis "github.com/opentracing-contrib/goredis"
ottrace "github.com/opentracing/opentracing-go"
octrace "go.opencensus.io/trace"
"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/bridge/opencensus"
"go.opentelemetry.io/otel/bridge/opentracing"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func main() {
ctx := context.Background()
// Setup Spanner client.
sdb, err := spanner.NewClient(ctx, "projects/your-project-id/instances/your-instance-id/databases/your-database-id")
if err != nil {
log.Fatalf("Failed to create Spanner client %v", err)
}
defer sdb.Close()
// Setup Redis client.
rdb := redis.NewUniversalClient(&redis.UniversalOptions{
Addrs: []string{"127.0.0.1:6379"},
DB: 0,
})
defer rdb.Close()
// Setup OpenTelemetry.
exporter, _ := texporter.New(texporter.WithProjectID("your-project-id"), texporter.WithTraceClientOptions([]option.ClientOption{option.WithTelemetryDisabled()}))
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithBatcher(exporter),
)
otel.SetTracerProvider(tp)
defer tp.Shutdown(context.Background())
// Setup OpenCensus bridge.
boc := otel.GetTracerProvider().Tracer("opencensus-bridge")
octrace.DefaultTracer = opencensus.NewTracer(boc)
// Setup OpenTracing bridge.
bot := otel.GetTracerProvider().Tracer("opentracing-bridge")
bt, wtp := opentracing.NewTracerPair(bot)
otel.SetTracerProvider(wtp)
ottrace.SetGlobalTracer(bt)
r := mux.NewRouter()
name := "hello-server"
r.Use(otelmux.Middleware(name))
r.HandleFunc("/hello", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, spanA := otel.Tracer(name).Start(r.Context(), "Ping Spanner")
// Spanner client creates OpenCensus spans.
stmt := spanner.Statement{SQL: "SELECT 1"}
iter := sdb.Single().Query(ctx, stmt)
defer iter.Stop()
_, _ = iter.Next()
spanA.End()
ctx, spanB := otel.Tracer(name).Start(r.Context(), "Ping Redis")
// Redis client creates OpenTracing spans
rc := otredis.Wrap(rdb).WithContext(ctx)
rc.Ping()
spanB.End()
_, _ = w.Write(([]byte)("Hello, World!"))
}))
http.Handle("/", r)
_ = http.ListenAndServe(":8080", nil)
}
実際にこのプログラム経由で Cloud Trace に送信されたトレースの詳細を見てみると、OpenCensus と OpenTracing の SDK で取得された Spanner 及び Redis へのアクセスの情報が span の形で取得されていることが確認できます。
このように bridge を使うことで OpenTracing 及び OpenCensus 向けのライブラリを OpenTelemetry でも利用することができるため、両プロジェクトから OpenTelemetry への移行について一気に行う必要はなく、OpenTelemetry 向けの実装に順次切り替えていくように形で段階的に進めることができます。
注意するべき点として、bridge は OpenTracing 及び OpenCensus プロジェクトと OpenTelemetry との間の完全な互換性は持っていないということが挙げられます。例として OpenTracing と OpenTelemetry には span を跨いだメタデータの伝搬のための API として Baggage が存在しますが、bridge 環境下では OpenTracing で設定された Baggage の値を OpenTelemetry 側で参照することができません。そのため非互換性による情報の欠損等が起こる可能性があることは留意しておく必要があります。
As a result of this change, baggage which is set using the OpenTracing API is not available to OpenTelemetry Propagators. As a result, mixing the OpenTelemetry and OpenTracing APIs is not recommended when using baggage.
https://opentelemetry.io/docs/migration/opentracing/#baggage
終わりに
今回は OpenTelemetry で提供されている OpenTracing 及び OpenCensus 向けの bridge について紹介しました。特に Google Cloud SDK では OpenCensus がサポートされているため、bridge を設定するだけで Spanner や Firestore といったデータストアへのアクセスの latency や error といった情報がトレースで取得することが可能になり非常に便利です。是非活用してみてください。
次回は @Hidekazu-Karino さんです。
Discussion