ECS Fargate上のアプリケーションへのDatadog APM導入
はじめに
こんにちは、株式会社スマートラウンドVP of Reliabilityの@shonansurvivorsです。SREなどを担当しています。
弊社ではECS Fargate上で動かしているJavaアプリケーションにDatadog APMを導入しています。本記事では、どのような設定でAPMを動かしているかを解説します。
具体的には以下の内容を扱います(説明する順序とは一致しません)。
- Datadog Agentコンテナについて
- GitHubとの連携
- Nginxのメトリクス取得
- ECRのプルスルーキャッシュ
- Java & Jibの設定
- KotlinのCoroutine対応
JavaやKotlinに限定しない内容も含まれているため、他言語のアプリケーションでDatadog APMを使用している方にも参考になればと思います。
環境
- Java 17
- Kotlin 2.0.x
- Ktor 3.0.x
- Gradle 7.6.x
- Jib Gradle Plugin 3.4.x
全体像
ECSタスクには、Nginxコンテナ → Javaアプリケーションコンテナ の構成に加えて、Datadog Agentコンテナをサイドカーとして追加しています。
また、Javaアプリケーションコンテナ内でDatadog Java TracerをJava Agentとして起動させる必要があります。
公式ドキュメントでは、このTracerをdd-java-agent.jar
というファイル名で取り扱うよう指示していますが、Datadog Agentコンテナとは別物なので混同しないよう注意してください。
Java TracerをJavaアプリケーションのコンテナイメージに含める
ここからは具体的な手順を解説していきます。
まず、Datadogが提供するJava TracerをJavaアプリケーションのコンテナイメージに含める手順を説明します。
公式の手順は以下になります。
弊社では、Dockerfileを使用せず、JibのGradle Pluginを利用してコンテナイメージをビルドしています。
そのため、build.gradleに以下の修正を加えることで、Java Tracerをコンテナイメージに含めるようにしました。jarファイル名は、公式の手順に従って、dd-java-agent.jar
という名前になるようにしています。
plugins {
id 'com.google.cloud.tools.jib' version '3.4.0'
}
// 略
task downloadDatadogAgent {
doLast {
def file = new File("$buildDir/dd-java-agent.jar")
if (!file.exists()) {
new URL('https://dtdg.co/latest-java-tracer').withInputStream { downloadStream ->
file.withOutputStream { fileOut ->
fileOut << downloadStream
}
}
}
}
}
jib {
// 略
// 何もしないとdd-java-agent.jarがコンテナのルートに配置され、それでも一応問題は無いが、より適切な/opt/datadogに配置する
extraDirectories {
paths {
path {
from = file("$buildDir")
into = '/opt/datadog'
includes = ['dd-java-agent.jar']
}
}
}
jibDockerBuild.dependsOn downloadDatadogAgent
}
// 略
これにより、
./gradlew jibDockerBuild
を実行してコンテナイメージをビルドすると、downloadDatadogAgent
のGradleタスクが先行して実施され、/opt/datadog
にTracerであるdd-java-agent.jar
が配置されるようになります。
ECSタスク定義:Javaアプリケーションコンテナ関連
ECSタスク定義における、Javaアプリケーションコンテナ関連部分は以下です。
{
"containerDefinitions": [
{
// 略
"entryPoint": [
"java",
// 略
"-javaagent:/opt/datadog/dd-java-agent.jar",
"-Ddd.integration.kotlin_coroutine.experimental.enabled=true",
"-XX:FlightRecorderOptions=stackdepth=256",
// 略
],
"command": [
// 略
],
"environment": [
// 略
{
"name": "DD_ENV",
"value": "your-env" // dev, stg, prodなど
},
{
"name": "DD_SERVICE",
"value": "your-service-name"
},
{
"name": "DD_VERSION",
"value": "your-application-version" // GitのタグやGit Commit SHAなど
},
{
"name": "DD_LOGS_INJECTION",
"value": "true"
},
{
"name": "DD_PROFILING_ENABLED",
"value": "true"
},
{
"name": "DD_GIT_REPOSITORY_URL",
"value": "https://github.com/your-org/your-repo" // アプリケーションを管理するリポジトリ
},
{
"name": "DD_GIT_COMMIT_SHA",
"value": "git-commit-sha" // 別途`git rev-parse HEAD`を実施した結果を設定
},
// 略
],
// 略
},
// 略
],
// 略
}
Datadog Java TracerをJava Agentとして起動するよう設定する
Datadog Java TracerをJava Agentとして起動するよう設定しているのは、以下です。
"entryPoint": [
"java",
// 略
"-javaagent:/opt/datadog/dd-java-agent.jar",
"-Ddd.integration.kotlin_coroutine.experimental.enabled=true",
"-XX:FlightRecorderOptions=stackdepth=256",
// 略
],
ECSタスク定義ではコンテナのENTRYPOINTを上記のように上書きできるので、これを利用しています。
"-XX:FlightRecorderOptions=stackdepth=256
は、Datadog公式のドキュメントの記載に沿って設定しています。
Kotlin Coroutineの考慮について
-Ddd.integration.kotlin_coroutine.experimental.enabled=true
は、Kotlin Coroutineを使っている場合にスパンを正しくDatadogに送信するために必要な設定です。不要な方は外してください。
なお、Tracerの起動にあたっては、他にもオプションがいくつか用意されているようです。デフォルトはいずれもfalseなので、必要に応じて有効化してください。
DD_ENV, DD_SERVICE, DD_VERSION
"environment": [
// 略
{
"name": "DD_ENV",
"value": "your-env" // dev, stg, prodなど
},
{
"name": "DD_SERVICE",
"value": "your-service-name"
},
{
"name": "DD_VERSION",
"value": "your-application-version" // GitのタグやGit Commit SHAなど
},
DD_ENV, DD_SERVICE, DD_VERSIONを設定します。DD_VERSIONについては、弊社ではCDパイプラインにおいて、git rev-parse HEAD
を実行することでGitのコミットSHAを取得しており、この値を設定するようにしています(なお、その前段でビルドしているイメージのタグも、GitのコミットSHAを採用しています)。
DD_LOGS_INJECTION
{
"name": "DD_LOGS_INJECTION",
"value": "true"
},
DD_LOGS_INJECTIONをtrueにすると、以下のようにアプリケーションのログに、DD_ENV, DD_SERVICE, DD_VERSIONで設定した値と、トレースID、スパンIDが自動で出力されるようになります。
{
// 略
"dd.env": "dev",
"dd.service": "your-servive-name",
"dd.version": "ec5107e25344c163af9358ce1870082a8b4650f3",
"dd.trace_id": "4646081607554497598",
"dd.span_id": "7271287317381225158"
}
DD_PROFILING_ENABLED
{
"name": "DD_PROFILING_ENABLED",
"value": "true"
},
DD_PROFILING_ENABLEDをtrueにするとプロファイラーが有効化され、より詳細なパフォーマンス分析が可能となります。
Datadog APMのFrame Graphの画面でソースコードを表示する
Datadog APMのFrame Graphの画面でのソースコード表示は以下のイメージです。対象のSpanについて、Profiles > Flame Graph > 対象のFlameと追っていくと、ソースコードが表示されます。
こうした表示を行うには以下を設定する必要があります。
- DatadogのGitHubアプリを
Contents:Read Only
の権限でGitHub Organizationにインストールし、アプリケーションのリポジトリにアクセスさせる - DD_GIT_REPOSITORY_URL, DD_GIT_COMMIT_SHAを設定する
GitHubアプリのインストールについては、Datadogの画面で、Integrations > GitHubで検索して画面の指示に従ってください。
このGitHubアプリは、GitHubの監査ログやGitHub Actionsのデータを収集するなど多機能のようですが、Datadog APMのFrame Graphの画面でソースコードを表示するだけであれば、Contents:Read Only
の権限を与えるだけで充分です。
その他の権限も有効化すると、Datadog側の機能が開放されて追加の料金が発生するかもしれません(未検証)。慎重に実施してください。
DD_GIT_REPOSITORY_URL, DD_GIT_COMMIT_SHAの設定については以下を参考にしてください。
{
"name": "DD_GIT_REPOSITORY_URL",
"value": "https://github.com/your-org/your-repo" // アプリケーションを管理するリポジトリ
},
{
"name": "DD_GIT_COMMIT_SHA",
"value": "git-commit-sha" // 別途`git rev-parse HEAD`を実施した結果を設定
},
ECSタスク定義:Datadog Agentコンテナ関連
ECSタスク定義における、Datadog Agentコンテナ関連部分は以下です。
{
"containerDefinitions": [
// 略
{
"cpu": 10,
"environment": [
{
"name": "DD_APM_ENABLED",
"value": "true"
},
{
"name": "DD_DOGSTATSD_NON_LOCAL_TRAFFIC",
"value": "true"
},
{
"name": "DD_EXPVAR_PORT",
"value": "15000" // ECSタスク内で他に5000番ポートを使用しており競合回避のため
},
{
"name": "DD_SITE",
"value": "datadoghq.com" // 他、us3.datadoghq.com, us5.datadoghq.comなど
},
{
"name": "ECS_FARGATE",
"value": "true"
}
],
"essential": false,
"image": "your-aws-account-id.dkr.ecr.your-aws-region.amazonaws.com/ecr-public/datadog/agent:7",
"logConfiguration": {
// 略
},
"memory": 256,
"name": "dd-agent",
"mountPoints": [
{
"containerPath": "/etc/datadog-agent",
"readOnly": false,
"sourceVolume": "dd-agent-etc-datadog-agent"
},
{
"containerPath": "/opt/datadog-agent/run",
"readOnly": false,
"sourceVolume": "dd-agent-opt-datadog-agent-run"
},
{
"containerPath": "/var/lib/amazon",
"readOnly": false,
"sourceVolume": "dd-agent-var-lib-amazon"
},
{
"containerPath": "/var/log/amazon",
"readOnly": false,
"sourceVolume": "dd-agent-var-log-amazon"
},
{
"containerPath": "/var/run/datadog",
"readOnly": false,
"sourceVolume": "dd-agent-var-run-datadog"
}
],
"portMappings": [
{
"appProtocol": "",
"containerPort": 8125,
"hostPort": 8125,
"protocol": "udp"
},
{
"appProtocol": "",
"containerPort": 8126,
"hostPort": 8126,
"protocol": "tcp"
}
],
"readonlyRootFilesystem": true,
"restartPolicy": {
"enabled": true,
"restartAttemptPeriod": 60
},
"secrets": [
{
"name": "DD_API_KEY",
"valueFrom": "/path/to/api_key"
}
]
}
],
// 略
"volumes": [
// 略
{
"name": "dd-agent-etc-datadog-agent"
},
{
"name": "dd-agent-opt-datadog-agent-run"
},
{
"name": "dd-agent-var-lib-amazon"
},
{
"name": "dd-agent-var-log-amazon"
},
{
"name": "dd-agent-var-run-datadog"
}
]
}
Datadog Agentコンテナの各種環境変数
"environment": [
{
"name": "DD_APM_ENABLED",
"value": "true"
},
{
"name": "DD_DOGSTATSD_NON_LOCAL_TRAFFIC",
"value": "true"
},
{
"name": "DD_EXPVAR_PORT",
"value": "15000"
},
{
"name": "DD_SITE",
"value": "datadoghq.com" // 他、us3.datadoghq.com, us5.datadoghq.comなど
},
{
"name": "ECS_FARGATE",
"value": "true"
}
],
// 略
"secrets": [
{
"name": "DD_API_KEY",
"valueFrom": "/path/to/api_key"
}
]
// 略
DD_APM_ENABLEDをtrueにすることで、Datadog APMを有効化します。
DD_DOGSTATSD_NON_LOCAL_TRAFFICをtrueにすると、DogStatsDと呼ばれる仕組みを使ってカスタムメトリクスの収集が可能になります。
DD_SITEは、使用しているDatadogの「サイト」によって、設定する値が異なります。
ECS_FARGATEをtrueにすることで、ECS Fargateのメトリクスを収集します。
DD_API_KEYは、Secrets ManagerやパラメータストアのSecure Stringsに設定しておき、そこから取得するようにしてください(上の例はパラメータストアから取得)。
Datadog Agentコンテナの使用するポートの競合回避について
DD_EXPVAR_PORTを15000に設定している理由は、弊社のJavaアプリケーションは5000番ポートでNginxのプロキシをリッスンしており、それと競合しないようにするためです。こうした事情が無ければ、DD_EXPVAR_PORT自体を設定する必要はありません(デフォルトの5000番ポートが使われます)。
これ以外にもDatadog Agentコンテナが使用するポートはいくつかあります。以下を見ると使用されているポートがわかるので、Datadog Agentコンテナがうまく起動せず、ポートの競合が疑われる場合は参考にしてみてください。
Datadog Agentコンテナが停止した時にECSタスク全体を再起動させずにDatadog Agentコンテナを再起動させる
"essential": false,
// 略
"restartPolicy": {
"enabled": true,
"restartAttemptPeriod": 60
},
essentialをfalseにすることで、Datadog Agentコンテナが停止しても、これに引きづられてECSタスク全体が停止->再起動されることが無くなります。
一方で、Datadog Agentコンテナが停止したらそのままの状態を継続することなく再起動して欲しいので、restartPolicyを設定しています。
Datadog Agentコンテナを読み取り専用で起動する
読み取り専用で起動するためにreadonlyRootFilesystemをtrueにするだけでは、Datadog Agentコンテナが起動しなくなってしまうので、/etc/datadog-agent
, /opt/datadog-agent/run
, /var/run/datadog
を書き込み可能にします。
{
"containerDefinitions": [
// 略
{
// 略
"mountPoints": [
{
"containerPath": "/etc/datadog-agent",
"readOnly": false,
"sourceVolume": "dd-agent-etc-datadog-agent"
},
{
"containerPath": "/opt/datadog-agent/run",
"readOnly": false,
"sourceVolume": "dd-agent-opt-datadog-agent-run"
},
{
"containerPath": "/var/lib/amazon",
"readOnly": false,
"sourceVolume": "dd-agent-var-lib-amazon"
},
{
"containerPath": "/var/log/amazon",
"readOnly": false,
"sourceVolume": "dd-agent-var-log-amazon"
},
{
"containerPath": "/var/run/datadog",
"readOnly": false,
"sourceVolume": "dd-agent-var-run-datadog"
}
],
// 略
"readonlyRootFilesystem": true,
}
],
// 略
"volumes": [
// 略
{
"name": "dd-agent-etc-datadog-agent"
},
{
"name": "dd-agent-opt-datadog-agent-run"
},
{
"name": "dd-agent-var-lib-amazon"
},
{
"name": "dd-agent-var-log-amazon"
},
{
"name": "dd-agent-var-run-datadog"
}
]
}
/var/lib/amazon
, /var/log/amazon
は、readonlyRootFilesystemをtrueにしたコンテナでもECS Execを利用可能とするために、書き込み可能にしています。ECS Execを利用する予定が無ければ設定不要です。
ECRのプルスルーキャッシュを利用する
{
"containerDefinitions": [
// 略
{
// 略
"image": "your-aws-account-id.dkr.ecr.your-aws-region.amazonaws.com/ecr-public/datadog/agent:7",
// 略
}
],
// 略
}
上記は、あらかじめECRに以下のプルスルーキャッシュルールを設定しておくことが前提となります。
- アップストリームレジストリ: ECR Public
- キャッシュリポジトリプレフィックス: ecr-public(上記のタスク定義の例の場合。必ずしもこの名前でなくても構わない。)
Terraformで設定する場合は以下になります。
resource "aws_ecr_pull_through_cache_rule" "ecr" {
ecr_repository_prefix = "ecr-public"
upstream_registry_url = "public.ecr.aws"
}
また、ECSのタスク実行ロールに以下のIAMポリシーが必要です。
- ecr:BatchImportUpstreamImage
- ecr:CreateRepository
これにより、Datadog Agentコンテナイメージをプライベートサブネットにプルするための通信費用を抑制できます。
ECSタスク定義:Nginxコンテナ関連
{
"containerDefinitions": [
{
// 略
"dockerLabels": {
"com.datadoghq.tags.env": "your-env", // dev, stg, prodなど
"com.datadoghq.tags.service": "your-service-name",
"com.dataddghq.tags.version": "your-application-version", // GitのタグやGit Commit SHAなど
"com.datadoghq.ad.check_names": "[\"nginx\"]",
"com.datadoghq.ad.init_configs": "[{}]",
"com.datadoghq.ad.instances": "[{\"nginx_status_url\":\"http://localhost/nginx_status/\"}]"
},
// 略
},
// 略
],
// 略
}
上記のようにNginxコンテナにlabelsを設定することで、NginxのメトリクスがDatadogに収集されます。
前提条件として、あらかじめNginx側では/nginx_status
のパスへのリクエストがあった時に、stub_statusモジュールを動かすようにしてください。
server {
# 略
location /nginx_status {
stub_status;
access_log off;
allow 127.0.0.1;
deny all;
}
}
DatadogにプリセットされているNGINX - Metricsという名前のダッシュボードなどを見ることで、Nginxのメトリクスが収集できているか確認できると思います。
なお、Nginxのログを同様の方法で収集することもできます。その場合は以下を参考にしてください。
Datadog APMの費用感
ECS Fargateタスク1つあたり、Datadogでは以下の費用がかかります。
- APM Fargate Tasks: $2.90/month
- Profiled Fargate Tasks: $1.70/month
(このほかに、Ingested Spans: $0.1/GBがかかります)
EC2であれば1インスタンスあたり、APM Hosts: $31/monthがかかるようなので、これと比べるとECS FargateでDatadog APMを利用するのは非常にお財布に優しいのではないでしょうか?
なお、ECSタスクがプライベートサブネットで動いている場合、Datadog AgentコンテナからDatadogへの通信にあたり、AWS側ではNatGateway-Bytesの費用も増加するので注意してください。
おわりに
以上となります。本記事がECS FargateでDatadog APMをこれから使う/すでに使っている人の役に立てば幸いです。
また、スマートラウンドでは全職種で仲間を募集中です。本記事を読んで興味を持たれた方はぜひ以下もご覧ください。
カジュアル面談も随時受け付けています。お気軽にお申し込みください。
Discussion