AWS Distro for OpenTelemetry (ADOT) Collector と ADOT SDKとでCloudWatch Application Signalsを使ってみた

AWS Distro for OpenTelemetry (ADOT) Collector と ADOT SDKとでCloudWatch Application Signalsを使ってみた

ADOT CollectorでもApplication Signalsは使用できるが、CloudWatch Agentを使用する場合との比較もしよう
2026.02.04

ADOT Collectorのサイドカー構成でCloudWatch Application Signalsを使用したい

こんにちは、のんピ(@non____97)です。

皆さんはADOT Collectorのサイドカー構成でCloudWatch Application Signals(以降、Application Signals)を使用したいなと思ったことはありますか? 私はあります。

ECSにおいてApplication Signalsを使用する際にはCloudWatch Agentのサイドカーコンテナを用意することが多いです。

https://dev.classmethod.jp/articles/amazon-cloudwatch-application-signals-ga/

https://dev.classmethod.jp/articles/ecs-container-insights-with-enhanced-observability-java-application/

https://dev.classmethod.jp/articles/cdk-cloudwatch-application-signals/

AWS公式ドキュメントを確認すると、素早くApplication Signalsを始めとするCloudWatchのAPM機能を使用する場合は、AWS Distro for OpenTelemetry (ADOT) SDK + CloudWatch Agentを使用することが推奨であるような紹介がされています。

AWS Distro for OpenTelemetry を CloudWatch エージェントと一緒に使用

CloudWatch で最も統合されたアプリケーションパフォーマンスモニタリング (APM) エクスペリエンスは、AWS Distro for OpenTelemetry (ADOT) SDK を介して提供され、CloudWatch エージェントと一緒に使用してアプリケーションのメトリクスおよびトレースを収集します。このオプションは、CloudWatch で APM をすばやく使用を開始し、Container Insights や CloudWatch Logs などの機能を備えた、すぐに使用できる統合機能を活用する場合に最適です。詳細については、「Amazon EKS クラスターで Application Signals の有効化」および「Amazon EC2、Amazon ECS、Kubernetes で Application Signals の有効化」を参照してください。

サポートされている計測設定 - Amazon CloudWatch

私は以下記事でECSタスク内で動作しているFluent BitのPrometheus形式のメトリクスをスクレイピングするために、ADOT Collectorのサイドカー構成を試しました。

https://dev.classmethod.jp/articles/aws-firelens-prometheus-metrics-adot-collector-cloudwatch/

既にADOT Collectorのサイドカーコンテナがある状況において、Application Signalsを使いたいからといって簡単にCloudWatch Agentのサイドカーコンテナを追加したくはありません。

先ほどのAWS公式ドキュメントにはApplication SignalsはOpenTelemetry SDK と OpenTelemetry Collectorを使用するパターンでも対応できると記載されています。

OpenTelemetry SDK と Collector を使用する

この設定は、次のユースケースに使用できます。

  1. OpenTelemetry SDK を使用してアプリケーションまたは計画を計測し、現在は OpenTelemetry コレクターを使用しています。
  2. AWS Distro for OpenTelemetry (ADOT) でサポートされていない Erlang や Rust などの言語を使用しています。

詳細については、「OpenTelemetry with CloudWatch」を参照してください。

ということで、既存のADOT Collectorを使ってApplication Signalsを試してみました。

いきなりまとめ

  • ADOT CollectorでもApplication Signalsを使用できる
  • ただし、以下については行われないため注意が必要
    • Container Insightsとの統合
    • CloudWatch Logs ですぐにログ記録
    • 常にトラフィックの 100% に関するメトリクスを取得する
  • ADOT Collector内で使用するコンポーネントの安定性も把握しておこう

やってみた

検証環境

検証環境は以下のとおりです。

AWS Distro for OpenTelemetry (ADOT) Collector と ADOT SDKをとでCloudWatch Application Signalsを使ってみた.png

リソースは全てAWS CDKで管理しています。コードは以下GitHubリポジトリに保存しています。

https://github.com/non-97/ecs-native-blue-green/tree/v2.0.1

登場人物が多くなってきたので、テレメトリ周りのやりとりのみを抽出した図も記載しておきます。

テレメトリ.png

ADOT SDKの自動計装を使ってトレースをADOT Collectorに送信します。

Application Signalsの実装パターンごとの機能比較は以下のとおりです。

機能 ADOT SDK + CloudWatch エージェント Open Telemetry SDK + OpenTelemetry コレクター X-Ray SDK
AWS サポート はい AWS に送信されたデータの場合のみ はい
非標準言語のサポート いいえ あり いいえ
Container Insights の統合 はい いいえ いいえ
CloudWatch Logs ですぐにログ記録 はい いいえ いいえ
すぐに使用できるランタイムメトリクス はい はい いいえ
常にトラフィックの 100% に関するメトリクスを取得する はい サンプリングレート 100% の場合のみ サンプリングレート 100% の場合のみ

抜粋 : サポートされている計測設定 - Amazon CloudWatch

今回はADOT SDK + ADOT Collectorを使用するので、真ん中の「Open Telemetry SDK + OpenTelemetry コレクター」に該当します。

以下対応していない3点が許容できるのであれば、十分ADOT SDK + ADOT Collectorのパターンでも良さそうです。

  • Container Insightsとの統合
  • CloudWatch Logs ですぐにログ記録
  • 常にトラフィックの 100% に関するメトリクスを取得する

「Container Insightsとの統合」はと現時点では具体的に以下のようなものを確認できました。

  • Container Insightsのコンソール上でApplication Signalsで設定したSLOを確認できる
  • Application Signalsのサービスでホストしている環境を確認できる
    • ECS上で動作しているのであれば、ECSクラスター名が判断できる
      • リンクもあるため、ECSクラスターへ遷移できる
  • Application Signalsのサービスでホストしている環境のイベント一覧を確認できる
  • オペレーショナル監査として、サービスクォータ監査などの情報を確認できる

