AWS Lambda での OpenTelemetry 計装 - 手動計装と Lambda Layers の活用
AWS Lambda で実行されるサーバーレスアプリケーションに OpenTelemetry を導入することは、複数のマイクロサービスの相互作用を可視化するために欠かせません。本記事では、Lambda 関数内での手動計装、Lambda Layers を使用した自動計装、そして AWS X-Ray との連携について、実践的な実装方法を解説します。
初めてサーバーレス環境での可視化に取り組む場合は、サーバーレス環境のObservability - OpenTelemetryによる計装の課題と解決策の記事で、サーバーレス環境特有の課題と対策パターンを先に理解されることをお勧めします。
Lambda 環境での OpenTelemetry の導入戦略
Lambda での OpenTelemetry 導入は、従来のコンテナやサーバーベース環境とは異なるアプローチが必要です。
環境の制約と対応
Lambda 関数の実行時間は通常数秒です。そのため、テレメトリデータの送信がタイムアウトする前に完了する必要があります。また、関数のデプロイメントパッケージサイズに制限があり(250MB)、大量の依存関係を含めることができません。
依存関係の最小化 不要なパッケージを除外し、本当に必要なインストルメンテーションのみを含めることで、パッケージサイズを最小化します。特に Python の auto-instrumentation パッケージは多くの依存関係を持つため、個別パッケージの選択が効果的です。
コールドスタート時間への対応 OpenTelemetry ライブラリの初期化はコールドスタート時間を増加させます。この影響を最小化するため、遅延初期化やネイティブの Lambda 拡張機能の活用を検討します。
Python での手動計装
Python は Lambda でも一般的に使用される言語で、OpenTelemetry のサポートも充実しています。
基本的な設定
Python アプリケーションに手動計装を追加する場合、まず必要なパッケージをインストールします。基本的なセットアップには、コア API、SDK、そしてエクスポーターが必要です。
OTLP エクスポーターは OTLP 形式でデータを送信でき、AWS X-Ray を含む多くのバックエンドが対応しています。XML 形式で X-Ray に直接送信することも可能ですが、OTLP 経由の方が拡張性が高いです。
Lambda ハンドラーでのトレーシング
Lambda の標準ハンドラーは event と context を受け取る関数です。これをトレーシングで計装する場合、関数の開始時にスパンを作成し、終了時にスパンを終了させます。
重要な点は、関数の終了前にエクスポーターをフラッシュすることです。Lambda 関数は実行完了時に即座に終了され、バッファ内のデータが失われる可能性があります。明示的なフラッシュを行わないと、一部のテレメトリデータが送信されないことがあります。
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor
# グローバル初期化(Lambda では最初の invocation でのみ実行)
tracer = None
def initialize_tracer():
global tracer
exporter = OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces")
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(exporter))
tracer = trace.get_tracer(__name__)
def lambda_handler(event, context):
global tracer
if tracer is None:
initialize_tracer()
with tracer.start_as_current_span("process_request") as span:
span.set_attribute("request.id", event.get("requestId"))
# ビジネスロジック
result = process(event)
# 終了前にフラッシュ
trace.get_tracer_provider().force_flush(timeout_millis=1000)
return resultこの例では、tracer のグローバル初期化は最初の invocation でのみ実行されます。これはコールドスタート時の初期化コストを容認しながら、以後の invocation では初期化コストを避ける工夫です。
X-Ray との連携
AWS X-Ray は Lambda ネイティブの分散トレーシングサービスです。OpenTelemetry からの Trace データを X-Ray 形式に変換して送信することで、AWS Console から直接トレースを確認できます。
X-Ray エクスポーターを使用することで、OTLP からの変換なく直接データを送信できます。この場合、Lambda IAM ロールに xray:PutTraceSegments と xray:PutTelemetryRecords の権限が必要です。
Node.js での手動計装
Node.js は Lambda 上で高速に動作し、OpenTelemetry のサポートも充実しています。
Express.js アプリケーションの計装
Express.js で実行されるサーバーレス API(API Gateway + Lambda)の場合、HTTP リクエストとレスポンスを自動的に計装できます。
const express = require('express');
const { NodeTracerProvider } = require("@opentelemetry/sdk-node");
const { getNodeAutoInstrumentations } = require("@opentelemetry/auto-instrumentations-node");
const { OTLPTraceExporter } = require("@opentelemetry/exporter-trace-otlp-http");
const { BatchSpanProcessor } = require("@opentelemetry/sdk-trace-node");
// トレーサープロバイダーの初期化
const sdk = new NodeTracerProvider({
instrumentations: [getNodeAutoInstrumentations()]
});
const exporter = new OTLPTraceExporter({
url: "http://localhost:4318/v1/traces",
});
sdk.addSpanProcessor(new BatchSpanProcessor(exporter));
sdk.register();
const app = express();
app.get('/api/process', (req, res) => {
// リクエスト処理
res.json({ status: "success" });
});
// Lambda ハンドラー
exports.handler = async (event, context) => {
// AWS Lambda Proxy Integration の event を Express に変換
const result = await new Promise((resolve, reject) => {
app(createRequest(event), createResponse(resolve, reject));
});
// フラッシュ
sdk.getActiveSpanProcessor().forceFlush();
return result;
};この例は簡略化されています。実際の実装では、API Gateway イベントを Express のリクエストに変換する処理が必要になります。
Lambda Layers による自動計装
Lambda Layers は、複数の関数で共有するコードやライブラリをパッケージ化できる機能です。これを利用することで、OpenTelemetry の自動計装を複数の関数に一括して適用できます。
Lambda 拡張機能としての活用
AWS はオフィシャルの OpenTelemetry Lambda Layers を提供しています。これらを使用することで、手動計装なしに一定レベルの可視化が実現できます。
Layer の作成は、node_modules や lib ディレクトリ構造を含む ZIP ファイルを作成し、AWS Lambda Console または AWS CLI を通じてアップロードすることで行われます。関数の設定で Layer を指定することで、その関数に適用されます。
環境変数による設定
Layer で自動計装を有効化する場合、Lambda の環境変数でエクスポーターのエンドポイントやサンプリング率を設定できます。
一般的な環境変数
OTEL_EXPORTER_OTLP_ENDPOINT: OTLP エクスポーターのエンドポイントOTEL_TRACES_SAMPLER: サンプリング戦略(always_on, always_off, traceidratio など)OTEL_TRACES_SAMPLER_ARG: サンプリング率(traceidratio の場合、0 から 1 の値)
X-Ray との統合ベストプラクティス
Trace Context の伝播
複数の Lambda 関数が連携する場合、トレースコンテキストを適切に伝播させることが重要です。HTTP API Gateway 経由の場合は、traceparent ヘッダーが自動的に伝播されます。
一方、SQS や SNS を経由する場合は、メッセージにトレースコンテキストを埋め込む必要があります。OpenTelemetry の W3C Trace Context 標準に従い、メッセージの属性またはボディにトレースコンテキスト情報を含めることが推奨されます。
サンプリングの設定
本番環境では、全てのリクエストをトレースすると、データボリュームとコストが膨大になります。適切なサンプリング戦略を設定することが重要です。
X-Ray の場合、Console から動的にサンプリングルールを設定できます。OpenTelemetry では、環境変数 OTEL_TRACES_SAMPLER でサンプラーを指定し、OTEL_TRACES_SAMPLER_ARG でサンプリング率を設定します。
エラーハンドリング
Lambda 関数でエラーが発生した場合、スパンに例外情報を記録することが重要です。これにより、エラーの原因を特定しやすくなります。
from opentelemetry.trace.status import Status, StatusCode
try:
# 処理
result = process(event)
except Exception as e:
span.record_exception(e)
span.set_status(Status(StatusCode.ERROR))
raise本番環境での運用
Lambda での OpenTelemetry 運用には、いくつかの考慮点があります。
パフォーマンスモニタリング コールドスタート時の OpenTelemetry 初期化時間を定期的に計測し、アプリケーション全体のパフォーマンスへの影響を把握することが重要です。
コスト管理 X-Ray やその他の監視サービスのコストは、トレーシング量に比例します。サンプリング戦略を適切に設定し、本当に必要なトレースのみを収集することが重要です。
ログとトレースの統合 Lambda のログとトレースを統合することで、問題発生時の調査を効率化できます。CloudWatch Logs で Trace ID を含めることで、ログからトレース詳細へのナビゲーションが容易になります。
AWS Lambda での OpenTelemetry の導入は、段階的なアプローチをお勧めします。まずは手動計装で重要な処理を追跡し、その後、自動計装や Lambda Layers の活用を検討することで、効率的にシステム全体の可視化を実現できます。