1. はじめに
最近、Datadogを利用してMetrics、Logs、APMの可視化について学ぶ機会がありました。MetricsやLogについては世の中に様々なプラットフォームが存在することを知っていましたが、APM(アプリケーションパフォーマンス監視)領域においては、Datadog以外の選択肢をあまり把握していませんでした。
そんな折、業務で「AWSのECS Fargate上に構築されたJavaアプリケーションのパフォーマンステストを実施する」という予定がありました。「Datadogのようなモニタリングツールをなしで、AWSネイティブな構成でJVM系のメトリクスやAPM、DBプール数等のメトリクスをどの用に取得すればいいのだろうか?」と疑問に思い調べたところ、AWS Distro for OpenTelemetry (ADOT) と CloudWatch Application Signals の組み合わせに辿り着きました。
本記事では、既存のJavaアプリケーションのコードを1行も変更することなく、自動計装(Auto-Instrumentation)によってメトリクスと分散トレーシングを可視化するまでの手順と、実装時にハマったポイントをご紹介します。
技術選定の理由
今回、AWSが提供するネイティブの「AWS X-Ray SDK」ではなく、あえて「ADOT (OpenTelemetry)」を選定しました。
現在、オブザーバビリティの領域ではOpenTelemetryがデファクトスタンダードとして標準化されつつあります。今後のベンダーロックインの回避や、他プラットフォームへのポータビリティを考慮すると、OpenTelemetryベースで計装しておくのがベストプラクティスだと考えたためです。
なお、今回のインフラ環境の構築にはTerraformを利用しています。
(というよりもTerraformを勉強するためにこのネタを消化しています。)
2. 全体構成とアーキテクチャ
今回の検証用アプリケーションの全体構成は以下の通りです。ECS (Fargate) 上にデプロイしています。
-
Appコンテナ: Spring Boot (Java) アプリケーション +
aws-opentelemetry-agent.jar(Javaエージェント) -
サイドカーコンテナ: CloudWatch Agent (
public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest) - 送信先バックエンド: Amazon CloudWatch Application Signals / CloudWatch Metrics
アプリケーションコンテナ内でJavaエージェントがトレース情報やメトリクスを自動収集し、同じタスク内で稼働しているサイドカー(CloudWatch Agent)へ送信します。サイドカーはそれを受け取り、AWSの各サービスへとデータを送る役割を果たします。
3. Javaアプリケーション側の設定(コード変更なし!)
JavaとOpenTelemetryの組み合わせの最大の強みは「Auto-Instrumentation(自動計装)」です。
JVM系のメトリクス、DBプール数のみならずトレースやスパンがどのように可視化されるかを確認するため、意図的に以下の2つの外部通信を行うSpring Bootアプリを用意しました。
Dockerfileの書き方
アプリケーションのソースコード自体には一切手を加えず、Dockerfileの中でOpenTelemetryのJavaエージェントをダウンロードし、起動オプションに渡すだけで計装が完了します。
FROM public.ecr.aws/amazoncorretto/amazoncorretto:21.0.0
COPY *.jar web-mock.jar
# OpenTelemetry Java Agent をダウンロード
ADD https://github.com/aws-observability/aws-otel-java-instrumentation/releases/latest/download/aws-opentelemetry-agent.jar /opt/aws-opentelemetry-agent.jar
# JAVA_TOOL_OPTIONS を使ってエージェントをアタッチ
ENV JAVA_TOOL_OPTIONS=-javaagent:/opt/aws-opentelemetry-agent.jar
CMD ["java", "-jar", "web-mock.jar"]
4. Fargateタスク定義とサイドカーの設定 (Terraform)
次に、ECS Fargateのタスク定義です。ここで重要なのは、Appコンテナに渡す「OpenTelemetry用の環境変数」と、サイドカーである「CloudWatch Agent」の設定です。
Appコンテナの環境変数
タスク定義 (ecs.tf) にて、Appコンテナに対して以下のような環境変数を設定します。これで、Javaエージェントがどこにデータを送るべきかが決まります。
environment = [
{
name = "OTEL_EXPORTER_OTLP_PROTOCOL"
value = "http/protobuf"
},
{
name = "OTEL_AWS_APPLICATION_SIGNALS_ENABLED"
value = "true" # Application Signalsを有効化
},
{
name = "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT"
value = "http://localhost:4316/v1/metrics"
},
{
name = "OTEL_RESOURCE_ATTRIBUTES"
value = "service.name=${local.service},deployment.environment=${var.env}"
},
{
name = "OTEL_METRICS_EXPORTER"
value = "otlp"
},
{
name = "OTEL_LOGS_EXPORTER"
value = "none"
},
{
name = "OTEL_TRACES_SAMPLER"
value = "xray"
},
{
name = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"
value = "http://localhost:4316/v1/traces" # サイドカーのポートを指定
},
{
name = "OTEL_PROPAGATORS"
value = "tracecontext,baggage,b3,xray"
}
]
CloudWatch Agent (サイドカー) の設定
サイドカーとなるCloudWatch Agentの設定ファイルは、AWS Systems Manager (SSM) パラメータストア経由で渡しています。
resource "aws_ssm_parameter" "opentelemock" {
name = "${local.prefix}-ecs-cwagent"
type = "String"
value = <<EOF
{
"traces": {
"traces_collected": {
"application_signals": {}
}
},
"logs": {
"metrics_collected": {
"application_signals": {}
}
},
"metrics": {
"metrics_collected": {
"otlp": {
"grpc_endpoint": "127.0.0.1:4317",
"http_endpoint": "127.0.0.1:4318"
}
}
}
}
EOF
}
5. 【重要】ハマったポイントと解決策
ここが今回一番お伝えしたかったポイントです!公式ドキュメント通りに進めていたつもりでしたが、1点だけつまずいた箇所がありました。
【課題】
最初はApplication Signalsのトレースは画面は表示されたものの、DBコネクションプールの値等が取得できませんでした。
【解決策】
原因は、CloudWatch Agent側でメトリクスを受け付ける(リッスンする)ための設定が不足していたためでした。
上記の ssm.tf の中にも記載していますが、以下のように metrics_collected.otlp ブロックを明示的に定義してポートを開放してあげる必要がありました。
"metrics": {
"metrics_collected": {
"otlp": {
"grpc_endpoint": "127.0.0.1:4317",
"http_endpoint": "127.0.0.1:4318"
}
}
}
【注意点】
これらのJVMメトリクス等の値は、Application Signalsのコンソール画面ではなく、通常のCloudWatch Metricsの画面(カスタムメトリクス等と同じ場所)に送信されるという仕様になっています。Application Signalsの画面ばかりを探していて「データが来ていない!」と焦りましたが、CloudWatch側を見ることで無事データを確認できました。
6. 動作確認と結果
実際に環境をデプロイしてAPIにリクエストを送ってみると、以下のようにデータが可視化されました。
Application Map
Tree Map
Segments Timeline
Javaヒープ等のメトリクス(CloudWatch Signals)
DBコネクションプール数のメトリクス(CloudWatch Metrics)
Datadog並みのグラフィカルで綺羅びやかなUIや操作性ではないもののJavaのコードを1行も変えずに外部APIへのHTTPリクエストや、MySQLへのJDBCクエリの処理時間が表示できるのは嬉しいなと思いました。
ボトルネックが視覚的にすぐわかるため、パフォーマンステスト時の分析が非常に捗るような気がします。
DatadogではProfile(うろ覚え)という機能でメソッド単位の処理時間等も計測できた気がしましたが、ADOTで同等なことが実現できるかは今後調査してみたいと思います。
7. まとめ
今回、AWS Distro for OpenTelemetry と Application Signals を使ってJavaアプリのAPM環境を構築してみました。
- 良かった点: アプリケーションコードを一切変更せず、Dockerfileとタスク定義の追加設定だけで可視化や詳細なメトリクス取得が実現できました。また、OpenTelemetryの標準仕様に沿っているため安心感があります。
- 所感: CloudWatch Agentの設定周りで少しハマりましたが、一度仕組みを理解してしまえば導入ハードルはそこまで高くないような気がしました。
最後までお読みいただきありがとうございました!




