
Kong + ECS Fargate + OpenTelemetry で分散トレーシングを試してみる
ゲームソリューション部の えがわ です。
本ブログはKong Advent Calendar 2025の19日目のブログとなります。
今回は、Kong GatewayのOpenTelemetryプラグインを使用した分散トレーシングを試してみます。
KongはTerraformでECS Fargateにデプロイできるようにし、New Relicでトレースを確認できるところまで実装します。
やりたいこと
マイクロサービスアーキテクチャでは、1つのリクエストが複数のサービスを経由します。
問題が発生した際、「どのサービスで」「どのくらい時間がかかったか」を把握することは困難です。
分散トレーシングを導入することで、リクエストの流れを一意のトレースIDで追跡し、サービス間の関係性とレイテンシを可視化できます。
今回は以下の構成で分散トレーシングを構築します。
- Kong Gateway: API Gatewayでトレースを開始
- AWS Fargate: Kong + バックエンドAPIをコンテナで実行
- OpenTelemetry Collector: 各サービスからトレースを収集
- New Relic: トレースを可視化・分析
アーキテクチャ
今回の構成図はこちらです。
分散トレーシングを実現するポイント
- Kong OpenTelemetryプラグイン: API Gatewayでトレースを開始し、W3C Trace Context(traceparent)を下流に伝播
- サイドカーパターン: 各ECSタスクにOTEL Collectorを配置、アプリは
localhostにトレースを送信するだけ - アプリケーション計装: Node.jsアプリもOpenTelemetry SDKで計装し、トレースを継続
- Kong Konnect(SaaS): Control Plane(CP)はKong Konnectで管理、Data Plane(DP)のみAWSにデプロイ
環境
- Kong Konnect(Control Plane)
- Kong Gateway 3.12(Data Plane)
- AWS ECS Fargate
- OpenTelemetry Collector
- New Relic(トレース収集先)
- Node.js
- Terraform
本記事で使用するコードは以下リポジトリに保存しています。
ディレクトリ構成
kong-fargate-otel-newrelic/
├── terraform/ # インフラ構成(Terraform)
├── app/
│ ├── dummy-api/ # フロントエンドAPI(Kong経由でアクセス)
│ └── downstream-api/ # バックエンドAPI(dummy-apiから呼び出し)
├── otel-collector/ # OTEL Collector設定
└── docs/ # ドキュメント
トレース連携の流れ
分散トレーシングがどのように動作するか、流れを確認しましょう。
1. Client → ALB → Kong
Kong が traceparent を生成: 00-{trace_id}-{span_id}-01
2. Kong → dummy-api
traceparent ヘッダーを付与してリクエスト転送
3. dummy-api → downstream-api
同じ trace_id を引き継いで下流に伝播
4. 各サービス → OTEL Collector → New Relic
同一 trace_id のスパンが New Relic で結合・可視化
W3C Trace Contextのtraceparentヘッダーが各サービス間で伝播され、New Relicで一連のリクエストとして可視化されます。
W3C Trace Context の伝播
Kongがtraceparentヘッダーを生成・付与し、下流サービスがこのヘッダーを受け取ることで、同一トレースとして認識されます。
traceparent: 00-{trace_id}-{span_id}-01
│ │ │ │
│ │ │ └── トレースフラグ
│ │ └── 親スパンID(16文字)
│ └── トレースID(32文字)
└── バージョン
Node.js アプリケーションの計装
OpenTelemetry SDK の初期化
アプリケーションの起動前にトレーシングを初期化する必要があります。
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici';
import { Resource } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
// Configure OTLP exporter
const traceExporter = new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
? `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`
: 'http://localhost:4318/v1/traces',
});
// Create the SDK with automatic instrumentation
const sdk = new NodeSDK({
resource: new Resource({
[ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'dummy-api',
[ATTR_SERVICE_VERSION]: '1.0.0',
}),
traceExporter,
instrumentations: [
new HttpInstrumentation({
requestHook: (span, request) => {
span.setAttribute('http.request.header.traceparent',
request.headers?.traceparent || 'none');
},
}),
new ExpressInstrumentation(),
new UndiciInstrumentation(), // fetch() API用(Node.js 18+)
],
});
// Start the SDK
sdk.start();
// Graceful shutdown
process.on('SIGTERM', () => {
sdk.shutdown()
.then(() => console.log('Tracing terminated'))
.catch((error) => console.error('Error terminating tracing', error))
.finally(() => process.exit(0));
});
console.log('OpenTelemetry tracing initialized');
traceparent ヘッダーの自動伝播
instrumentation-undiciがfetch()呼び出し時に自動でtraceparent`ヘッダーを伝播します。
これはOpenTelemetry SDKの自動計装機能により、アプリケーションコードで明示的にヘッダーを設定することなく、トレースコンテキストが下流に引き継がれます。
// Downstream API URL (Service Discovery or environment variable)
const DOWNSTREAM_API_URL = process.env.DOWNSTREAM_API_URL
|| 'http://downstream-api.kong-otel-dev.local:3001';
// Chain endpoint - calls downstream-api to demonstrate distributed tracing
app.get('/api/chain', async (req, res) => {
const tracer = trace.getTracer('dummy-api');
tracer.startActiveSpan('chain-request', async (span) => {
try {
span.setAttribute('downstream.url', DOWNSTREAM_API_URL);
// traceparentは instrumentation-undici により自動で付与される
const downstreamResponse = await fetch(`${DOWNSTREAM_API_URL}/api/data`);
const downstreamData = await downstreamResponse.json();
span.setAttribute('downstream.status', 200);
span.setAttribute('downstream.success', true);
span.end();
res.json({
message: 'Chain request completed successfully',
localData: { service: 'dummy-api', userCount: users.length },
downstreamData: downstreamData,
});
} catch (error) {
span.recordException(error);
span.end();
res.status(500).json({ error: 'Failed to call downstream service' });
}
});
});
OTEL Collector の設定
New Relic 向け設定
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 1s
send_batch_size: 50
exporters:
otlphttp:
endpoint: https://otlp.nr-data.net
headers:
api-key: ${NEW_RELIC_LICENSE_KEY}
debug:
verbosity: detailed
extensions:
health_check:
endpoint: 0.0.0.0:13133
service:
extensions: [health_check]
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp, debug]
telemetry:
logs:
level: debug
| 項目 | 説明 |
|---|---|
| receivers.otlp | アプリケーションからトレースを受信(ポート4318) |
| processors.batch | バッチ処理でネットワーク効率化 |
| exporters.otlphttp | New RelicへOTLP形式で送信 |
| exporters.debug | デバッグ用にログ出力 |
| extensions.health_check | ヘルスチェック用エンドポイント(ポート13133) |
※New Relic UIのAPI KeysセクションでIngest - Licenseを選択し、表示されたキーをコピーしてください。
ECS タスク定義(Terraform)
Kong Data Plane タスク定義
resource "aws_ecs_task_definition" "kong" {
family = "${local.name_prefix}-kong"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = var.kong_task_cpu
memory = var.kong_task_memory
execution_role_arn = aws_iam_role.ecs_task_execution.arn
task_role_arn = aws_iam_role.ecs_task.arn
container_definitions = jsonencode([
{
name = "kong"
image = "kong/kong-gateway:3.12"
essential = true
portMappings = [
{ containerPort = 8000, hostPort = 8000, protocol = "tcp" },
{ containerPort = 8100, hostPort = 8100, protocol = "tcp" }
]
environment = [
{ name = "KONG_ROLE", value = "data_plane" },
{ name = "KONG_DATABASE", value = "off" },
{ name = "KONG_CLUSTER_MTLS", value = "pki" },
{ name = "KONG_CLUSTER_CONTROL_PLANE", value = var.kong_cluster_endpoint },
{ name = "KONG_TRACING_INSTRUMENTATIONS", value = "all" },
{ name = "KONG_TRACING_SAMPLING_RATE", value = "1.0" }
# ... Kong Konnect 認証設定
]
healthCheck = {
command = ["CMD-SHELL", "kong health"]
interval = 30
timeout = 5
retries = 3
startPeriod = 60
}
},
{
name = "otel-collector"
image = "${aws_ecr_repository.otel_collector.repository_url}:latest"
essential = true
portMappings = [
{ containerPort = 4318, hostPort = 4318, protocol = "tcp" }
]
environment = [
{ name = "NEW_RELIC_LICENSE_KEY", value = var.new_relic_license_key }
]
}
])
}
dummy-api タスク定義
resource "aws_ecs_task_definition" "dummy_api" {
family = "${local.name_prefix}-dummy-api"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = var.api_task_cpu
memory = var.api_task_memory
execution_role_arn = aws_iam_role.ecs_task_execution.arn
task_role_arn = aws_iam_role.ecs_task.arn
container_definitions = jsonencode([
{
name = "dummy-api"
image = "${aws_ecr_repository.dummy_api.repository_url}:latest"
essential = true
portMappings = [
{ containerPort = 3000, hostPort = 3000, protocol = "tcp" }
]
environment = [
{ name = "PORT", value = "3000" },
{ name = "OTEL_EXPORTER_OTLP_ENDPOINT", value = "http://localhost:4318" },
{ name = "OTEL_SERVICE_NAME", value = "dummy-api" },
{ name = "DOWNSTREAM_API_URL", value = "http://downstream-api.${local.name_prefix}.local:3001" }
]
healthCheck = {
command = ["CMD-SHELL", "wget -q --spider http://localhost:3000/health || exit 1"]
interval = 30
timeout = 5
retries = 3
startPeriod = 30
}
},
{
name = "otel-collector"
image = "${aws_ecr_repository.otel_collector.repository_url}:latest"
essential = true
portMappings = [
{ containerPort = 4318, hostPort = 4318, protocol = "tcp" }
]
environment = [
{ name = "NEW_RELIC_LICENSE_KEY", value = var.new_relic_license_key }
]
}
])
}
ポイント
- OTEL_EXPORTER_OTLP_ENDPOINT:
http://localhost:4318でサイドカーに送信 - DOWNSTREAM_API_URL: Cloud MapのDNS名でサービス間通信
- KONG_TRACING_INSTRUMENTATIONS:
allで全てのリクエストをトレース
AWS Cloud Map(サービスディスカバリ)
設定
resource "aws_service_discovery_private_dns_namespace" "main" {
name = "${local.name_prefix}.local"
description = "Private DNS namespace for ${local.name_prefix}"
vpc = aws_vpc.main.id
}
resource "aws_service_discovery_service" "dummy_api" {
name = "dummy-api"
dns_config {
namespace_id = aws_service_discovery_private_dns_namespace.main.id
dns_records {
ttl = 10
type = "A"
}
routing_policy = "MULTIVALUE"
}
health_check_custom_config {
failure_threshold = 1
}
}
ECSサービスとの連携
resource "aws_ecs_service" "dummy_api" {
name = "${local.name_prefix}-dummy-api"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.dummy_api.arn
# ...
service_registries {
registry_arn = aws_service_discovery_service.dummy_api.arn
}
}
サービス間通信
| サービス | DNS名 |
|---|---|
| kong | kong.kong-otel-dev.local:8000 |
| dummy-api | dummy-api.kong-otel-dev.local:3000 |
| downstream-api | downstream-api.kong-otel-dev.local:3001 |
ECSサービスをservice_registriesに登録することで、タスクのIPアドレスが自動的にDNSに登録されます。
構築手順
Terraform apply前にKong Konnectの設定が必要なので、順番に進めていきます。
Kong Konnect で Control Plane を作成
Terraform applyの前に、Kong KonnectでData Plane用の認証情報を取得します。
Kong Konnect にログインし、API Gateway → New API Gateway をクリックします。
Self-managedを選択してGateway nameを入力して作成します。

PlatformでLinux(Docker)を選択してGenerate certificateをクリックします。
エンドポイントや証明書が表示されるので、控えておきます。

New Relic の準備
New Relic にログインし、API Keys で Ingest - License Key を取得します。
terraform.tfvars を設定
# リポジトリをクローン
git clone https://github.com/egawa-takeki/kong-fargate-otel-newrelic.git
cd kong-fargate-otel-newrelic/terraform
# 変数ファイルをコピー
cp terraform.tfvars.example terraform.tfvars
terraform.tfvars を編集し、Kong Konnectで取得した情報を次のように設定します。
# Kong Konnect の設定
kong_cluster_endpoint = "xxxxxxxx.us.cp0.konghq.com:443"
kong_telemetry_endpoint = "xxxxxxxx.us.tp0.konghq.com:443"
# 証明書(改行は \n でエスケープ)
kong_cluster_cert = "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----"
kong_cluster_cert_key = "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----"
# New Relic License Key
new_relic_license_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
ECR リポジトリを作成
terraform init
terraform apply -target=aws_ecr_repository.dummy_api \
-target=aws_ecr_repository.downstream_api \
-target=aws_ecr_repository.otel_collector
Docker イメージのビルドとプッシュ
# AWS にログイン
aws ecr get-login-password --region ap-northeast-1 | \
docker login --username AWS --password-stdin \
$(aws sts get-caller-identity --query Account --output text).dkr.ecr.ap-northeast-1.amazonaws.com
# ECR URL を取得
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=ap-northeast-1
# 各イメージをビルド・プッシュ
cd ../app/dummy-api
docker build -t ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/kong-otel-dev-dummy-api:latest .
docker push ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/kong-otel-dev-dummy-api:latest
cd ../downstream-api
docker build -t ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/kong-otel-dev-downstream-api:latest .
docker push ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/kong-otel-dev-downstream-api:latest
cd ../../otel-collector
docker build -t ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/kong-otel-dev-otel-collector:latest .
docker push ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/kong-otel-dev-otel-collector:latest
インフラのデプロイ
cd ../terraform
terraform plan
terraform apply
デプロイ完了後、Kong Konnect接続できれば成功です。

Kong Konnect でルーティング設定
Data Planeが接続されたら、Kong Konnectコンソールでルーティングを設定します。
Gateway Services → New Gateway Service を選択します。
| 項目 | 値 |
|---|---|
| Name | dummy-api-service |
| Host | dummy-api.kong-otel-dev.local |
| Port | 3000 |
| Protocol | http |
作成したServiceの Routes → Add Route を選択します。
| 項目 | 値 |
|---|---|
| Name | dummy-api-route |
| Paths | /api |
| Strip Path | OFF |
OpenTelemetry プラグインを有効化
Plugins → Add Plugin → OpenTelemetry を選択します。
| 項目 | 値 |
|---|---|
| Traces Endpoint | http://localhost:4318/v1/traces |
| Propagation Default Format | w3c |

これで分散トレーシングの設定が完了です。
動作確認
# ALB DNS名を取得
ALB_DNS=$(terraform output -raw alb_dns_name)
# APIにアクセス
curl -i http://${ALB_DNS}/api/users
# 分散トレーシング確認(downstream-apiを呼び出し)
curl http://${ALB_DNS}/api/chain
# 特定IDでの分散トレーシング確認
curl http://${ALB_DNS}/api/chain/123
New Relic での確認
設定が完了すると、New RelicのTraces画面でトレースを確認できます。

さいごに
Kong GatewayのOpenTelemetryプラグインとOTEL Collectorを組み合わせた分散トレーシングを試してみました。
インフラ構築はTerraformで再現できるようにしているので、ぜひ試してみてください。
この記事がどなたかの参考になれば幸いです。










