TECH BLOG

OpenTelemetry Transformation LanguageでGCPとアプリケーションのトレース情報を連携する

はじめに

こんにちは、グリーエックス株式会社の茶円です。

先日、Google Kubernetes Engine(GKE)上のアプリケーションのボトルネックになっている処理を把握するために、トレース情報を送信する機能を実装しました。

GCPとアプリケーションのトレース情報を連携する際に、OpenTelemetry Transformation Language(OTTL)を用いてトレース情報の加工を行いました。 本記事ではOTTLを使用したトレース情報の加工について紹介します。

概要


アプリケーションのボトルネックを把握するために

  • トレース情報の生成と送信
  • トレース情報を受け取り表示

する機能が必要です。

Google Cloudを使用しているため、トレース情報を受け取り表示はCloud Traceを使用しました。

トレース情報の生成と送信をさらに細かくみていくと、

  • アプリケーション側
    • トレース情報の生成
    • トレース情報をOpen Telemetryコレクターへ送信 (省略)
  • Open Telemetryコレクター側
    • トレース情報を受信,加工,送信

にわけることができます。 各実装について紹介していきます。

本文

トレース情報の生成

アプリケーションはGo言語とMiddlewareを使用して実装しました。

FiberにはサードパーティのOtelFiberが存在するため、これを使用してトレース情報を生成しました。

package main
 
import (
	"fmt"
 
  	gcppropagator "github.com/GoogleCloudPlatform/opentelemetry-operations-go/propagator"
	"github.com/gofiber/contrib/otelfiber/v2"
	"github.com/gofiber/fiber/v2"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/propagation"
)
 
func main() {
 
  // エクスポーターの設定(省略)
 
	app := fiber.New()
	app.Use(traceMiddleware())
 
	// エンドポイントとハンドラーの設定(省略)
 
	if err := app.Listen(":3000"); err != nil {
		fmt.Printf("Error: %v", err)
	}
}
 
// ミドルウェアの設定
func traceMiddleware() fiber.Handler {
 
	otelMiddleware := otelfiber.Middleware(
		otelfiber.WithTracerProvider(otel.GetTracerProvider()),
 
		// middlewareが生成するスパンにattributeを設定
		otelfiber.WithCustomAttributes(func(c *fiber.Ctx) []attribute.KeyValue {
			return []attribute.KeyValue{
				// リクエストのデータをattributeとして追加
				attribute.String("http.remote_ip", c.IP()),
				attribute.String("http.referrer", string(c.Request().Header.Referer())),
				attribute.String("http.user_agent", string(c.Request().Header.UserAgent())),
 
				attribute.Bool("parent_span_is_lb", true), // middlewareで生成するスパンに印をつける
			}
		}),
		// スパンの名前を設定
		otelfiber.WithSpanNameFormatter(func(ctx *fiber.Ctx) string {
			return ctx.Method() + " " + ctx.Route().Path
		}),
 
		// コンテキストのpropagatorを設定
		otelfiber.WithPropagators(propagation.NewCompositeTextMapPropagator(
			gcppropagator.CloudTraceFormatPropagator{},
			propagation.TraceContext{},
			propagation.Baggage{},
		)),
	)
 
	// リクエスト毎にotelMiddlwareを実行するハンドラーを返す
	return func(c *fiber.Ctx) error {
 
		if err := otelMiddleware(c); err != nil {
			return err
		}
 
		// リクエストが既に完了している場合は処理をスキップ
		ctx := c.UserContext()
		if ctx.Err() != nil {
			return nil
		}
 
		// 次のミドルウェアを実行
		return c.Next()
	}
}

コンテキストのPropagaterでは、CloudTraceFormatPropagatorを設定しています。

このパッケージを使用することで、Google Cloud Serviceが提供しているトレース情報をアプリケーション上でも利用することができます。

GKEへのリクエストは

  • Cloud Load Balancing -> GKE上のService -> GKE上のPod

の順で伝播するようにアプリケーションを実装しました。

Cloud Load BalancingではX-Cloud-Trace-Contextヘッダーを追加し、トレース情報(トレースIDやスパンID)の生成を行っています。 しかし、このスパン情報はCloud Traceに送信されないため、OtelFiberで作成したスパンの親スパンがCloud Trace上で確認することができませんでした。

トレース機能はアプリケーション ロードバランサではサポートされていません。グローバル アプリケーション ロードバランサと従来のアプリケーション ロードバランサは、X-Cloud-Trace-Context ヘッダーが存在しない場合、そのヘッダーを追加します。リージョン外部アプリケーション ロードバランサは、このヘッダーを追加しません。X-Cloud-Trace-Context ヘッダーがすでに存在する場合、変更されていない状態でロードバランサを通過します。ただし、ロードバランサによってトレースやスパンはエクスポートされません。

https://cloud.google.com/load-balancing/docs/https?hl=ja#trace-support

そこで、OTTLでスパンの情報を変換する(親スパンIDの削除)を行うことでスパンを見やすくしていきます。

トレース情報をOpen Telemetryコレクターへ送信

GKE上でのObservabilityの設定方法については、 こちらの記事で説明しています。

トレース情報を受信,加工,送信

OpenTelemetry Transformation Languageはコレクターでテレメトリーを加工する際に使用する言語です。 tranformプロセッサーを使用することで利用することができます。

コレクターの設定ファイルを以下に示します。

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
 
processors:
  # otelFiberのMiddlewareで作成したスパンの親スパンIDを削除する
  transform:
    trace_statements:
      - context: span
        conditions:
          # 条件に一致するスパンの親スパン ID を持つ場合
          - span.attributes["parent_span_is_lb"] == true
        statements:
          # 条件に一致するスパンの親スパン ID を削除
          - set(span.parent_span_id, SpanID(0x0000000000000000))
          # span情報を編集したことを記録
          - set(span.status.message, "span edited by transform processor")
 
exporters:
  # GCP Cloud Trace/Monitoringへのエクスポーター
  googlecloud:
    trace: {}
 
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [transform]
      exporters: [googlecloud]

processors.transform節で変換処理を行うことで親スパンのIDを削除することができました。

注意点

OTTL(traces)のstabilityはbetaのため仕様が変更される場合があります。

スパンコンテキストにおいてもv.0.120.0以降と以前で親スパンIDの指定方法が異なっていました。

# v0.120.0以降
> span.parent_span_id

# v0.120.0以前
> parent_span_id

v0.120.0以降の仕様

v.0.120.0以前の仕様

おわりに

本記事ではGoogle Cloud Serviceとアプリケーションのトレース情報を紐付ける際にOpenTelemetry Transformation Languageを使用する方法について紹介しました。

filter processorsattribute processorsなど、OTTLが不要なprocessorも用意されているため、用途に応じて使い分けてみることが大事だと考えています。 この記事が皆様の役に立てれば幸いです。

2024年4月に新卒でグリーホールディングス株式会社に入社。