🛠

AWS Distro for OpenTelemetryを試してみた

2023/07/17に公開

概要

OpenTelemetryの学習の一環としてAWS Distro for OpenTelemetryを試した内容をまとめました。OpenTelemetryについては初心者であるため、自分なりに理解した内容を記載しています。

ECS上にアプリケーションを起動し、トレースをX-Ray、メトリクスをCloudWatchで可視化してみました。アプリケーションはGoで実装し、AWS X-Ray SDK for Go、OpenTelemetry Go SDKを利用してトレースデータをX-Rayに送信しています。

OpenTelemetryやAWS Distro for OpenTelemetryについては勉強中であるため、不正確な情報があるかもしれないという点をご留意ください。

OpenTelemetryとは

OpenTelemetryは、Cloud Native Computing Foundation(CNCF)のプロジェクトです。

CNCFとは、CNCFにおけるOpenTelemetryの位置付け

OpenTelemetryの解説の前に、CNCFとCNCFにおけるOpenTelemetryの位置付けについて軽く触れたいと思います。

CNCFはLinux Foundationのプロジェクトの1つで、コンテナ技術の推進とその進化を取り巻くテクノロジー業界の足並みを揃えるために設立された非営利団体です。代表的なプロジェクトにはKubernetesなどがあります。

CNCFではプロジェクトを成熟度レベル別にSandbox、Incubating、Graduatedの3つに分類しており、 OpenTelemetryは成熟したプロジェクトとして認められたGraduatedとなっています。
Graduatedと認められたその他のプロジェクトについて下記のリンクより確認できます。
https://landscape.cncf.io/card-mode?project=graduated

また以下のブログによるとOpenTelemetryはCNCFエコシステムのなかでKubernetesに続く2番目に高速なプロジェクトとなっています。
CNCF、Linux Foundation、およびトップ30のオープンソース プロジェクトの2022年のベロシティに関する考察 - The Linux Foundation

OpenTelemetry

OpenTelemetryはオブザーバビリティのフレームワークです。ベンダーに依存しない実装を提供し、テレメトリデータを収集してバックエンドのツールに転送するための方法を標準化することを目的としています。具体的には、トレース、メトリクス、ログといったテレメトリーデータを作成・管理するためのAPI、SDK、その他のツールを提供しています。 ベンダーに依存した実装では、バックエンドのツールを変更する際にコードの書き換えやツール固有のエージェントを変更する必要がありますが、 標準化されたOpenTelemetryではこういった作業が不要になります。

詳細については以下を参照してください。
OpenTelemetry

OpenTelemetry Collector

OpenTelemetryに収集したテレメトリーデータはSDKなどを利用してアプリケーションから直接バックエンドのツールにエクスポートできる他に、OpenTelemetry Collectorというプロキシも用意されています。

OpenTelemetry Collectorは、テレメトリーデータの収集、処理、バックエンドツールへのエクスポートついて実装を提供します。 以下の図にあるとおり、Receiver、Processor、Exporterの3つの主要コンポーネントから構成されています。

  • Receiver
    データをOpenTelemetry Collectorに取り込む方法です。
    OpenTelemetryネイティブフォーマットをサポートするOTLレシーバーやトレースデータ向けのJaegarやメトリクスデータ向けのPrometheusなどのオープンソースフォーマットをサポートするレシーバーがあります。
  • Processor
    データをエクスポートする前にさまざまな処理を行なえます。フィルタリング、変換などの処理を実行します。
  • Exporter
    データの送信先を設定します。データは内部フォーマットから別の定義されたフォーマットに変換されます。

詳細は下記のページを参照してください。

Configuration | OpenTelemetry

AWS Distro for OpenTelemetry とは

AWS Distro for OpenTelemetryはOpenTelemetryのAWSのディストリビューションです。ディストリビューションとは、OpenTelemetryコンポーネントのカスタマイズされたバージョンのことです。
ディストリビューションの詳細については以下を参照してください。
Distributions | OpenTelemetry
ディストリビューションにはAWS以外にも複数のベンダーがあります。
Vendors | OpenTelemetry

