はじめに
こんにちは、グリーエックス株式会社の茶円です。
先日、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
おわりに
本記事ではGoogle Cloud Serviceとアプリケーションのトレース情報を紐付ける際にOpenTelemetry Transformation Languageを使用する方法について紹介しました。
filter processorsやattribute processorsなど、OTTLが不要なprocessorも用意されているため、用途に応じて使い分けてみることが大事だと考えています。 この記事が皆様の役に立てれば幸いです。