2
0

Application SignalsでECSで動かしているMetabaseのサービスマップを作成する

Last updated at Posted at 2024-08-27

TL;DR

Application Signals で Metabase のサービスマップを作成してみました。

スクリーンショット 2024-08-26 21.59.54.png

Amazon CloudWatch Application Signalsとは

CloudWatch Application Signalsを使用するとアプリケーションの状態やメトリクスやトレースが自動で収集され、呼び出し量、可用性、エラーなどの指標をサービスマップなどの形で可視化することができます。
今まで似たサービスに AWS X-Ray がありましたが、SDK をアプリケーションコードに実装する必要があり、ハードルが高かったです。
Amazon CloudWatch Application Signalsを使用することでアプリケーションコードに手を加えることなく、導入することが可能です。

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 回だけ行う必要があります。

  1. CloudWatchコンソールを開きます。 https://console.aws.amazon.com/cloudwatch/
  2. ナビゲーションペインで Application Signals の Service を選択します
  3. 「サービスの検出を開始」を選択します
  4. チェックを入れ、「サービスの検出を開始」を選択します。IAMロールに AWSServiceRoleForCloudWatchApplicationSignals が作成されます

スクリーンショット 2024-08-26 15.14.21.png

スクリーンショット 2024-08-26 15.14.38.png

2. IAMロールの作成

ECS用のIAMロールを作成します。
通常の権限に加えて、 CloudWatchAgentServerPolicy を追加する必要があります。
今回は kabigon-ecs-task-role という IAM ロールを作成します。
AmazonECSTaskExecutionRolePolicy, AmazonSSMReadOnlyAccess, CloudWatchAgentServerPolicy をアタッチしました。

screencapture-us-east-1-console-aws-amazon-iam-home-2024-08-26-21_06_54.png

スクリーンショット 2024-08-26 21.07.24.png

{
    "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"

screencapture-ap-northeast-1-console-aws-amazon-systems-manager-parameters-ecs-cwagent-description-2024-08-26-21_10_13.png

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コンテナでマウントします。

スクリーンショット 2024-08-26 21.47.31.png

5.5 全体の設定

全体の設定はこちらです。

画像

screencapture-ap-northeast-1-console-aws-amazon-ecs-v2-task-definitions-kabigon-metabase-task-definition-13-create-revision-2024-08-26-21_49_02.png

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. サービスマップの確認

しばらくするとサービスマップを確認することができます。

スクリーンショット 2024-08-26 21.59.54.png

まとめ

Application Signalsで簡単にサービスマップを作成することができました。
今後はボトルネックの特定などがかなり容易に行うことができそうです。
まだまだ新しいサービスで知らないことばかりなので、積極的に使っていこうと思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0