AWS Distro for OpenTelemetryの特徴として、AWSリソースとマネージドサービスからメタデータを取得できます。そのためアプリケーションとアプリケーションが動作しているインフラストラクチャデータを関連付けることができ、問題解決までの時間を短縮できます。

詳細は以下のドキュメントを参照してください。

AWS Distro for OpenTelemetry Collector

AWS Distro for OpenTelemetry Collector (ADOT Collector) は、アップストリームのOpenTelemetry CollectorのAWSサポートバージョンです。

今回はECS Fargate上でサイドカーとして実行します。

ADOT CollectorをECS Fargateのサイドカーとして実行する

AWS Distro for OpenTelemetryのECSのチュートリアルを参考にECS FargateのサイドカーとしてADOT Collectorを実行します。

タスク定義

ECSタスクに3つのコンテナを定義しています。

  • aws-otel-collector
  • aws-otel-emitter
  • aoc-statsd-emitter
{
  "family": "adot-tracing",
  "taskRoleArn": "{{ecsTaskRoleArn}}",
  "executionRoleArn": "{{ecsTaskExecutionRoleArn}}",
  "networkMode": "awsvpc",
  "containerDefinitions": [
      {
          "name": "aws-otel-collector",
          "image": "amazon/aws-otel-collector",
          "essential": true,
          "command": [
              "--config=/etc/ecs/ecs-default-config.yaml"
            ],
          "logConfiguration": {
              "logDriver": "awslogs",
              "options": {
                  "awslogs-group": "adot-tracing-otel-collector",
                  "awslogs-region": "ap-northeast-1",
                  "awslogs-stream-prefix": "ecs"
                }
            },
          "healthCheck": {
              "command": [
                  "/healthcheck"
                ],
              "interval": 5,
              "timeout": 6,
              "retries": 5,
              "startPeriod": 1
            }
        },
      {
          "name": "aws-otel-emitter",
          "image": "{adotTracingServer}",
          "essential": false,
          "dependsOn": [
              {
                  "containerName": "aws-otel-collector",
                  "condition": "START"
                }
            ],
          "logConfiguration": {
              "logDriver": "awslogs",
              "options": {
                  "awslogs-group": "adot-tracing-server",
                  "awslogs-region": "ap-northeast-1",
                  "awslogs-stream-prefix": "ecs"
                }
            }
        },
      {
          "name": "aoc-statsd-emitter",
          "image": "alpine/socat:latest",
          "essential": false,
          "entryPoint": [
              "/bin/sh",
              "-c",
              "while true; do echo 'statsdTestMetric:1|c' | socat -v -t 0 - UDP:127.0.0.1:8125; sleep 1; done"
            ],
          "dependsOn": [
              {
                  "containerName": "aws-otel-collector",
                  "condition": "START"
                }
            ],
          "logConfiguration": {
              "logDriver": "awslogs",
              "options": {
                  "awslogs-group": "adot-tracing-statsd-emitter",
                  "awslogs-region": "ap-northeast-1",
                  "awslogs-stream-prefix": "ecs"
                }
            }
        }
    ],
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "cpu": "256",
    "memory": "512"
}

それぞれのコンテナの詳細は以下の通りです。

aws-otel-collector

AWSが提供しているイメージamazon/aws-otel-collector を利用します。
commandでContollerの設定ファイルを指定します。
今回設定しているファイル--config=/etc/ecs/ecs-default-config.yamlの内容は以下で定義されています。
https://github.com/aws-observability/aws-otel-collector/blob/main/config/ecs/ecs-default-config.yaml

