TL;DR
Application Signals で Metabase のサービスマップを作成してみました。
Amazon CloudWatch Application Signalsとは
CloudWatch Application Signalsを使用するとアプリケーションの状態やメトリクスやトレースが自動で収集され、呼び出し量、可用性、エラーなどの指標をサービスマップなどの形で可視化することができます。
今まで似たサービスに AWS X-Ray がありましたが、SDK をアプリケーションコードに実装する必要があり、ハードルが高かったです。
Amazon CloudWatch Application Signalsを使用することでアプリケーションコードに手を加えることなく、導入することが可能です。
今回の目的
今回は ECS Fargate で動かしている Metabase に Application Signales を導入し、サービスマップを作成してみようと思います。
参考にした公式ドキュメントはこちらです。
Use a custom setup to enable Application Signals on Amazon ECS
1. アカウントでアプリケーションシグナルを有効にする
AWSアカウントで Application Signals を有効にしていない場合は、サービスを検出するために必要なアクセス許可を Application Signals に付与する必要があります。
アカウントに対して 1 回だけ行う必要があります。
- CloudWatchコンソールを開きます。 https://console.aws.amazon.com/cloudwatch/
- ナビゲーションペインで Application Signals の Service を選択します
- 「サービスの検出を開始」を選択します
- チェックを入れ、「サービスの検出を開始」を選択します。IAMロールに
AWSServiceRoleForCloudWatchApplicationSignals
が作成されます
2. IAMロールの作成
ECS用のIAMロールを作成します。
通常の権限に加えて、 CloudWatchAgentServerPolicy
を追加する必要があります。
今回は kabigon-ecs-task-role
という IAM ロールを作成します。
AmazonECSTaskExecutionRolePolicy
, AmazonSSMReadOnlyAccess
, CloudWatchAgentServerPolicy
をアタッチしました。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
3. CloudWatch エージェント構成の準備
SSM パラメーターストアに CloudWatchエージェントの設定をアップロードします。
/tmp/ecs-cwagent.json
という名前でローカル環境に以下の内容のファイルを作成します。
{
"traces": {
"traces_collected": {
"application_signals": {}
}
},
"logs": {
"metrics_collected": {
"application_signals": {}
}
}
}
以下のコマンドを実行し、パラメーターストアに ecs-cwagent
という名前でアップロードします。
aws ssm put-parameter \
--name "ecs-cwagent" \
--type "String" \
--value "`cat /tmp/ecs-cwagent.json`" \
--region "ap-northeast-1"
4. ECSクラスターの作成
Metabaseらのコンテナを動かすECSクラスターを作成します。
kabigon-metabase-cluster
という名前でクラスターを作成しました。
5. タスク定義の作成
kabigon-metabase-task-definition
という名前でタスク定義を作成します。
タスクロール、タスク実行ロールは先ほど作成した kabigon-exs-task-role
をアタッチします。
細かい設定は画像およびJSONを参考にしてください。
5.1 Metabase用のコンテナ
Metabase用のコンテナを作成します。
コンテナ名
metabase
イメージ
metabase/metabase
環境変数
name | value |
---|---|
JAVA_TOOL_OPTIONS | -javaagent:/otel-auto-instrumentation/javaagent.jar |
OTEL_AWS_APPLICATION_SIGNALS_ENABLED | true |
OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT | http://localhost:4316/v1/metrics |
OTEL_EXPORTER_OTLP_PROTOCOL | http/protobuf |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT | none |
OTEL_LOGS_EXPORTER | none |
OTEL_METRICS_EXPORTER | none |
OTEL_PROPAGATORS | tracecontext,baggage,b3,xray |
OTEL_RESOURCE_ATTRIBUTES | service.name=metabase |
OTEL_TRACES_SAMPLER | xray |
タスク定義はJSONから編集してください
JAVA_TOOL_OPTIONS
の値の先頭にはスペースを入れる必要があります。
JSONではなくGUIでタスク定義を編集すると先頭のスペースが削除されるので、JSONから編集するのがおすすめです。
5.2 ECS CloudWatch Agentのコンテナ
ECS CloudWatch Agentのコンテナを作成します。
コンテナ名
ecs-cwagent
イメージ
public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest
環境変数
name | value(ParameterStoreより) |
---|---|
CW_CONFIG_CONTENT | ecs-cwagent |
5.3 init用のコンテナ
init用のコンテナを作成します。
javaagent.jar
を共通のボリュームにコピーしています。
コンテナ名
init
イメージ
public.ecr.aws/aws-observability/adot-autoinstrumentation-java:v1.32.3
コマンド
cp /javaagent.jar /otel-auto-instrumentation/javaagent.jar
5.4 ボリュームの設定
opentelemetry-auto-instrumentation
というボリュームを作成し、metabaseコンテナとinitコンテナでマウントします。
5.5 全体の設定
全体の設定はこちらです。
JSON
{
"taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:{AWS_ACCOUNT_ID}:task-definition/kabigon-metabase-task-definition:13",
"containerDefinitions": [
{
"name": "metabase",
"image": "metabase/metabase",
"cpu": 0,
"portMappings": [
{
"name": "3000",
"containerPort": 3000,
"hostPort": 3000,
"protocol": "tcp",
"appProtocol": "http"
}
],
"essential": true,
"environment": [
{
"name": "OTEL_EXPORTER_OTLP_PROTOCOL",
"value": "http/protobuf"
},
{
"name": "OTEL_AWS_APPLICATION_SIGNALS_ENABLED",
"value": "true"
},
{
"name": "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT",
"value": "http://localhost:4316/v1/metrics"
},
{
"name": "OTEL_RESOURCE_ATTRIBUTES",
"value": "service.name=metabase"
},
{
"name": "OTEL_METRICS_EXPORTER",
"value": "none"
},
{
"name": "JAVA_TOOL_OPTIONS",
"value": " -javaagent:/otel-auto-instrumentation/javaagent.jar"
},
{
"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"
}
],
"mountPoints": [
{
"sourceVolume": "opentelemetry-auto-instrumentation",
"containerPath": "/otel-auto-instrumentation",
"readOnly": false
}
],
"volumesFrom": [],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/kabigon-metabase-task-definition",
"mode": "non-blocking",
"awslogs-create-group": "true",
"max-buffer-size": "25m",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
},
"systemControls": []
},
{
"name": "ecs-cwagent",
"image": "public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest",
"cpu": 0,
"portMappings": [],
"essential": true,
"environment": [],
"mountPoints": [],
"volumesFrom": [],
"secrets": [
{
"name": "CW_CONFIG_CONTENT",
"valueFrom": "ecs-cwagent"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/kabigon-metabase-task-definition",
"mode": "non-blocking",
"awslogs-create-group": "true",
"max-buffer-size": "25m",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
},
"systemControls": []
},
{
"name": "init",
"image": "public.ecr.aws/aws-observability/adot-autoinstrumentation-java:v1.32.3",
"cpu": 0,
"portMappings": [],
"essential": false,
"command": [
"cp",
"/javaagent.jar",
"/otel-auto-instrumentation/javaagent.jar"
],
"environment": [],
"mountPoints": [
{
"sourceVolume": "opentelemetry-auto-instrumentation",
"containerPath": "/otel-auto-instrumentation",
"readOnly": false
}
],
"volumesFrom": [],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/kabigon-metabase-task-definition",
"mode": "non-blocking",
"awslogs-create-group": "true",
"max-buffer-size": "25m",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
},
"systemControls": []
}
],
"family": "kabigon-metabase-task-definition",
"taskRoleArn": "arn:aws:iam::{AWS_ACCOUNT_ID}:role/kabigon-ecs-task-role",
"executionRoleArn": "arn:aws:iam::{AWS_ACCOUNT_ID}:role/kabigon-ecs-task-role",
"networkMode": "awsvpc",
"revision": 13,
"volumes": [
{
"name": "opentelemetry-auto-instrumentation",
"host": {}
}
],
"status": "ACTIVE",
"requiresAttributes": [
{
"name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
},
{
"name": "ecs.capability.execution-role-awslogs"
},
{
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
},
{
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.28"
},
{
"name": "com.amazonaws.ecs.capability.task-iam-role"
},
{
"name": "ecs.capability.secrets.ssm.environment-variables"
},
{
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
},
{
"name": "ecs.capability.task-eni"
},
{
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.29"
}
],
"placementConstraints": [],
"compatibilities": [
"EC2",
"FARGATE"
],
"requiresCompatibilities": [
"FARGATE"
],
"cpu": "1024",
"memory": "3072",
"runtimePlatform": {
"cpuArchitecture": "X86_64",
"operatingSystemFamily": "LINUX"
},
"registeredAt": "2024-08-26T11:00:14.580Z",
"registeredBy": "arn:aws:iam::{AWS_ACCOUNT_ID}:user/kabigon.ono",
"tags": []
}
6. タスクの起動
kabigon-metabase-task-definition
からタスクを作成し、metabase
, ecs-cwagent
, init
の各コンテナが起動することを確認します。
init
コンテナは起動後すぐに終了します。
7. サービスマップの確認
しばらくするとサービスマップを確認することができます。
まとめ
Application Signalsで簡単にサービスマップを作成することができました。
今後はボトルネックの特定などがかなり容易に行うことができそうです。
まだまだ新しいサービスで知らないことばかりなので、積極的に使っていこうと思います。