AWS Distro for OpenTelemetry (ADOT) Collector と ADOT SDKとでCloudWatch Application Signalsを使ってみた
ADOT Collectorのサイドカー構成でCloudWatch Application Signalsを使用したい
こんにちは、のんピ(@non____97)です。
皆さんはADOT Collectorのサイドカー構成でCloudWatch Application Signals(以降、Application Signals)を使用したいなと思ったことはありますか? 私はあります。
ECSにおいてApplication Signalsを使用する際にはCloudWatch Agentのサイドカーコンテナを用意することが多いです。
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 の有効化」を参照してください。
私は以下記事でECSタスク内で動作しているFluent BitのPrometheus形式のメトリクスをスクレイピングするために、ADOT Collectorのサイドカー構成を試しました。
既にADOT Collectorのサイドカーコンテナがある状況において、Application Signalsを使いたいからといって簡単にCloudWatch Agentのサイドカーコンテナを追加したくはありません。
先ほどのAWS公式ドキュメントにはApplication SignalsはOpenTelemetry SDK と OpenTelemetry Collectorを使用するパターンでも対応できると記載されています。
OpenTelemetry SDK と Collector を使用する
この設定は、次のユースケースに使用できます。
- OpenTelemetry SDK を使用してアプリケーションまたは計画を計測し、現在は OpenTelemetry コレクターを使用しています。
- 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 CDKで管理しています。コードは以下GitHubリポジトリに保存しています。
登場人物が多くなってきたので、テレメトリ周りのやりとりのみを抽出した図も記載しておきます。

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クラスターへ遷移できる
- ECS上で動作しているのであれば、ECSクラスター名が判断できる
- Application Signalsのサービスでホストしている環境のイベント一覧を確認できる
- オペレーショナル監査として、サービスクォータ監査などの情報を確認できる
後述するとおり、ADOT CollectorではOTEL_AWS_APPLICATION_SIGNALS_ENABLEDをtrueにしても動作しません。統合されている様子を比較するためにこちらの記事のようにサイドカーにCloudWatch Agentを使用する形において、OTEL_AWS_APPLICATION_SIGNALS_ENABLEDをtrueにする場合とfalseにする場合とを見比べるとtrueの方はホストされているECSクラスターの情報が表示され、falseの場合はGenericとなっていることが分かります。

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_ENABLEDがtrueの場合は以下のように変更イベントを確認できます。

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

個人的には便利は便利であるものの、運用上ないとどうしようもならないというものではないかなという感覚です。
「CloudWatch Logs ですぐにログ記録」については、トレースとログの関連付けを指しているものと理解しています。今回はFluent BitでエラーログのみCloudWatch Logsに出力するようにしているので効力は発揮するものではないと考えています。全てのアクセスのログ記録したいのであれば良さそうですね。
「常にトラフィックの 100% に関するメトリクスを取得する」については、サンプリングされてX-Rayへ送信されたトレース分から生成するメトリクスで運用上十分なのであれば良いでしょう。
ADOT SDK + CloudWatch AgentのようにX-Rayに送信するトレースは絞りつつも、トラフィックの100%分のメトリクスを生成したい場合はAWS Application Signals ProcessorなるProcessorを使いところですが、ADOT Collector内には組み込まれていません。
Issueを確認したところ、Closed as not plannedでクローズされてしまっているので、継続的にメンテナンスされるかどうかはなんとも言えません。
ちなみに、Application Signals によって収集されるメトリクスは以下にまとまっています。
ランタイムメトリクスを取得したい場合は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コンテナとして起動し、マウントしたボリュームに自動計装エージェントをコピーします。
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を使用するように各種環境変数を設定します。
// 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_ENABLEDをfalseとするのは何とも不思議な感覚ですがCloudWatch Agentのサイドカーコンテナが存在しない場合は、基本的にこの形になります。OTEL_AWS_APPLICATION_SIGNALS_ENABLEDをtrueにして、動作できるのは現状Application Signalsのメトリクスの受け口があるCloudWatch Agentを使用する場合か、カスタムビルドしたOTel Collectorを使用する場合です。
これらがない状況において、OTEL_AWS_APPLICATION_SIGNALS_ENABLEDをtrue、OTEL_EXPORTER_OTLP_TRACES_ENDPOINTをhttp://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に送信しようとすることが分かります。
APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIGはOTEL_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_ENDPOINTをhttp://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の設定ファイルは以下のとおりです。
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を追加しています。以下記事で紹介されているアダプティブサンプリングをサポートしている言語なのであれば、この機能はぜひ使いたいところです。
また、ALBからのヘルスチェックを受けるパスはトレースの除外をしています。
デプロイ
cdk deployでデプロイします。
デプロイする前にApplication Signalsの有効化をしていない場合は有効化をしてください。Step 1のみで大丈夫です。Step 1を実行すると裏側でAWSServiceRoleForCloudWatchApplicationSignalsというIAMロールが作成されます。

おおよそ10分ほどで完了します。
Container Insightsの確認
デプロイが完了したのち、動作確認をします。
まずはContainer Insightsです。
Application Signalsタブを確認すると、サービスが表示されていることが分かります。

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

しかし、メトリクスは何も記録されていません。そもそも環境変数OTEL_RESOURCE_ATTRIBUTESにてservice.name=ecs-express-appと指定したサービス名ではありません。
Application Signalsで計装済みのサービス一覧を確認すると、ecs-express-appが確認できました。

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_ENABLEDをfalseにしていることにより、Container Insightsとの統合が効いていないことが分かりますね。
Application Signalsの確認
そのままApplication Signalsの確認をします。
サービス名をクリックするとダッシュボードを確認できました。

裏側では1-10秒間隔でhttp://<ALBのDNS名>/app/にアクセスしています。
グラフ上のデータポイントをクリックすると、その時のトレースを確認することが可能です。

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

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

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

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

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

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

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

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

/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"
}

ちなみに、OTEL_AWS_APPLICATION_SIGNALS_ENABLEDをtrueとする場合は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です。

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)でした!








