コンテンツにスキップ

OpenTelemetry Go SDK

OpenTelemetry Go SDK は、API、SDK、およびフレームワーク インストルメンテーション ライブラリを通じて Go アプリケーションのテレメトリ データを収集します。 Java エージェントなどのランタイム自動インストルメンテーション ソリューションとは異なり、Go アプリケーションは通常、コード内で SDK を初期化し、Web フレームワーク、HTTP クライアント、データベース、またはメッセージ キューを対応するインストルメンテーション ライブラリでラップする必要があります。

この記事では、DataKit の OpenTelemetry コレクターを使用して OTLP データを受信し、Guance に転送します。

Go アプリケーション + OpenTelemetry Go SDK -> OTLP -> DataKit -> Guance

この記事の例では、OTLP/HTTP + Protobuf を介してトレースとメトリックを送信します。OpenTelemetry Go のトレースとメトリックは安定していますが、Log SDK の成熟度とエコシステムでのサポート状況は変化する可能性があるため、本番利用前に最新の公式状況を確認してください。

前提条件

  • Go 1.23 以降;
  • DataKit がインストールされており、DataKit がターゲット Guance ワークスペースに接続されています。
  • DataKit への Go アプリケーションのネットワーク到達可能性: OTLP/HTTP は DataKit HTTP ポート 9529 を使用し、OTLP/gRPC はデフォルトで 4317 を使用します。
  • アプリケーションが使用するフレームワークまたはコンポーネントに、対応する OpenTelemetry Go インスツルメンテーション ライブラリ が存在するか、OpenTelemetry API を通じてスパンとメトリックを手動で作成する予定であることが確認されています。

1. OpenTelemetry コレクターを有効にする

DataKit のインストールディレクトリにある conf.d/opentelemetry に移動します。コレクター設定がまだ作成されていない場合は、サンプルファイルをコピーします。

cd /usr/local/datakit/conf.d/opentelemetry
sudo cp opentelemetry.conf.sample opentelemetry.conf

opentelemetry.conf に少なくとも次の受信設定が含まれていることを確認します。

[[inputs.opentelemetry]]
  # Guance でカスタム属性をタグとして保持する場合は、このホワイトリストに追加してください。
  # 属性名のドットはアンダースコアに変換されます。例: team.name -> team_name。
  customer_tags = ["team", "project"]

  [inputs.opentelemetry.http]
    http_status_ok = 200
    trace_api = "/otel/v1/traces"
    metric_api = "/otel/v1/metrics"
    logs_api = "/otel/v1/logs"

  [inputs.opentelemetry.grpc]
    addr = "127.0.0.1:4317"
    max_payload = 16777216

上記の設定により、次の受信アドレスが有効になります。

プロトコル データ型 DataKit 受信アドレス
OTLP/HTTP + プロトバッファ トレース http://<DataKit-IP>:9529/otel/v1/traces
OTLP/HTTP + プロトバッファ メトリック http://<DataKit-IP>:9529/otel/v1/metrics
OTLP/HTTP + プロトバッファ ログ http://<DataKit-IP>:9529/otel/v1/logs
OTLP/gRPC トレース、メトリック、ログ http://<DataKit-IP>:4317

アプリケーションと DataKit が同じホスト上にない場合は、実際の構成に応じて DataKit HTTP の待受アドレス、ファイアウォール、その他のネットワーク制御を調整する必要があります。OTLP/gRPC を使う場合は、addr も 0.0.0.0:4317 のようにアプリケーションから到達できる待受アドレスへ変更してください。OTLP の受信ポートをインターネットへ直接公開しないでください。

DataKit を再起動して構成を有効にします。

sudo datakit service restart

DataKit HTTP サービスにアクセスできるかどうかを確認します。

curl http://127.0.0.1:9529/v1/ping

2. アプリケーションを OpenTelemetry に接続する

Go SDK とインストルメンテーション ライブラリをインストールする

公式 SDK、OTLP/HTTP エクスポーター、および net/http インストルメンテーション ライブラリを Go プロジェクト ディレクトリにインストールします。

go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/sdk
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
go get go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
go mod tidy

go.mod と go.sum には依存関係のバージョンが記録されます。本番環境では、OpenTelemetry の依存関係を更新した後に、これら 2 つのファイルをコミットし、コンパイル、単体テスト、トレース回帰テストを実施してください。

SDK を初期化する

