🔖

AWS Distro for OpenTelemetry で APM に近付く

2023/11/16に公開

こんにちは。
ご機嫌いかがでしょうか。
"No human labor is no human error" が大好きな吉井 亮です。

前回は AWS Distro for OpenTelemetry をゼロから学ぶということで、CloudWatch Agent を経由してトレースを取得してみました。

今回はもう少し進んで ADOT Collector を計装してログ・トレース・メトリクスの基本3種を取得してみます。
ざっくりした構成は以下です。
アプリケーションは前回と同じく サイコロを振るサンプルアプリケーション を使います。
ECS on Fargate 上で動かし、サイドカーに ADOT Collector を走らせます。Collector で収集したテレメトリは CloudWatch へ保管します。

img

それではやっていきましょう。

ECS 用 IAM ロール作成

タスクロールとタスク実行ロールを作成します。

タスクロール

作成手順は タスク IAM ロール を参照ください。
タスクロールには以下のポリシーを追加します。

クリックして展開
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "xray:PutTraceSegments",
                "xray:PutTelemetryRecords",
                "xray:GetSamplingRules",
                "xray:GetSamplingTargets",
                "xray:GetSamplingStatisticSummaries",
                "cloudwatch:PutMetricData",
                "logs:PutLogEvents",
                "logs:DescribeLogStreams",
                "logs:DescribeLogGroups",
                "logs:CreateLogStream",
                "logs:CreateLogGroup"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

タスク実行ロール

作成手順は Amazon ECS タスク実行 IAM ロール を参照ください。
タスク実行ロールには AmazonECSTaskExecutionRolePolicy マネージドポリシーを追加します。

カスタマー管理キー (CMK) 作成

ADOT Collector の設定ファイルを SSM パラメータストアに格納しています。これは Collector の設定変更のたびに Docker イメージを作り直す手間を省くためです。
かつ、SSM パラメータストアは暗号化して予期せぬ変更を防止します。

CMK の作成手順は キーの作成 を参照ください。
キーポリシーは以下を参考に実際の ID や ARN に置き換えてください。また、マネジメントコンソールで CMK を作るとキーポリシーが比較的簡単に作成可能です。

クリックして展開
{
    "Id": "key-consolepolicy-3",
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::your_aws_account_id:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "Allow access for Key Administrators",
            "Effect": "Allow",
            "Principal": {
                "AWS": "キー管理者のIAM ユーザー/ロール ARN"
            },
            "Action": [
                "kms:Create*",
                "kms:Describe*",
                "kms:Enable*",
                "kms:List*",
                "kms:Put*",
                "kms:Update*",
                "kms:Revoke*",
                "kms:Disable*",
                "kms:Get*",
                "kms:Delete*",
                "kms:TagResource",
                "kms:UntagResource",
                "kms:ScheduleKeyDeletion",
                "kms:CancelKeyDeletion"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Allow use of the key",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::your_aws_account_id:role/your_ecsTaskExecutionRole"
            },
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:DescribeKey"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Allow attachment of persistent resources",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::your_aws_account_id:role/your_ecsTaskExecutionRole"
            },
            "Action": [
                "kms:CreateGrant",
                "kms:ListGrants",
                "kms:RevokeGrant"
            ],
            "Resource": "*",
            "Condition": {
                "Bool": {
                    "kms:GrantIsForAWSResource": "true"
                }
            }
        }
    ]
}

SSM パラメータストア

ADOT Collector 設定ファイルを格納するパラメータストアを作成します。
手順は Parameter Store の使用 を参照ください。
タイプは安全な文字列を選択肢、KMS キー IDは前の手順で作ったキーを指定します。
値は以下を参考に実際のリージョンや設定値に置き換えてください。

クリックして展開
receivers:
  otlp:
    protocols:
      grpc:
      http:

processors:
  batch:

  memory_limiter:
    limit_mib: 100
    check_interval: 5s


exporters:
  awsxray:
    region: us-west-2

  awsemf:
    region: 'us-west-2'
    dimension_rollup_option: "NoDimensionRollup"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter,batch]
      exporters: [awsxray]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter,batch]
      exporters: [awsemf]

ECS タスク実行ロールにポリシーを追加

ECS タスク実行ロールに戻りまして、KMS と SSM への権限を付与します。
前の手順で作成した ECS タスク実行ロールのインラインポリシーかカスタマー管理ポリシーを作り、以下の権限を付与します。