receivers、processors、exporters、serviceの設定内容はそれぞれ以下のようになっています。

  • receivers
    データの受信についての設定です。

    • otlp: OpenTelemetry Go SDKからトレースデータを受信する
    • awsxray: AWS X-Ray SDK for Goからトレースデータを受信する
    • statsd: aoc-statsd-emitterコンテナからStatsDカスタムメトリクスを受信する
  • processors
    データを圧縮し、データ送信に必要な発信接続数を削減するためのバッチ処理を行なうための設定です。 トレース(batch/traces)とメトリクス(batch/metrics)ごとに設定されています。
    batch processorはすべてのCollectorに設定するが強く推奨されています。 詳細については以下のドキュメントを参照してください。
    https://github.com/open-telemetry/opentelemetry-collector/blob/main/processor/batchprocessor/README.md

  • exporters
    データの送信先についての設定です。

    • awsxray: AWS X-Rayに送信
    • awsemf: CloudWatch LogsにEMF (Embedded Metric Format)形式でログを出力することで、ログからCloudWatch Metricsに自動的に変化される
  • service
    receivers、processors、exportersは設定しただけでは有効になりません。
    serviceでpipelinesを設定することで有効になります。tracesとmetricsの2つのpipelineが設定されています。

aws-otel-emitter

HTTP Serverを動かすためのコンテナです。 今回はGoでHTTP Serverを実装しました。Goでの計装については後述します。

aoc-statsd-emitter

StatsDを利用してカスタムメトリクスをCloudWatchに送ります。
送信されたメトリクスは名前空間「ECS/AWSOTel/Application」、メトリクス名「statsdTestMetric」でメトリクスを確認できます。

Goで計装する

トレースデータをCollectorに送る方法として、以下の2つのSDKを利用するパターンをそれぞれ試してみました。 今回は勉強のために実装方法の比較をしたかったのでこのような形にしています。

  • AWS X-Ray SDK for Go
  • OpenTelemetry Go SDK

実装したアプリケーションは簡単なHTTP Serverです。それぞれのSDKで以下の処理を行なっています。

  • S3バケットをリストアップする
  • aws.amazon.com にHTTPリクエストを行なう

リポジトリは以下です。
https://github.com/kobayashi-m42/adot-tracing

AWS X-Ray SDK for Go

aws-xray-sdk-goを利用したパターンです。 SDKはトレースデータをX-Rayに直接送信するのではなく、127.0.0.1:2000に送信します。 ADOT CollectorはUDPポート2000をリッスンしているため、SDKからデータを受信できます。 なお、ADOT Collectorを利用しない場合、X-Rayデーモンを利用することでX-Rayにデータを送信できます。

詳細は以下を参考にしてください。

OpenTelemetry Go SDK

opentelemetry-goを利用したパターンです。

今回はGoを利用しますが、OpenTelemetryはGo以外の言語の計装もサポートしています。言語ごとにテレメトリーデータの対応状況が異なります。
2023年7月現在のGoの対応状況は以下のようになっています。

Language Traces Metrics Logs
Go Stable Beta Not yet implemented

その他の言語や最新の情報は以下を確認してください。
Instrumentation | OpenTelemetry

またOpenTelemetryにおけるトレースについては、以下に記載されています。
Traces | OpenTelemetry

ドキュメントを参考に、前提知識としてトレースを計装するための要素をまとめました。

  • Tracer Provider
    Tracerを提供します。後述するResourceとExporterについても定義します。
  • Tracer
    spansを作成します。
  • Trace Exporters
    トレースデータをバックエンドに出力します。バックエンドには、標準出力、OpenTelemetry Collector、Jaegerのようなツールを設定できます。
  • Context Propagation
    複数のプロセスやサービス間でSpanの関連付けを行ないトレースを組み立てるためのコンセプト。
  • Spans
    トレースの構成要素。

上記の具体的な実装方法について解説した記事は他にもあると思うので、今回はX-Rayにデータを送信する際にポイントとなる部分のみ解説しようと思います。
また、ソースは以下のリポジトリにあります。
https://github.com/kobayashi-m42/adot-tracing

ID Generator

X-RayとOpenTelemetryのデフォルトのトレースIDのフォーマットは異なります。