後述するとおり、ADOT CollectorではOTEL_AWS_APPLICATION_SIGNALS_ENABLEDtrueにしても動作しません。統合されている様子を比較するためにこちらの記事のようにサイドカーにCloudWatch Agentを使用する形において、OTEL_AWS_APPLICATION_SIGNALS_ENABLEDtrueにする場合とfalseにする場合とを見比べるとtrueの方はホストされているECSクラスターの情報が表示され、falseの場合はGenericとなっていることが分かります。

16.OTEL_AWS_APPLICATION_SIGNALS_ENABLEDのtrue:false.png

AWS CLIで情報を取得すると以下のようになります。

// OTEL_AWS_APPLICATION_SIGNALS_ENABLED が true 
> aws application-signals get-service \
  --start-time $(date -u -v-1H +"%Y-%m-%dT%H:%M:%SZ") \
  --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") \
  --key-attributes 'Environment=ecs:MainStack-EcsCluster97242B84-HY8y61ouuBfa,Name=sample-ecs-app,Type=Service' \
  --region us-east-1
{
    "Service": {
        "KeyAttributes": {
            "Environment": "ecs:MainStack-EcsCluster97242B84-HY8y61ouuBfa",
            "Name": "sample-ecs-app",
            "Type": "Service"
        },
        "AttributeMaps": [
            {
                "ECS.Cluster": "MainStack-EcsCluster97242B84-HY8y61ouuBfa",
                "ECS.TaskDefinitionFamily": "MainStackTaskDefinitionC957DE9A",
                "PlatformType": "AWS::ECS"
            },
            {
                "InstrumentationType": "INSTRUMENTED"
            }
        ],
        "ServiceGroups": [
            {
                "GroupName": "Related",
                "GroupValue": "MainSt-Alb16-oHFyM7iy5Cgq",
                "GroupSource": "Default",
                "GroupIdentifier": "group/source:inferred/groupType:Related/name:MainSt-Alb16-oHFyM7iy5Cgq"
            },
            {
                "GroupName": "Environment",
                "GroupValue": "ecs:MainStack-EcsCluster97242B84-HY8y61ouuBfa",
                "GroupSource": "Telemetry",
                "GroupIdentifier": "group/source:inferred/groupType:Environment/name:ecs:MainStack-EcsCluster97242B84-HY8y61ouuBfa"
            }
        ],
        "MetricReferences": [
            {
                "Namespace": "ApplicationSignals",
                "MetricType": "LATENCY",
                "Dimensions": [
                    {
                        "Name": "Environment",
                        "Value": "ecs:MainStack-EcsCluster97242B84-HY8y61ouuBfa"
                    },
                    {
                        "Name": "Service",
                        "Value": "sample-ecs-app"
                    }
                ],
                "MetricName": "Latency"
            },
            {
                "Namespace": "ApplicationSignals",
                "MetricType": "ERROR",
                "Dimensions": [
                    {
                        "Name": "Environment",
                        "Value": "ecs:MainStack-EcsCluster97242B84-HY8y61ouuBfa"
                    },
                    {
                        "Name": "Service",
                        "Value": "sample-ecs-app"
                    }
                ],
                "MetricName": "Error"
            },
            {
                "Namespace": "ApplicationSignals",
                "MetricType": "FAULT",
                "Dimensions": [
                    {
                        "Name": "Environment",
                        "Value": "ecs:MainStack-EcsCluster97242B84-HY8y61ouuBfa"
                    },
                    {
                        "Name": "Service",
                        "Value": "sample-ecs-app"
                    }
                ],
                "MetricName": "Fault"
            }
        ],
        "LogGroupReferences": [
            {
                "Identifier": "/ecs/sample-ecs-app",
                "ResourceType": "AWS::Logs::LogGroup",
                "Type": "AWS::Resource"
            }
        ]
    },
    "StartTime": "2026-02-02T20:00:00+09:00",
    "EndTime": "2026-02-02T22:00:01+09:00",
    "LogGroupReferences": [
        {
            "Identifier": "/ecs/sample-ecs-app",
            "ResourceType": "AWS::Logs::LogGroup",
            "Type": "AWS::Resource"
        }
    ]
}

