ADOT を使用して Next.js を計測する

kubell Advent Calendar 2024 の投稿です。 [^4]

https://qiita.com/advent-calendar/2024/kubell

この投稿では、AWS Distro for OpenTelemetry (ADOT) [^2] を用いた Next.js の計測について書きます。

![cloudwatch-trace-a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569971/fa9359ec-db24-5406-fa30-c47d84547373.jpeg)


```mermaid
flowchart LR
    subgraph AWS Amplify
        CF["CloudFront
        (CDN)"]
        Lambda["Lambda
        (Render)"]
    end

    subgraph ECS Cluster
        LambdaCollector["ADOT Collector
        for Lambda"]
    end

    subgraph ECS Cluster
        subgraph Backend Container
            API[Backend API]
        end
        
        subgraph ADOT Sidecar
            APICollector["ADOT Collector
            for Backend"]
        end
    end

    subgraph AWS Services
        XRay[AWS X-Ray]
        CloudWatch[CloudWatch]
    end

    Client[Client] --> CF
    CF --> Lambda
    Lambda -- Trace Data --> LambdaCollector
    API -- Trace Data --> APICollector
    LambdaCollector -- Traces --> XRay
    APICollector -- Traces --> XRay
    LambdaCollector -- Metrics --> CloudWatch
    APICollector -- Metrics --> CloudWatch
```

## TL;DR

- ADOT を使用することで、Next.js アプリケーションにレーシングを実装。特に AWS のサービスを使用している環境では、X-Ray との連携も容易で、運用面でも優れる
- Amplify Gen 2 (Amplify) でホスティングしている Next.js アプリケーションに AWS Distro for OpenTelemetry (ADOT) を導入
- Amazon ECS (ECS) にデプロイした ADOT Collector で X-Ray [^3] トレーシングを実現
- バックエンドの計測では Collector を Sidecar として起動可能
- ⚠️ Amplify Managed の制約により、フロントエンドの Tracing とログの紐付けには制限あり

## はじめに

本記事では、Amplify でホスティングされている Next.js アプリケーションに、AWS Distro for OpenTelemetry（ADOT）を使用してトレーシング機能を実装する方法について説明します。ADOT は、AWS が提供する OpenTelemetry ディストリビューションで、ECS にデプロイできます。

## 前提条件と制約事項

- フロントエンドは Amplify Managed で運用されています。
  - Amplify の CDN と Render (Lambda) は管理された状態で提供されています。
  - Amplify の Lambda の設定は 2024 年 12 月 1 日時点では変更できません。
  - この制約により、Tracing とログを紐づけて観察することが困難です。
これらの制約は、特にフロントエンドのパフォーマンス監視やデバッグの際に考慮が必要です。

## アーキテクチャ概要

- フロントエンド: Amplify ホスティング
- Server-side Rendering: Lambda
- トレーシング: ADOT
- バックエンド デプロイ環境: ECS

## ADOT Collector の設定

### ECS でのヘルスチェック設定

ECS でヘルスチェックを行うために、以下のような `config.yaml` の設定が必要です。

```yaml
extensions:
  health_check:
    endpoint: 0.0.0.0:13133
  pprof:
    endpoint: 0.0.0.0:1777

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:

exporters:
  debug:
    verbosity: detailed
  awsxray:
    region: 'ap-northeast-1'
  awsemf:
    region: 'ap-northeast-1'

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [awsxray]
    metrics:
      receivers: [otlp]
      exporters: [awsemf]
  extensions: [pprof, health_check]
  telemetry:
    logs:
      level: debug
```

### ローカル開発環境での設定

ローカルで ADOT を起動する場合、AWS の認証情報が必要です。以下の Docker コマンドで実行できます：

```bash
docker run --rm -p 13133:13133 -p 4317:4317 -p 4318:4318 -p 55680:55680 -p 8889:8888 \
  -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \
  -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \
  -e "AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN}" \
  -e AWS_REGION=ap-northeast-1 \
  --name awscollector public.ecr.aws/aws-observability/aws-otel-collector:latest
```

### 必要な IAM Policy

以下の IAM Policy は、ECS タスク定義のタスク実行ロールに必要です。この Policy により、ADOT Collector が X-Ray や CloudWatch と連携できるようになります：

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:PutLogEvents",
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:DescribeLogStreams",
                "logs:DescribeLogGroups",
                "ssm:GetParameters",
                "xray:PutTraceSegments",
                "xray:PutTelemetryRecords",
                "xray:GetSamplingRules",
                "xray:GetSamplingTargets",
                "xray:GetSamplingStatisticSummaries"
            ],
            "Resource": "*"
        }
    ]
}
```

- ECS タスク定義でこの Policy をタスク実行ロールにアタッチすることで、ADOT Collector は必要な権限を持って AWS サービスと連携できるようになります。

## Next.js アプリケーションでのトレーシング実装

### 基本実装

Next.js の公式ドキュメント [^1] をベースに、実装できます：

```typescript
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'

const sdk = new NodeSDK({
  resource: new Resource({
    [ATTR_SERVICE_NAME]: 'next-app',
  }),
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
})
sdk.start()
```

このコードは以下のことを行います：
- OTLP TraceExporter を使用してトレースデータを HTTP で送信。
- リソース名（サービス名）を `next-app` として設定。
- SimpleSpanProcessor を使用してトレースの処理を行う。

### カスタマイズのポイント

- Vercel 以外でホスティングする場合は、環境に応じたカスタマイズが必要です。
- Full-Stack TypeScript プロジェクトの場合、トレーシングの実装からテストまでを一貫して行うことが容易です。

## ECS での運用について

- バックエンドの場合、OpenTelemetry Collector を Sidecar として起動できます。
- ECS では、ポートマッピングは 1 つで十分です（ `localhost` でヘルスチェックが可能なため）

---

記事は以上です。

この投稿をみていただい方はいいねをお願いします。

それでは次回のアドカレでお会いしましょう👋


[^1]: https://nextjs.org/docs/pages/building-your-application/optimizing/open-telemetry

[^2]: https://aws-otel.github.io/

[^3]: https://docs.aws.amazon.com/xray/latest/devguide/aws-xray.html

[^4]: 2024年7月1日、Chatwork株式会社は株式会社kubellへと社名変更しました。