お題目
前回の記事でAWS SAMを利用して作成したLambda関数にADOTを追加することで、
アプリを改修することなくパフォーマンス情報を収集します。
今回はOTel(ADOT)を利用して、X-Rayにテレメトリーデータを連携します。
自動計装のための技術
各用語の解説になります。
まず、以降で説明するためのシーケンス図だけ掲載します。
OTel(Open Telemetry)とは
アプリケーション内部の挙動を詳細に観測するための、オープンなフレームワークです。
ベンダーに依存することなく、テレメトリーデータ(測定データ)を生成、収集、管理、エクスポートするための取り決めです。
トレース
1回のリクエスト全体を表します(クライアントからのリクエスト〜レスポンスまで)。
例えば、上記シーケンス図の(1)から(7)までを1つのシーケンスとして扱います。
(親)スパン
アプリケーション内部の処理単位です。
例えば、上記シーケンス図の(2)から(6)までを1つのスパンとして扱います。
(子)スパン
親スパンを細分化し、詳細な処理ごとに分けた単位です。
例えば、上記シーケンス図の(3)、(4)、(5)の1つ1つを指します。
X-Rayとは
分散トレーシングを実現するための、AWS上のマネージドサービスです。
アプリケーションからOTelのテレメトリーデータを連携することで、収集、管理することができます。トレースを検索したり、トレース内の各スパンにどれだけの時間を要したか図で表示することができます。
※ X-RayではOTelで言うところの"スパン"を"セグメント"と言います。
ADOT(AWS Distro for OpenTelemetry)とは
概要
オープンソースのOpenTelemetryをベースにAWSが開発したツールです。
OpenTelemetryのテレメトリーデータをX-Rayで扱えるようにします。
ECSではサイドカーとして、LambdaではLambda Layerとして提供され、アプリケーションの外部に配置するだけで自動計装を実現できます。(ただし、セグメント(スパン)の粒度には限界があります。)スパンの粒度を詳細化するには、アプリケーションにSDKを組み込んで作り込む必要があります。
ADOT Node Layer(SDK)
計測対象のLambdaに対して、Lambda Layerとして追加することで、テレメトリーデータを生成してくれるコンポーネントです。AWSがリージョンごとに公開しているARNを指定します。
OTel Export
デフォルト127.0.0.1:4318でリッスンし、ADOT Node Layerが生成したテレメトリーデータを受け取ります。
ADOT Collector Extension
収集したテレメトリーデータをX-Rayに転送してくれるコンポーネントです。
AWSがリージョンごとに公開しているARNを指定します。
検証
前回記事でAWS SAMで作成したLambdaにADOTを追加していきます。
template.yamlを書き換えるだけで、アプリケーションに手は入れません。
template.yamlの編集
template.yamlにParametersセクションとConditionsセクションを追加して、ADOTのARNを渡せるようにします。
sam deployの引数として指定して後から上書きすることもできます。
ADOT Collector Extensionは任意としています。
Parameters:
# 必須:ADOT Node.js ランタイム Layer の ARN(リージョン/アーキ用)
AdotNodeLayerArn:
Type: String
Default: "arn:aws:lambda:ap-northeast-1:615299751070:layer:AWSOpenTelemetryDistroJs:9"
Description: ADOT Node.js runtime layer ARN (match your region/arch)
AdotCollectorLayerArn:
Type: String
Default: ""
Description: ADOT Collector extension layer ARN (optional)
# OTLP エンドポイント(Collector Extension使うなら http://127.0.0.1:4318 を推奨)
OtelOtlpEndpoint:
Type: String
Default: http://127.0.0.1:4318
Description: OTLP endpoint (e.g., http://127.0.0.1:4318 or https://collector.example:4318)
# OTLP プロトコル
OtelOtlpProtocol:
Type: String
AllowedValues: ["http/protobuf", "grpc"]
Default: http/protobuf
Conditions:
HasCollector: !Not [!Equals [!Ref AdotCollectorLayerArn, ""]]
同様にGlobalsセクションに、OTel関連の環境変数を追記します。
Globals:
Function:
Runtime: nodejs20.x
Timeout: 10
MemorySize: 128
Architectures: [x86_64]
Tracing: Active
CodeUri: .
Environment:
Variables:
TABLE_NAME: !Ref MessagesTable
QUEUE_URL: !Ref InboundQueue
# ---------- OTel共通 ----------
AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-handler
OTEL_RESOURCE_ATTRIBUTES: !Sub 'service.namespace=${AWS::StackName},aws.region=${AWS::Region}'
OTEL_TRACES_EXPORTER: otlp
OTEL_METRICS_EXPORTER: otlp
OTEL_LOGS_EXPORTER: otlp
OTEL_EXPORTER_OTLP_ENDPOINT: !Ref OtelOtlpEndpoint
OTEL_EXPORTER_OTLP_PROTOCOL: !Ref OtelOtlpProtocol
OTEL_TRACES_SAMPLER: parentbased_always_on
それぞれのLambda関数の定義にLayersセクションを追加します。
下記はEnqueueの記述例です。
# ---------- ① Enqueue ----------
EnqueueFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub '${AWS::StackName}-Enqueue'
Handler: src/enqueue/index.handler
Layers:
- !Sub '${AdotNodeLayerArn}'
- !If [HasCollector, !Sub '${AdotCollectorLayerArn}', !Ref "AWS::NoValue"]
Policies:
- AWSLambdaBasicExecutionRole
- SQSSendMessagePolicy:
QueueName: !GetAtt InboundQueue.QueueName
Events:
EnqueueGet:
Type: HttpApi
Properties:
ApiId: !Ref HttpApi
Path: /enqueue
Method: GET
ここで気づいた。
REST APIで作ればよかった。。。
API GatewayがHTTP APIの場合、X-Rayに連携できません。
検証なのでコスト削減ということで、このままHTTP APIでいきます。
SAMのデプロイ
ap-northeast-1のADOTのARNを指定してデプロイします。
sam build
sam deploy \
--stack-name sam-test-stack \
--region ap-northeast-1 \
--capabilities CAPABILITY_IAM \
--no-confirm-changeset \
--resolve-s3 \
--parameter-overrides \
AdotNodeLayerArn="arn:aws:lambda:ap-northeast-1:615299751070:layer:AWSOpenTelemetryDistroJs:9"
OtelOtlpEndpoint=http://127.0.0.1:4318 \
OtelOtlpProtocol=http/protobuf
AdotNodeLayerArnで指定した
arn:aws:lambda:ap-northeast-1:615299751070:layer:AWSOpenTelemetryDistroJs:9
はCollectorが含まれているということなので、Collectorの指定は省略します。
確認
では確認。
Lambda実行
前回同様、curlコマンドでリクエストを投げます。
X-Rayに情報が連携されることの確認が目的のため、curlコマンドの実行結果は割愛します。
キューに突っ込む
curl -sS "APIのURL/enqueue?msg=test"
DynamoDBの中身を確認
curl -sS "APIのURL/items?limit=10" | jq .
X-Rayを確認
X-Rayのコンソールにトレースの一覧が表示されました。
トレースの内容を見てみます。
「1.現在のトレース」と、「2.リンクされたトレース」の2つが図で表示されました。

上記の例では「1.現在のトレース」としてEnqueue Lambdaが表示されています。
「2.リンクされたトレース」はWorker Lambdaを指しているようです。
Enqueue Lambdaがクライアントから呼ばれたあと、「Lambda Context」と「Lambda Function」の2つのアイコンがあります。
Lambda Context
Lambda Cold/Warm Startも含んだ処理全体を表します。
Lambda Function
Handler関数の処理を表します。
上記例で行くと、Lambd Functionの実行時間にInitとOverheadの時間を足したものがLambda Contextになります。InitがColod/Warm Startの処理時間、OverheadがExtensionの起動・終了を指すようです。
SQS
Lambda FunctionはさらにSQSの処理を含んでいます。
sam-test-stack-EnqueueアイコンはLambda側からのSQSの呼び出しを表し、SQSアイコン側はSQSマネージドサービス側での処理を表しているようです。
DynamoDB
SQS同様に、「2.リンクされたトレース」側のWorker Lambda内の処理としてDynamoDBの処理も表示されています。
見えた課題
それぞれのLambdaの実行時間(Init、Overhead、Invoke(Handerの実行))は確認できましたが、
* Enqueue Lambda内で実行したSQSの処理時間
* Workder Lambda内で実行したDynamoDBへのアクセス時間
は取得できませんでした。
ADOT Layerを
AdotNodeLayerArn "arn:aws:lambda:ap-northeast-1:901920570463:layer:aws-otel-nodejs-amd64-ver-1-25-0:1"
AdotCollectorLayerArn "arn:aws:lambda:ap-northeast-1:901920570463:layer:aws-otel-collector-amd64-ver-0-102-0:1"
から
AdotNodeLayerArn "arn:aws:lambda:ap-northeast-1:615299751070:layer:AWSOpenTelemetryDistroJs:9"
AdotCollectorLayerArn "" (指定しない)
に変更し
Layerのハンドラを
AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-handler
から
AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-instrument
に修正したところ、上記のとおりSQS、Lambda含めて自動計装できました。
まとめ
ADOTを利用することで、アプリケーションを改修せずにある程度トレースを取得することはできました。
ただし、さらに細かいセグメント(スパン)の情報を計測するにはアプリケーションの改修が必要なようです。
もしかしてApplication Signalsでサービス自動検出すればできる???(またの機会に調べます)