// OTEL_AWS_APPLICATION_SIGNALS_ENABLED が false
> aws application-signals get-service \
   --start-time $(date -u -v-1H +"%Y-%m-%dT%H:%M:%SZ") \
   --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") \
   --key-attributes 'Environment=generic:default,Name=sample-ecs-app_OTEL_AWS_APPLICATION_SIGNALS_ENABLED-false,Type=Service' \
   --region us-east-1
{
    "Service": {
        "KeyAttributes": {
            "Environment": "generic:default",
            "Name": "sample-ecs-app_OTEL_AWS_APPLICATION_SIGNALS_ENABLED-false",
            "Type": "Service"
        },
        "AttributeMaps": [
            {
                "PlatformType": "Generic"
            },
            {
                "Telemetry.Extended": "true"
            },
            {
                "InstrumentationType": "INSTRUMENTED"
            }
        ],
        "ServiceGroups": [
            {
                "GroupName": "Related",
                "GroupValue": "sample-ecs-app_OTEL_AWS_APPLICATION_SIGNALS_ENABLED-false",
                "GroupSource": "Default",
                "GroupIdentifier": "group/source:inferred/groupType:Related/name:sample-ecs-app_OTEL_AWS_APPLICATION_SIGNALS_ENABLED-false"
            },
            {
                "GroupName": "Environment",
                "GroupValue": "generic:default",
                "GroupSource": "Telemetry",
                "GroupIdentifier": "group/source:inferred/groupType:Environment/name:generic:default"
            }
        ],
        "MetricReferences": [
            {
                "Namespace": "ApplicationSignals",
                "MetricType": "LATENCY",
                "Dimensions": [
                    {
                        "Name": "Environment",
                        "Value": "generic:default"
                    },
                    {
                        "Name": "Service",
                        "Value": "sample-ecs-app_OTEL_AWS_APPLICATION_SIGNALS_ENABLED-false"
                    }
                ],
                "MetricName": "Latency"
            },
            {
                "Namespace": "ApplicationSignals",
                "MetricType": "ERROR",
                "Dimensions": [
                    {
                        "Name": "Environment",
                        "Value": "generic:default"
                    },
                    {
                        "Name": "Service",
                        "Value": "sample-ecs-app_OTEL_AWS_APPLICATION_SIGNALS_ENABLED-false"
                    }
                ],
                "MetricName": "Error"
            },
            {
                "Namespace": "ApplicationSignals",
                "MetricType": "FAULT",
                "Dimensions": [
                    {
                        "Name": "Environment",
                        "Value": "generic:default"
                    },
                    {
                        "Name": "Service",
                        "Value": "sample-ecs-app_OTEL_AWS_APPLICATION_SIGNALS_ENABLED-false"
                    }
                ],
                "MetricName": "Fault"
            }
        ],
        "LogGroupReferences": [
            {
                "Identifier": "/ecs/sample-ecs-app",
                "ResourceType": "AWS::Logs::LogGroup",
                "Type": "AWS::Resource"
            }
        ]
    },
    "StartTime": "2026-02-02T20:00:00+09:00",
    "EndTime": "2026-02-02T22:00:01+09:00",
    "LogGroupReferences": [
        {
            "Identifier": "/ecs/sample-ecs-app",
            "ResourceType": "AWS::Logs::LogGroup",
            "Type": "AWS::Resource"
        }
    ]
}

OTEL_AWS_APPLICATION_SIGNALS_ENABLEDtrueの場合は以下のように変更イベントを確認できます。

17.sample-ecs-app.png

Container Insightsコンソールでは以下のように確認できます。

18.Container Insights sample-ecs-app.png

個人的には便利は便利であるものの、運用上ないとどうしようもならないというものではないかなという感覚です。

「CloudWatch Logs ですぐにログ記録」については、トレースとログの関連付けを指しているものと理解しています。今回はFluent BitでエラーログのみCloudWatch Logsに出力するようにしているので効力は発揮するものではないと考えています。全てのアクセスのログ記録したいのであれば良さそうですね。

「常にトラフィックの 100% に関するメトリクスを取得する」については、サンプリングされてX-Rayへ送信されたトレース分から生成するメトリクスで運用上十分なのであれば良いでしょう。

ADOT SDK + CloudWatch AgentのようにX-Rayに送信するトレースは絞りつつも、トラフィックの100%分のメトリクスを生成したい場合はAWS Application Signals ProcessorなるProcessorを使いところですが、ADOT Collector内には組み込まれていません。

Receiver Processor Exporter Extensions
prometheusreceiver attributesprocessor awsxrayexporter healthcheckextension
otlpreceiver resourceprocessor awsemfexporter pprofextension
awsecscontainermetricsreceiver batchprocessor prometheusremotewriteexporter zpagesextension
awsxrayreceiver memorylimiterprocessor debugexporter ecsobserver
statsdreceiver probabilisticsamplerprocessor otlpexporter awsproxy
zipkinreceiver metricstransformprocessor fileexporter sigv4authextension
jaegerreceiver spanprocessor otlphttpexporter filestorage
awscontainerinsightreceiver filterprocessor prometheusexporter
kafka resourcedetectionprocessor datadogexporter
filelogreceiver metricsgenerationprocessor sapmexporter
cumulativetodeltaprocessor signalfxexporter
deltatorateprocessor logzioexporter
groupbytraceprocessor kafka
tailsamplingprocessor loadbalancingexporter
k8sattributesprocessor awscloudwatchlogsexporter