クリックして展開
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:GetParameters",
                "kms:Decrypt"
            ],
            "Resource": [
                "SSM パラメータストアの ARN",
                "KMS CMK の ARN"
            ]
        }
    ]
}

Docker イメージ作成

サンプルアプリケーションを ECS で動かすための Docker イメージを作成します。

ファイル用意

任意のディレクトリを作り以下のファイルを作成します。

.
├─ Dockerfile
├─ DiceApplication.java   # https://opentelemetry.io/docs/instrumentation/java/getting-started/
├─ RollController.java    # https://opentelemetry.io/docs/instrumentation/java/getting-started/
├─ build.gradle.kts
└─ logback.xml

Dockerfile

以下を参考に微修正して Docker イメージを作ります。
JAVA_TOOL_OPTIONS で自動計装エージェントとログ定義ファイルの指定をしています。

クリックして展開
Dockerfile
FROM public.ecr.aws/docker/library/gradle:jdk17 AS builder

WORKDIR /java-simple
COPY *.java .
COPY build.gradle.kts .

RUN ["gradle", "assemble"]

FROM gcr.io/distroless/java17-debian11:latest

WORKDIR /java-simple
COPY --from=builder /java-simple/build/libs/ ./build/libs/
COPY logback.xml ./logback.xml

ADD https://github.com/aws-observability/aws-otel-java-instrumentation/releases/download/v1.31.0/aws-opentelemetry-agent.jar ./aws-opentelemetry-agent.jar
ENV JAVA_TOOL_OPTIONS "-javaagent:/java-simple/aws-opentelemetry-agent.jar -Dlogging.config=/java-simple/logback.xml"

ENV OTEL_RESOURCE_ATTRIBUTES "service.name=dice-server"
ENV OTEL_IMR_EXPORT_INTERVAL "60000"
ENV OTEL_EXPORTER_OTLP_ENDPOINT "http://127.0.0.1:4317"

CMD ["/java-simple/build/libs/java-simple.jar"]

build.gradle.kts

dependencies で自動計装エージェントと Logback のライブラリを指定します。

自動計装エージェントの最新バージョンは releases を調べてください。
Logback の最新バージョンは latest release を調べてください。

クリックして展開
build.gradle.kts
plugins {
  id("java")
  id("org.springframework.boot") version "3.0.6"
  id("io.spring.dependency-management") version "1.1.0"
}

sourceSets {
  main {
    java.setSrcDirs(setOf("."))
  }
}

repositories {
  mavenCentral()
}

dependencies {
  implementation("org.springframework.boot:spring-boot-starter-web")
  implementation("software.amazon.opentelemetry:aws-opentelemetry-agent:1.31.0")
  runtimeOnly("io.opentelemetry.instrumentation:opentelemetry-logback-mdc-1.0:1.31.0-alpha")
}

logback.xml

ログファイルに AWS X-Ray 形式のトレース ID を埋め込みます。こうすることでトレースとログの紐づけが CloudWatch 上で可能になりトラブルシューティングに役立ちます。

クリックして展開
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} trace_id=%X{AWS-XRAY-TRACE-ID} span_id=%X{span_id} trace_flags=%X{trace_flags} %msg%n</pattern>
    </encoder>
  </appender>

  <!-- Just wrap your logging appender, for example ConsoleAppender, with OpenTelemetryAppender -->
  <appender name="OTEL" class="io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender">
    <appender-ref ref="CONSOLE"/>
  </appender>

  <!-- Use the wrapped "OTEL" appender instead of the original "CONSOLE" one -->
  <root level="DEBUG">
    <appender-ref ref="OTEL"/>
  </root>

</configuration>

ビルド

Docker イメージを格納するためのリポジトリを作っておきます。
特に決まった場所はありませんが、AWS には ECR がありますので今回は ECR を使います。
リポジトリの作り方は Amazon ECR プライベートリポジトリ を参照ください。

ビルドして ECR リポジトリにプッシュします。

docker build . -t your_aws_account_id.dkr.ecr.us-west-2.amazonaws.com/imagename:tagname
docker push your_aws_account_id.dkr.ecr.us-west-2.amazonaws.com/imagename:tagname

NLB 作成

ALB でも NLB でもどちらでも大丈夫です。
今回は最近セキュリティグループをサポートした NLB を使います。
ターゲットグループのターゲットタイプは「ip」を選択しておいてください。ECS タスクをターゲットに含めるためです。