OpenTelemetryのデフォルトのトレースIDのフォーマットではトレースデータをX-Rayに送信できないため、X-Rayのフォーマットに合わせたIDGeneratorを利用します。
TracerProviderを生成する際に設定します。

	idg := xray.NewIDGenerator()
	tp := sdktrace.NewTracerProvider(
		sdktrace.WithIDGenerator(idg),
	)

Propagator

X-Rayは複数のサービスへのリクエストをトレースするために、X-Amzn-Trace-Idという名前のHTTPヘッダーを利用します。 X-Ray Headerのフォーマットは以下のようになっています。

X-Amzn-Trace-Id: Root={traceId};Parent={parentId};Sampled={samplingFlag}

X-Ray専用のPropagatorを利用することでX-Ray Headerのフォーマットでトレースすることが可能になります。以下のように設定できます。

    otel.SetTracerProvider(tp)
    otel.SetTextMapPropagator(xray.Propagator{})

propagatorの仕組みについては、以下のブログの解説がわかりやすかったので紹介させていただきます。
【OpenTelemetry】カスタムPropagatorでバッチや非同期処理のTraceを行う - ymtdzzz.dev

AWS resource Detector

AWS resource Detectorsを利用することで、アプリケーションを実行するECSの情報を取得できます。
取得した情報は以下の通りです(一部の情報は実際の値から変更しています)。

{
  "default": {
    "otel.resource.aws.ecs.task.arn": "<ECS TASK ARN>",
    "otel.resource.service.version": "1.0.0",
    "net.sock.peer.addr": "14.13.106.32",
    "otel.resource.aws.log.stream.arns": [
      "<LOG STREAM ARN>"
    ],
    "http.flavor": "1.1",
    "otel.resource.aws.log.group.names": [
      "adot-tracing-server"
    ],
    "otel.resource.aws.ecs.cluster.arn": "<ECS Cluster ARN>",
    "otel.resource.aws.ecs.task.family": "adot-tracing",
    "otel.resource.aws.ecs.launchtype": "fargate",
    "otel.resource.aws.ecs.task.revision": "1",
    "otel.resource.service.name": "adot-tracing-sample",
    "otel.resource.aws.log.group.arns": [
      "<LOG GROUP ARN>"
    ],
    "otel.resource.container.id": "",
    "otel.resource.aws.ecs.container.arn": "<ECS Container ARN>",
    "http.wrote_bytes": 49,
    "otel.resource.cloud.platform": "aws_ecs",
    "net.sock.peer.port": 38294,
    "otel.resource.aws.log.stream.names": [
      "<LOG STREAM NAME>"
    ],
    "otel.resource.cloud.provider": "aws",
    "otel.resource.container.name": "ip-10-1-1-53.ap-northeast-1.compute.internal"
  }
}

今回は、サービス名とサービスバージョンを設定したかったので以下のように実装しました。

    resourceAttributes := []attribute.KeyValue{
        semconv.ServiceName("adot-tracing-sample"),
        semconv.ServiceVersion("1.0.0"),
    }

    ecsResourceDetector := ecs.NewResourceDetector()
	ecsRes, err := ecsResourceDetector.Detect(ctx)
    if err != nil {
        return nil, err
    }

    if ecsRes.Attributes() != nil {
        resourceAttributes = append(resourceAttributes, ecsRes.Attributes()...)
    }

    res := resource.NewWithAttributes(
      semconv.SchemaURL,
      resourceAttributes...,
    )

    tracerProvider := trace.NewTracerProvider(
        trace.WithResource(res),
    )

最後に

OpenTelemetryの学習を進めながら実際にAWS Distro for OpenTelemetryを試すことで、よりOpenTelemetryについて理解を深めることができました。 今後もOpenTelemetryだけでなくオブザーバビリティ、分散トレーシングについて理解を深めていきたいです。
実際にADOTを導入する予定はありませんが、OpenTelemetryの状況をふまえて必要なタイミングでプロダクトにも活かしていけたらと思います。

参考

Discussion