抜粋 : aws-observability/aws-otel-collector: AWS Distro for OpenTelemetry Collector (see ADOT Roadmap at https://github.com/orgs/aws-observability/projects/4)

Issueを確認したところ、Closed as not plannedでクローズされてしまっているので、継続的にメンテナンスされるかどうかはなんとも言えません。

https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/32808?timeline_page=1

ちなみに、Application Signals によって収集されるメトリクスは以下にまとまっています。

https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/AppSignals-MetricsCollected.html

ランタイムメトリクスを取得したい場合はCloudWatch Agentが必須になります。

Application Signals は AWS Distro for OpenTelemetry SDK を使用し、Java および Python アプリケーションから OpenTelemetry 互換メトリクスを自動的に収集します。ランタイムメトリクスを収集するには、次の前提条件を満たす必要があります。

  • CloudWatch エージェントは、バージョン 1.300049.1 以降である必要があります。
  • Amazon CloudWatch Observability EKS アドオンを使用した場合、バージョン 2.30-eksbuild.1 以降である必要があります。アドオンを更新した場合、アプリケーションを再起動する必要があります。
  • Java アプリケーションの場合、AWS Distro for OpenTelemetry SDK for Java の 1.32.5 以降を実行している必要があります。
  • Python アプリケーションの場合、AWS Distro for OpenTelemetry SDK for Python の 0.7.0 以降を実行している必要があります。
  • .Net アプリケーションの場合、AWS Distro for OpenTelemetry SDK for .Net の 1.6.0 以降を実行している必要があります。

ランタイムメトリクスは、Node.js アプリケーションでは収集されません。

コードの紹介

コードの紹介です。

といっても、CloudWatch Agentで対応する場合とそこまで変わりはないものと考えています。

Node.jsのADOT SDK自動計装用のコンテナイメージをInitコンテナとして起動し、マウントしたボリュームに自動計装エージェントをコピーします。

./lib/construct/ecs-construct.ts
const CONTAINER_IMAGES = {
  FLUENT_BIT: "public.ecr.aws/aws-observability/aws-for-fluent-bit:init-3.2.0",
  ADOT_COLLECTOR: "public.ecr.aws/aws-observability/aws-otel-collector:v0.46.0",
  ADOT_NODE_AUTOINSTRUMENTATION:
    "public.ecr.aws/aws-observability/adot-autoinstrumentation-node:v0.8.0",
} as const;
.
.
(中略)
.
.

    const initContainer = this.taskDefinition.addContainer(
      "AdotAutoInstrumentationInitContainer",
      {
        containerName: "init",
        image: cdk.aws_ecs.ContainerImage.fromRegistry(
          CONTAINER_IMAGES.ADOT_NODE_AUTOINSTRUMENTATION
        ),
        essential: false,
        command: [
          "cp",
          "-r",
          "/autoinstrumentation/.",
          "/otel-auto-instrumentation/",
        ],
        logging: cdk.aws_ecs.LogDrivers.awsLogs({
          streamPrefix: "init",
        }),
      }
    );

    initContainer.addMountPoints({
      sourceVolume: "opentelemetry-auto-instrumentation",
      containerPath: "/otel-auto-instrumentation",
      readOnly: false,
    });

アプリケーションコンテナ側ではApplication Signalsを使用するように各種環境変数を設定します。

./lib/construct/ecs-construct.ts
      // NODE_OPTIONS: Node.jsアプリケーション起動時にADOT自動計装エージェントを読み込む
      // initコンテナからコピーされた自動計装スクリプトを--requireで事前読み込みし、自動計装をさせる
      // ref: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Application-Signals-ECS-Sidecar.html
      appEnvironment["NODE_OPTIONS"] =
        "--require /otel-auto-instrumentation/autoinstrumentation.js";

      // OTEL_RESOURCE_ATTRIBUTES: トレースに付与するリソース属性
      // - service.name: Application Signalsダッシュボードに表示されるサービス名
      // - deployment.environment: 環境名。今回は分かりやすいように "ecs:<クラスター名>" 形式を設定
      // ref: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Application-Signals-ECS-Sidecar.html
      // ref: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AppSignals-MetricsCollected.html
      appEnvironment[
        "OTEL_RESOURCE_ATTRIBUTES"
      ] = `service.name=ecs-express-app,deployment.environment=ecs:${this.cluster.clusterName}`;

      // OTEL_EXPORTER_OTLP_PROTOCOL: OTLPエクスポーターのプロトコル
      // http/protobuf(ポート4318)またはgrpc(ポート4317)を選択可能。ADOT Collectorは両方サポート
      // ref: https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/
      appEnvironment["OTEL_EXPORTER_OTLP_PROTOCOL"] = "http/protobuf";

      // OTEL_EXPORTER_OTLP_ENDPOINT: OTLPエクスポーターの送信先エンドポイント
      // 同一タスク内のADOT Collectorサイドカーに送信(localhost:4318はHTTP/protobuf用)
      // ref: https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/
      appEnvironment["OTEL_EXPORTER_OTLP_ENDPOINT"] = "http://localhost:4318";

      // OTEL_TRACES_EXPORTER: トレースのエクスポーター指定
      // "otlp"でOTLPプロトコルを使用してADOT Collectorに送信
      // ref: https://opentelemetry.io/docs/languages/sdk-configuration/general/#otel_traces_exporter
      appEnvironment["OTEL_TRACES_EXPORTER"] = "otlp";

      // OTEL_METRICS_EXPORTER: メトリクスのエクスポーター指定
      // "none"でSDKからのメトリクス送信を無効化。ADOT SDKで計算されるApplication Signalsのメトリクスの出力先は OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT で指定する
      // ref: https://opentelemetry.io/docs/languages/sdk-configuration/general/#otel_metrics_exporter
      appEnvironment["OTEL_METRICS_EXPORTER"] = "none";

      // OTEL_LOGS_EXPORTER: ログのエクスポーター指定
      // "none"でOTel経由のログ送信を無効化。ログはFireLens経由で別途収集
      // ref: https://opentelemetry.io/docs/languages/sdk-configuration/general/#otel_logs_exporter
      appEnvironment["OTEL_LOGS_EXPORTER"] = "none";

      // OTEL_TRACES_SAMPLER: トレースのサンプリング方式
      // "xray"でX-Rayの集中サンプリングを使用。サンプリングルールをAWSコンソールから一元管理可能
      // ref: https://opentelemetry.io/docs/languages/sdk-configuration/general/#otel_traces_sampler
      // ref: https://docs.aws.amazon.com/xray/latest/devguide/xray-console-sampling.html
      appEnvironment["OTEL_TRACES_SAMPLER"] = "xray";

      // OTEL_TRACES_SAMPLER_ARG: X-Rayサンプラーの設定
      // - endpoint: ADOT Collectorのawsproxy拡張機能エンドポイント(ポート2000)
      // - polling_interval: サンプリングルールのポーリング間隔(秒)
      // ref: https://aws-otel.github.io/docs/getting-started/remote-sampling
      // ref: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/awsproxy
      // ref : トレースのサンプリングレート - Amazon CloudWatch https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/Application-Signals-SampleRate.html
      // ref : Tracing and Metrics with the AWS Distro for OpenTelemetry JavaScript Auto-Instrumentation | AWS Distro for OpenTelemetry https://aws-otel.github.io/docs/getting-started/js-sdk/trace-metric-auto-instr#using-x-ray-remote-sampling
      appEnvironment["OTEL_TRACES_SAMPLER_ARG"] =
        "endpoint=http://localhost:2000,polling_interval=300";

      // OTEL_PROPAGATORS: コンテキスト伝播形式
      // - tracecontext: W3C Trace Context
      // - baggage: W3C Baggage
      // - xray: AWS X-Ray形式
      // ref: https://opentelemetry.io/docs/languages/sdk-configuration/general/#otel_propagators
      appEnvironment["OTEL_PROPAGATORS"] = "tracecontext,baggage,xray";

      // OTEL_AWS_APPLICATION_SIGNALS_ENABLED: クライアントサイドメトリクス生成
      // 現状、"true"にする場合はCloudWatch Agentサイドカー(ポート4316)が必要
      // OTel Collector 側で awsapplicationsignalsprocessor を使用することでも対応はできるが、ADOT Collectorには含まれていない
      // falseにし、サーバーサイドでトレースからLatency/Error/Faultメトリクスを生成させる
      // トレースからメトリクスを生成するため、記録されるメトリクスはサンプリングされたトレースのトラフィックのみ = 全てをサンプリングする場合を除いて、全てのトラフィックについてのメトリクスではないため、抜け漏れが発生する
      // ref: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Application-Signals-ECS-Sidecar.html
      // ref : https://github.com/amazon-contributing/opentelemetry-collector-contrib/tree/processor/awsapplicationsignalsprocessor/v0.121.0/processor/awsapplicationsignalsprocessor
      appEnvironment["OTEL_AWS_APPLICATION_SIGNALS_ENABLED"] = "false";

Application Signalsを使用するのにOTEL_AWS_APPLICATION_SIGNALS_ENABLEDfalseとするのは何とも不思議な感覚ですがCloudWatch Agentのサイドカーコンテナが存在しない場合は、基本的にこの形になります。OTEL_AWS_APPLICATION_SIGNALS_ENABLEDtrueにして、動作できるのは現状Application Signalsのメトリクスの受け口があるCloudWatch Agentを使用する場合か、カスタムビルドしたOTel Collectorを使用する場合です。

これらがない状況において、OTEL_AWS_APPLICATION_SIGNALS_ENABLEDtrueOTEL_EXPORTER_OTLP_TRACES_ENDPOINThttp://localhost:4318/v1/tracesにした場合、アプリケーションコンテナの標準エラーとして以下エラーメッセージが記録されていました。

{
    "stack": "Error: PeriodicExportingMetricReader: metrics export failed (error Error: connect ECONNREFUSED 127.0.0.1:4316)\n    at PeriodicExportingMetricReader._doRun (/otel-auto-instrumentation/node_modules/@opentelemetry/sdk-metrics/build/src/export/PeriodicExportingMetricReader.js:86:19)\n    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)\n    at async PeriodicExportingMetricReader._runOnce (/otel-auto-instrumentation/node_modules/@opentelemetry/sdk-metrics/build/src/export/PeriodicExportingMetricReader.js:54:13)",
    "message": "PeriodicExportingMetricReader: metrics export failed (error Error: connect ECONNREFUSED 127.0.0.1:4316)",
    "name": "Error",
    "container_id": "33eb5b65cd1842b6b7b71142ac2853cd-0527074092",
    "container_name": "app",
    "source": "stderr",
    "ecs_cluster": "EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-ULZ9CLYI4Rn6",
    "ecs_task_arn": "arn:aws:ecs:us-east-1:<AWSアカウントID>:task/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-ULZ9CLYI4Rn6/33eb5b65cd1842b6b7b71142ac2853cd",
    "ecs_task_definition": "EcsNativeBlueGreenStackEcsConstructTaskDefinitionF683F4B2:91"
}

ローカルホストの4316にアクセスしようとしていますね。

GitHub上に公開されているJava ScriptのADOT自動計装エージェントのコードを確認すると、APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIGが未設定だと、http/protobufの場合はhttp://localhost:4316/v1/metrics、gRPCの場合はhttp://localhost:4315に送信しようとすることが分かります。

https://github.com/aws-observability/aws-otel-js-instrumentation/blob/main/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts#L450-L492

APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIGOTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINTの環境変数の値をそのまま使用します。

AWS公式ドキュメントを確認すると、OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINTはメトリクスを送信する先であるCloudWatch Agentのサイドカーのアドレスを指定する形であることが分かります。

環境変数 Application Signals を有効にする設定
OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT CloudWatch サイドカーにメトリクスを送信するには、http://localhost:4316/v1/metrics に設定します。

抜粋 : サイドカー戦略を使用してデプロイする - Amazon CloudWatch

先述のとおり、ADOT CollectorではApplication Signalsのメトリクスを処理するコンポーネントが含まれていないため、ADOT Collectorのアドレスを指定しても処理することはできません。

OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINThttp://localhost:4318とした場合は以下のようなエラーメッセージが出力されます。

{
    "stack": "Error: PeriodicExportingMetricReader: metrics export failed (error OTLPExporterError: Not Found)\n    at PeriodicExportingMetricReader._doRun (/otel-auto-instrumentation/node_modules/@opentelemetry/sdk-metrics/build/src/export/PeriodicExportingMetricReader.js:86:19)\n    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)\n    at async PeriodicExportingMetricReader._runOnce (/otel-auto-instrumentation/node_modules/@opentelemetry/sdk-metrics/build/src/export/PeriodicExportingMetricReader.js:54:13)",
    "message": "PeriodicExportingMetricReader: metrics export failed (error OTLPExporterError: Not Found)",
    "name": "Error",
    "container_id": "b975ef6d12e640ef99c10f6c77838e71-0527074092",
    "container_name": "app",
    "source": "stderr",
    "ecs_cluster": "EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-ULZ9CLYI4Rn6",
    "ecs_task_arn": "arn:aws:ecs:us-east-1:<AWSアカウントID>:task/EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-ULZ9CLYI4Rn6/b975ef6d12e640ef99c10f6c77838e71",
    "ecs_task_definition": "EcsNativeBlueGreenStackEcsConstructTaskDefinitionF683F4B2:85"
}

ADOT Collectorの設定ファイルは以下のとおりです。

./src/otel-config/otel-app-signals.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

  prometheus:
    config:
      scrape_configs:
        - job_name: "fluent-bit-metrics"
          scrape_interval: 1m
          scrape_timeout: 10s
          static_configs:
            - targets: ["127.0.0.1:2020"]
          metrics_path: "/api/v2/metrics/prometheus"

extensions:
  sigv4auth:
    region: ${AWS_REGION}
    service: xray

  awsproxy:
    endpoint: 0.0.0.0:2000
    service_name: xray
    region: ${AWS_REGION}

processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 100
    spike_limit_mib: 25

  filter/exclude_health:
    traces:
      span:
        - 'attributes["http.route"] == "/health"'

  filter/exclude_names:
    metrics:
      datapoint:
        - 'attributes["name"] == "tcp.0"'
        - 'attributes["name"] == "forward.2"'
        - 'attributes["name"] == "re_emitted"'
        - 'IsMatch(attributes["name"], "^null\\..*")'
        - 'IsMatch(attributes["name"], "^emitter_for_multiline\\..*")'

  resourcedetection:
    detectors: [env, ecs]
    timeout: 5s
    override: false

  resource:
    attributes:
      - key: aws.ecs.cluster.arn
        pattern: "^arn:aws:ecs:[^:]+:[^:]+:cluster/(?P<ClusterName>[^/]+)$"
        action: extract
      - key: TaskId
        from_attribute: aws.ecs.task.id
        action: insert
      - key: TaskDefinitionFamily
        from_attribute: aws.ecs.task.family
        action: insert
      - key: aws.ecs.task.arn
        action: delete
      - key: aws.log.group.arns
        action: delete
      - key: aws.log.stream.arns
        action: delete

  metricstransform:
    transforms:
      - include: "^(.*)_total$"
        match_type: regexp
        action: update
        new_name: "$${1}"

  batch:
    timeout: 60s

exporters:
  otlphttp:
    traces_endpoint: https://xray.${AWS_REGION}.amazonaws.com/v1/traces
    compression: gzip
    auth:
      authenticator: sigv4auth

  awsemf/fluent_bit:
    namespace: ECS/FluentBit
    log_group_name: "/aws/ecs/containerinsights/{ClusterName}/prometheus"
    log_stream_name: "/task/{TaskId}/fluent-bit"
    log_retention: 14
    resource_to_telemetry_conversion:
      enabled: true
    dimension_rollup_option: NoDimensionRollup
    metric_declarations:
      - dimensions:
          - [ClusterName, TaskId, TaskDefinitionFamily, name]
        metric_name_selectors:
          - "^fluentbit_input_.*"
          - "^fluentbit_output_.*"

service:
  extensions: [sigv4auth, awsproxy]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [filter/exclude_health, resourcedetection, batch]
      exporters: [otlphttp]

    metrics/fluent_bit:
      receivers: [prometheus]
      processors:
        [
          memory_limiter,
          filter/exclude_names,
          resourcedetection,
          resource,
          metricstransform,
          batch,
        ]
      exporters: [awsemf/fluent_bit]

今回はX-Rayコンソールからサンプリングレートを調整できるようにしたかったので、AWS Proxy Extensionを追加しています。以下記事で紹介されているアダプティブサンプリングをサポートしている言語なのであれば、この機能はぜひ使いたいところです。

https://dev.classmethod.jp/articles/x-ray-adaptive-sampling/

また、ALBからのヘルスチェックを受けるパスはトレースの除外をしています。

デプロイ

cdk deployでデプロイします。

デプロイする前にApplication Signalsの有効化をしていない場合は有効化をしてください。Step 1のみで大丈夫です。Step 1を実行すると裏側でAWSServiceRoleForCloudWatchApplicationSignalsというIAMロールが作成されます。

15.CloudWatch Application Signals の使用を開始.png

おおよそ10分ほどで完了します。

Container Insightsの確認

デプロイが完了したのち、動作確認をします。

まずはContainer Insightsです。

Application Signalsタブを確認すると、サービスが表示されていることが分かります。

1.Container Insights.png

サービスの名前をクリックすると、Application Signalsのサービス単位のダッシュボードを確認することができます。

2.サービス EcsNativeBlueGreenStack-EcsConstructServiceC04CE253-zeFvmoquzFeO.png

しかし、メトリクスは何も記録されていません。そもそも環境変数OTEL_RESOURCE_ATTRIBUTESにてservice.name=ecs-express-appと指定したサービス名ではありません。

Application Signalsで計装済みのサービス一覧を確認すると、ecs-express-appが確認できました。

3.ecs-express-app.png

AWS CLIで確認すると、以下のようになります。

> aws application-signals get-service \
  --start-time $(date -u -v-1H +"%Y-%m-%dT%H:%M:%SZ") \
  --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") \
  --key-attributes 'Environment=ecs:EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-ULZ9CLYI4Rn6,Name=ecs-express-app,Type=Service' \
  --region us-east-1
{
    "Service": {
        "KeyAttributes": {
            "Environment": "ecs:EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-ULZ9CLYI4Rn6",
            "Name": "ecs-express-app",
            "Type": "Service"
        },
        "AttributeMaps": [
            {
                "PlatformType": "Generic"
            },
            {
                "Telemetry.Extended": "true"
            },
            {
                "InstrumentationType": "INSTRUMENTED"
            }
        ],
        "ServiceGroups": [
            {
                "GroupName": "Related",
                "GroupValue": "ecs-express-app",
                "GroupSource": "Default",
                "GroupIdentifier": "group/source:inferred/groupType:Related/name:ecs-express-app"
            },
            {
                "GroupName": "Environment",
                "GroupValue": "ecs:EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-ULZ9CLYI4Rn6",
                "GroupSource": "Telemetry",
                "GroupIdentifier": "group/source:inferred/groupType:Environment/name:ecs:EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-ULZ9CLYI4Rn6"
            }
        ],
        "MetricReferences": [
            {
                "Namespace": "ApplicationSignals",
                "MetricType": "LATENCY",
                "Dimensions": [
                    {
                        "Name": "Environment",
                        "Value": "ecs:EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-ULZ9CLYI4Rn6"
                    },
                    {
                        "Name": "Service",
                        "Value": "ecs-express-app"
                    }
                ],
                "MetricName": "Latency"
            },
            {
                "Namespace": "ApplicationSignals",
                "MetricType": "ERROR",
                "Dimensions": [
                    {
                        "Name": "Environment",
                        "Value": "ecs:EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-ULZ9CLYI4Rn6"
                    },
                    {
                        "Name": "Service",
                        "Value": "ecs-express-app"
                    }
                ],
                "MetricName": "Error"
            },
            {
                "Namespace": "ApplicationSignals",
                "MetricType": "FAULT",
                "Dimensions": [
                    {
                        "Name": "Environment",
                        "Value": "ecs:EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-ULZ9CLYI4Rn6"
                    },
                    {
                        "Name": "Service",
                        "Value": "ecs-express-app"
                    }
                ],
                "MetricName": "Fault"
            }
        ],
        "LogGroupReferences": [
            {
                "Identifier": "EcsNativeBlueGreenStack-EcsConstructTaskDefinitionAdotAutoInstrumentationInitContainerLogGroup8060A51D-CH8Zi0HrfGhP",
                "ResourceType": "AWS::Logs::LogGroup",
                "Type": "AWS::Resource"
            },
            {
                "Identifier": "EcsNativeBlueGreenStack-EcsConstructTaskDefinitionlogRouterLogGroup27EC9B3C-vW4qwyCZXrnn",
                "ResourceType": "AWS::Logs::LogGroup",
                "Type": "AWS::Resource"
            }
        ]
    },
    "StartTime": "2026-02-03T16:00:00+09:00",
    "EndTime": "2026-02-03T18:00:01+09:00",
    "LogGroupReferences": [
        {
            "Identifier": "EcsNativeBlueGreenStack-EcsConstructTaskDefinitionAdotAutoInstrumentationInitContainerLogGroup8060A51D-CH8Zi0HrfGhP",
            "ResourceType": "AWS::Logs::LogGroup",
            "Type": "AWS::Resource"
        },
        {
            "Identifier": "EcsNativeBlueGreenStack-EcsConstructTaskDefinitionlogRouterLogGroup27EC9B3C-vW4qwyCZXrnn",
            "ResourceType": "AWS::Logs::LogGroup",
            "Type": "AWS::Resource"
        }
    ]
}

OTEL_AWS_APPLICATION_SIGNALS_ENABLEDfalseにしていることにより、Container Insightsとの統合が効いていないことが分かりますね。

Application Signalsの確認

そのままApplication Signalsの確認をします。

サービス名をクリックするとダッシュボードを確認できました。

4.サービス ecs-express-app.png

裏側では1-10秒間隔でhttp://<ALBのDNS名>/app/にアクセスしています。

グラフ上のデータポイントをクリックすると、その時のトレースを確認することが可能です。

8.相関するスパン.png

コントリビューターも確認可能です。

9.上位のコントリビューター.png

トレースIDをクリックすると、以下のようにトレースを確認可能です。PostgreSQLへ実行したクエリも確認できていますね。

10.トレース結果.png

5XXエラーが返ってきた場合は以下のように可用性が悪化します。

11.障害.png

トレースは以下のようになります。

12.障害のトレース.png

サービスオペレーションタブではパスごとのメトリクスを確認することが可能です。

6.サービスオペレーション.png

より分かりやすいように他のパスにもアクセスすると、以下のようにサービスオペレーションに表示されてきました。

19.POST :session:reset.png

依存関係タブを確認すると、PostgreSQLやRedisの存在を確認できます。GETやSET、SELECTなどオペレーションの種類ごとのメトリクスを確認できるのは嬉しいですね。

5.依存関係.png

関連メトリクスタブでは個別のメトリクスの詳細が確認可能です。今回はOTEL_AWS_APPLICATION_SIGNALS_ENABLEDfalseにしているため、全てトレースから生成されたメトリクスです。

7.関連メトリクス.png

/aws/application-signals/dataロググループのdefaultストリームを確認すると、以下のようにEMF形式でログ出力されていることが分かりました。

{
    "Telemetry.Extended": "true",
    "Service": "ecs-express-app",
    "Error": {
        "Counts": [
            1
        ],
        "Min": 0,
        "Max": 0,
        "Values": [
            0
        ],
        "Sum": 0,
        "Count": 1
    },
    "Host": "ip-10-10-8-103.ec2.internal",
    "Fault": {
        "Counts": [
            1
        ],
        "Min": 0,
        "Max": 0,
        "Values": [
            0
        ],
        "Sum": 0,
        "Count": 1
    },
    "Operation": "GET /",
    "Throttle": {
        "Counts": [
            1
        ],
        "Min": 0,
        "Max": 0,
        "Values": [
            0
        ],
        "Sum": 0,
        "Count": 1
    },
    "Latency": {
        "Counts": [
            1
        ],
        "Min": 23.567046,
        "Max": 23.567046,
        "Values": [
            24.358747455696502
        ],
        "Sum": 23.567046,
        "Count": 1
    },
    "_aws": {
        "CloudWatchMetrics": [
            {
                "Namespace": "ApplicationSignals",
                "Dimensions": [
                    [
                        "Environment",
                        "Service"
                    ],
                    [
                        "Environment",
                        "Operation",
                        "Service"
                    ]
                ],
                "Metrics": [
                    {
                        "Name": "Error"
                    },
                    {
                        "Name": "Fault"
                    },
                    {
                        "Name": "Latency",
                        "Unit": "Milliseconds"
                    },
                    {
                        "Name": "Throttle"
                    }
                ]
            }
        ],
        "Timestamp": 1770192600000
    },
    "Telemetry.SDK": "opentelemetry,0.8.0-aws,nodejs,Auto",
    "Environment": "ecs:EcsNativeBlueGreenStack-EcsConstructCluster14AE103B-s5tytpIiJ9VQ",
    "aws.log.group.names": "EcsNativeBlueGreenStack-EcsConstructTaskDefinitionAdotAutoInstrumentationInitContainerLogGroup8060A51D-dewvPgWVXovK&EcsNativeBlueGreenStack-EcsConstructTaskDefinitionlogRouterLogGroup27EC9B3C-16UVlHXjRuoO",
    "Exemplars": {
        "Throttle": [],
        "Latency": []
    },
    "PlatformType": "Generic"
}

13.CloudWatch Logs.png

ちなみに、OTEL_AWS_APPLICATION_SIGNALS_ENABLEDtrueとする場合はdefaultログストリームではなく、otel-stream-<文字列>に以下のようなログが出力されます。ECSクラスター名やタスクIDなど情報量がかなり異なることが分かります。

{
    "ECS.Cluster": "MainStack-EcsCluster97242B84-Ji1lbqe7KZ0p",
    "ECS.TaskDefinitionFamily": "MainStackTaskDefinitionC957DE9A",
    "ECS.TaskDefinitionRevision": "3",
    "ECS.TaskId": "49226fc8c404451d859f600a6333b039",
    "Environment": "ecs:MainStack-EcsCluster97242B84-Ji1lbqe7KZ0p",
    "Host": "ip-10-0-236-171.ec2.internal",
    "Operation": "GET /",
    "PlatformType": "AWS::ECS",
    "Service": "sample-ecs-app",
    "Telemetry.Agent": "CWAgent/1.300064.0b1337",
    "Telemetry.SDK": "opentelemetry,0.6.0-aws,nodejs,Auto",
    "Telemetry.Source": "LocalRootSpan",
    "Version": "1",
    "_aws": {
        "CloudWatchMetrics": [
            {
                "Namespace": "ApplicationSignals",
                "Dimensions": [
                    [
                        "Environment",
                        "Operation",
                        "Service"
                    ],
                    [
                        "Environment",
                        "Service"
                    ]
                ],
                "Metrics": [
                    {
                        "Name": "Fault",
                        "Unit": "",
                        "StorageResolution": 60
                    },
                    {
                        "Name": "Latency",
                        "Unit": "Milliseconds",
                        "StorageResolution": 60
                    },
                    {
                        "Name": "Error",
                        "Unit": "",
                        "StorageResolution": 60
                    }
                ]
            }
        ],
        "Timestamp": 1770187169068
    },
    "aws.log.group.names": "[\"/ecs/sample-ecs-app\"]",
    "Error": {
        "Values": [
            0
        ],
        "Counts": [
            2
        ],
        "Max": 0,
        "Min": 0,
        "Count": 2,
        "Sum": 0
    },
    "Fault": {
        "Values": [
            0
        ],
        "Counts": [
            2
        ],
        "Max": 0,
        "Min": 0,
        "Count": 2,
        "Sum": 0
    },
    "Latency": {
        "Values": [
            0.4989857922441042,
            0.43580605403437667
        ],
        "Counts": [
            1,
            1
        ],
        "Max": 0.498699,
        "Min": 0.435567,
        "Count": 2,
        "Sum": 0.934266
    }
}

メトリクスはCloudWatchメトリクスの一覧からでも確認可能です。名前空間はApplicationSignalsです。

14.ディビジョンの確認.png

ADOT CollectorでもApplication Signalsは使用できるが、CloudWatch Agentを使用する場合との比較もしよう

AWS Distro for OpenTelemetry (ADOT) Collector と ADOT SDKとでCloudWatch Application Signalsを使ってみました。

結論使用することはできます。ただし、CloudWatch Agentを用いる場合と比べると使用できない機能もあります。十分に検討をしましょう。

また、ADOT Collector内で使用するコンポーネントの安定性も把握しておきましょう。今回使用したコンポーネントごとの2026/2/4時点の安定性は以下のとおりです。

コンポーネント 安定性
otlpreceiver Stable
prometheusreceiver Beta
sigv4authextension Beta
awsproxy Beta
memorylimiterprocessor Beta
filterprocessor Alpha
resourcedetectionprocessor Beta
resourceprocessor Beta
metricstransformprocessor Beta
batchprocessor Beta
otlphttpexporter Stable
awsemfexporter Beta

この記事が誰かの助けになれば幸いです。

以上、クラウド事業本部 コンサルティング部の のんピ(@non____97)でした!

この記事をシェアする

FacebookHatena blogX

関連記事