Network Load Balancers を参照して NLB を作成します。

ECS クラスターとサービス作成

Docker イメージができたら ECS クラスターとサービスを作成します。
作成手順は AWS Fargate の Linux コンテナによるコンソールの使用開始 を参照ください。

Container Insights を有効にします。

タスクロールとタスク実行ロールは前の手順で作成したものを使います。

サイドカーとして ADOT Collector を利用します。ECS タスク定義を以下に示します。自身の環境に合わせて微修正して利用してください。
secrets の項目は前の手順作成した SSM パラメータストア名を指定します。

クリックして展開
    "containerDefinitions": [
        {
            "name": "dice",
            "image": "your_aws_account_id.dkr.ecr.us-west-2.amazonaws.com/imagename:tagname",
            "cpu": 0,
            "portMappings": [
                {
                    "name": "dice-8080-tcp",
                    "containerPort": 8080,
                    "hostPort": 8080,
                    "protocol": "tcp",
                    "appProtocol": "http"
                }
            ],
            "essential": true,
            "environment": [],
            "mountPoints": [],
            "volumesFrom": [],
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-create-group": "true",
                    "awslogs-group": "/ecs/your_apps",
                    "awslogs-region": "us-west-2",
                    "awslogs-stream-prefix": "ecs"
                }
            }
        },
        {
            "name": "aws-otel-collector",
            "image": "public.ecr.aws/aws-observability/aws-otel-collector:v0.35.0",
            "cpu": 0,
            "portMappings": [],
            "essential": true,
            "environment": [],
            "mountPoints": [],
            "volumesFrom": [],
            "secrets": [
                {
                    "name": "AOT_CONFIG_CONTENT",
                    "valueFrom": "otel-collector-config"
                }
            ],
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-create-group": "true",
                    "awslogs-group": "/ecs/ecs-aws-otel-sidecar-collector",
                    "awslogs-region": "us-west-2",
                    "awslogs-stream-prefix": "ecs"
                }
            }
        }
    ],

CloudWatch の観え方

ECS タスク(コンテナ) が動いたらいよいよ CloudWatch を見てみます。どれだけの情報が取れているのか楽しみです。
前回はトレースのみだったので違いを期待します。

前回と同じく curl コマンドを連打します。

while true 
do
  curl http://nlb-url/rolldice
  sleep 10
done

トレース

トレース取れています。
そして、今回はログも表示されています。やりました。
もし異常や遅延が発生した場合、トレースとログが紐付いて表示されることは有用です。今までの苦労は何であったのかと思うほどです。

img

CloudWatch Logs に出力されているログには X-Ray トレース ID (trace_id) が埋め込まれています。CloudWatch Logs Insights でこれを検索してトレース画面に表示しています。

00:50:13.242 trace_id=1-655415c5-f8ce3ffe767daf7d6ef94d72@ed4719643c9748b6 span_id=ed4719643c9748b6 trace_flags=01 GET "/rolldice", parameters={}

メトリクス

メトリクスを見てみます。
CloudWatch 画面で dice-server というカスタム名前空間が増えていることが確認できます。コンテナ起動時に指定している環境変数 ENV OTEL_RESOURCE_ATTRIBUTES "service.name=dice-server" でしているサーバー名称です。
メトリクスも追加で取得できています。

img

img



ここで取得したいメトリクスはアプリケーションのメトリクスです。サンプルは Java アプリケーションなので主に JVM の情報が取得されていることが確認できます。
自動計装でどのような情報が取得できるかは以下を参照ください。
Supported libraries, frameworks, application servers, and JVMs

まとめ

ADOT Collector を利用してアプリケーションのトレース、メトリクスを取得しました。
商用の Observability SaaS に比較すると物足りないかもしれませんが、AWS 内で完結する環境でも APM は実現可能です。

AWS 上のアプリケーションで基本3種を取得するには、このような構成が基本になると考えます。

  • メトリクス
    • インフラメトリクス
      • CloudWatch
    • アプリケーションメトリクス
      • ADOT Collector
  • ログ
    • CloudWatch Logs に転送、保管
    • X-Ray トレース ID をログに埋め込む
  • トレース
    • ADOT Collector

参考

AWS Distro for OpenTelemetry Documentation
AWS X-Ray SDK for Java
A language-specific implementation of OpenTelemetry in Java.
Logger MDC auto-instrumentation
GitHub aws-observability
AWS Observability Best Practices

Discussion