次の例では両方を完了します。

  1. OTEL_SERVICE_NAME および OTEL_RESOURCE_ATTRIBUTES からリソース属性を読み取ります。
  2. OTLP/HTTP トレース エクスポーターとメトリック エクスポーターを作成します。
  3. グローバル TracerProvider、MeterProvider、および W3C コンテキスト プロパゲータを登録します。
  4. otelhttp を使用して HTTP ハンドラーをラップし、ビジネス サブスパンとカスタム カウンタを作成します。
  5. バッファー内のデータ損失を避けるために、プロセスの終了時にプロバイダーを更新して閉じます。
package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/propagation"
    sdkmetric "go.opentelemetry.io/otel/sdk/metric"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) {
    res, err := resource.New(
        ctx,
        resource.WithFromEnv(),
        resource.WithTelemetrySDK(),
        resource.WithHost(),
        resource.WithOS(),
        resource.WithProcess(),
    )
    if err != nil {
        return nil, fmt.Errorf("create resource: %w", err)
    }

    traceExporter, err := otlptracehttp.New(ctx)
    if err != nil {
        return nil, fmt.Errorf("create trace exporter: %w", err)
    }

    tracerProvider := sdktrace.NewTracerProvider(
        sdktrace.WithResource(res),
        sdktrace.WithSampler(
            sdktrace.ParentBased(sdktrace.TraceIDRatioBased(1.0)),
        ),
        sdktrace.WithBatcher(traceExporter),
    )

    metricExporter, err := otlpmetrichttp.New(ctx)
    if err != nil {
        _ = tracerProvider.Shutdown(ctx)
        return nil, fmt.Errorf("create metric exporter: %w", err)
    }

    meterProvider := sdkmetric.NewMeterProvider(
        sdkmetric.WithResource(res),
        sdkmetric.WithReader(
            sdkmetric.NewPeriodicReader(
                metricExporter,
                sdkmetric.WithInterval(30*time.Second),
            ),
        ),
    )

    otel.SetTracerProvider(tracerProvider)
    otel.SetMeterProvider(meterProvider)
    otel.SetTextMapPropagator(
        propagation.NewCompositeTextMapPropagator(
            propagation.TraceContext{},
            propagation.Baggage{},
        ),
    )

    shutdown := func(ctx context.Context) error {
        return errors.Join(
            meterProvider.Shutdown(ctx),
            tracerProvider.Shutdown(ctx),
        )
    }
    return shutdown, nil
}

func main() {
    ctx, stop := signal.NotifyContext(
        context.Background(),
        os.Interrupt,
        syscall.SIGTERM,
    )
    defer stop()

    shutdown, err := setupOTelSDK(ctx)
    if err != nil {
        log.Fatal(err)
    }
    defer func() {
        shutdownCtx, cancel := context.WithTimeout(
            context.Background(),
            5*time.Second,
        )
        defer cancel()
        if err := shutdown(shutdownCtx); err != nil {
            log.Printf("shutdown OpenTelemetry: %v", err)
        }
    }()

    meter := otel.Meter("example/order-service")
    requestCounter, err := meter.Int64Counter("app.request.count")
    if err != nil {
        log.Fatal(err)
    }

    mux := http.NewServeMux()
    mux.Handle("/hello", otelhttp.NewHandler(
        http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            requestCtx, span := otel.Tracer("example/order-service").Start(
                r.Context(),
                "prepare-response",
            )
            defer span.End()

            span.SetAttributes(attribute.String("app.route", "/hello"))
            requestCounter.Add(requestCtx, 1)
            _, _ = fmt.Fprintln(w, "hello from OpenTelemetry Go SDK")
        }),
        "GET /hello",
    ))

    server := &http.Server{
        Addr:              ":8080",
        Handler:           mux,
        ReadHeaderTimeout: 5 * time.Second,
    }

    go func() {
        <-ctx.Done()
        shutdownCtx, cancel := context.WithTimeout(
            context.Background(),
            5*time.Second,
        )
        defer cancel()
        if err := server.Shutdown(shutdownCtx); err != nil {
            log.Printf("shutdown HTTP server: %v", err)
        }
    }()

    log.Println("listening on http://127.0.0.1:8080")
    if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
        log.Fatal(err)
    }
}

実際のプロジェクトでは、使用するコンポーネントに対応するインストルメンテーション ライブラリをインストールする必要があります。たとえば、標準ライブラリ net/http は otelhttp を使用します。他の Web フレームワーク、データベース、またはメッセージ キューは、OpenTelemetry Registry から一致するパッケージを選択し、そのパッケージの指示に従ってハンドラー、トランスポート、クライアント、またはドライバーをラップする必要があります。

報告されたアドレスを構成し、{#configure-and-run} を開始します。

次に、OTLP/HTTP + Protobuf を使ってローカルの DataKit に送信します。OTEL_EXPORTER_OTLP_ENDPOINT はベースアドレスで、トレースエクスポーターとメトリックエクスポーターがそれぞれ /v1/traces と /v1/metrics を付与し、最終的に DataKit の /otel/v1/* ルートへ送られます。

export OTEL_SERVICE_NAME="order-service"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment.name=prod,service.version=1.0.0,team=backend"

export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:9529/otel"
export OTEL_EXPORTER_OTLP_INSECURE="true"
export OTEL_EXPORTER_OTLP_COMPRESSION="gzip"

go run .

トレースとメトリクスを生成するリクエストを作成します。

curl http://127.0.0.1:8080/hello

メトリックはデフォルトで 30 秒ごとにエクスポートされ、この例では sdkmetric.WithInterval(30*time.Second) で制御しています。1 回のエクスポート周期を待った後、Guance で service=order-service のトレースを確認し、app.request.count メトリックを検索できます。

OTLP/gRPC {#use-grpc} の使用

Go SDK の OTLP 転送方式は、コードで使用する Exporter パッケージによって決まります。この例では otlptracehttp と otlpmetrichttp を直接使っているため、OTEL_EXPORTER_OTLP_PROTOCOL=grpc を設定しただけでは gRPC へ切り替わりません。

OTLP/gRPC を使用するには、gRPC Exporter をインストールします。

go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
go get go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc
go mod tidy

次に、コード内のエクスポーター パッケージと初期化関数を次のように置き換えます。

traceExporter, err := otlptracegrpc.New(ctx)
metricExporter, err := otlpmetricgrpc.New(ctx)

そして、/v1/traces、/v1/metrics パスなしで gRPC アドレスを使用します。

export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:4317"
export OTEL_EXPORTER_OTLP_INSECURE="true"

3. データレポートパラメータ

リソースパラメータ

この例では resource.WithFromEnv() を介して次の標準環境変数を読み取ります。

環境変数 説明 推奨値または例
OTEL_SERVICE_NAME リソース属性 service.name に対応するサービス名。 order-service; 本番環境では明示的に設定してください。
OTEL_RESOURCE_ATTRIBUTES カンマ区切りの key=value 形式で指定するリソース属性。 deployment.environment.name=prod,service.version=1.0.0,team=backend

Guance でサービス帰属、環境フィルタリング、バージョン分析を行うため、少なくとも service.name、deployment.environment.name、service.version を設定することを推奨します。カスタムリソース属性をタグとして保持するには、事前に DataKit の customer_tags ホワイトリストへ追加してください。属性名の . は _ に変換されます。

OTLP/HTTP パラメータ

otlptracehttp.New() と otlpmetrichttp.New() は、次の環境変数を直接読み取ります。信号固有のパラメーターは、一般的なパラメーターよりも優先されます。

一般的な環境変数 Signal 固有の環境変数 説明 DataKit の例
OTEL_EXPORTER_OTLP_ENDPOINT OTEL_EXPORTER_OTLP_TRACES_ENDPOINT、OTEL_EXPORTER_OTLP_METRICS_ENDPOINT 一般的なパラメータはベース URL であり、Exporter は信号パスを自動的に追加します。信号固有のパラメーターは完全な URL であり、元の値に従って使用されます。 ユニバーサル: http://127.0.0.1:9529/otel;Trace:http://127.0.0.1:9529/otel/v1/traces;Metric:http://127.0.0.1:9529/otel/v1/metrics
OTEL_EXPORTER_OTLP_INSECURE OTEL_EXPORTER_OTLP_TRACES_INSECURE、OTEL_EXPORTER_OTLP_METRICS_INSECURE トランスポート層の TLS を無効化するかどうか。 DataKit が平文 HTTP を使う場合は true を設定します。
OTEL_EXPORTER_OTLP_HEADERS OTEL_EXPORTER_OTLP_TRACES_HEADERS、OTEL_EXPORTER_OTLP_METRICS_HEADERS カンマ区切りの key=value 形式のリクエストヘッダー。 DataKit で expected_headers を設定する場合に対応する値を指定します。
OTEL_EXPORTER_OTLP_TIMEOUT OTEL_EXPORTER_OTLP_TRACES_TIMEOUT、OTEL_EXPORTER_OTLP_METRICS_TIMEOUT 1 回のエクスポートのタイムアウト。単位はミリ秒です。 例: 10000。ネットワーク状況に応じて調整してください。
OTEL_EXPORTER_OTLP_COMPRESSION OTEL_EXPORTER_OTLP_TRACES_COMPRESSION、OTEL_EXPORTER_OTLP_METRICS_COMPRESSION OTLP リクエストの圧縮方式。 gzip に設定できます。圧縮しない場合は空白のままにします。
OTEL_EXPORTER_OTLP_CERTIFICATE OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE、OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE サーバー証明書の検証に使用される PEM CA ファイルへのパス。 HTTPS 経由で OTLP を受信する場合は、証明書によって設定を展開します。

アプリケーションと DataKit が同じホスト上にない場合は、例の 127.0.0.1 をアプリケーションから到達可能な DataKit アドレスへ置き換えてください。

SDK コードパラメータ

次のパラメータは Go SDK 初期化コードによって制御され、同じ名前の共通環境変数を設定しても自動的には有効になりません。

設定項目 サンプルコード 説明
トレースサンプリング sdktrace.ParentBased(sdktrace.TraceIDRatioBased(1.0)) 1.0 はルートトレースを全量サンプリングすることを意味します。本番環境では容量に応じて 0.1 などへ調整し、ParentBased で上流のサンプリング判断を引き継げます。
スパンバッチエクスポート sdktrace.WithBatcher(traceExporter) 本番環境ではバッチ エクスポートをお勧めします。 WithMaxQueueSize、WithMaxExportBatchSize、WithBatchTimeout、および WithExportTimeout を使用してさらに調整できます。
メトリックのエクスポート期間 sdkmetric.WithInterval(30*時間.秒) 定期的なエクスポート間隔を制御します。短すぎると、アプリケーション、ネットワーク、ストレージのオーバーヘッドが増加します。
コンテキスト伝播 TraceContext{}、Baggage{} W3C の traceparent、tracestate、baggage を使用します。呼び出しチェーン上のサービスは伝播形式の互換性を維持してください。
リソースの検出 resource.WithHost()、WithOS()、WithProcess() ホスト、オペレーティング システム、プロセスのプロパティを自動的に補足します。プロセスパラメータやリソース属性のキーなどの機密情報を保存しないでください。

この例では OTLP エクスポーターを直接作成するため、特別な注意が必要です。- OTEL_EXPORTER_OTLP_PROTOCOL は、コードによってすでに選択されている HTTP または gRPC エクスポーターを変更しません。 - OTEL_TRACES_EXPORTER、OTEL_METRICS_EXPORTER は、この例で直接作成されたエクスポーターを閉じません。 - OpenTelemetry Go コア SDK は現在、すべての一般的な SDK 環境変数を自動的に適用しません。特に、OTEL_SDK_DISABLED、OTEL_TRACES_SAMPLER、または OTEL_PROPAGATORS がカスタム初期化コードで自動的に有効になることを想定しないでください。 - 標準環境変数を使用してエクスポーターを動的に選択して初期化する必要がある場合は、公式 Contrib の autoexport パッケージを評価し、テスト環境での動作を検証できます。

検証とトラブルシューティング

  1. curl http://127.0.0.1:9529/v1/ping を実行し、アプリケーションから DataKit に接続できることを確認します。
  2. アプリケーションを起動して /hello にアクセスし、アプリケーションログに create trace exporter、create metric exporter、または OTLP エクスポートエラーが出ていないことを確認します。
  3. 少なくとも 1 つのメトリック エクスポート サイクルを待ちます。
  4. Guance の APM サービス一覧で order-service のトレースを確認します。
  5. データを確認できない場合は、DataKit の opentelemetry コレクター設定、DataKit へのネットワーク、送信 URL、DataKit ログを順に確認します。
  6. 重複スパンが発生した場合は、同じハンドラー、トランスポート、データベース クライアント、またはドライバーが繰り返しパッケージ化されていないか確認します。

参照

フィードバック

このページは役に立ちましたか?