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 関数は実行完了時に即座に終了され、バッファ内のデータが失われる可能性があります。明示的なフラッシュを行わないと、一部のテレメトリデータが送信されないことがあります。

python
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 リクエストとレスポンスを自動的に計装できます。

javascript
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 関数でエラーが発生した場合、スパンに例外情報を記録することが重要です。これにより、エラーの原因を特定しやすくなります。

python
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 の活用を検討することで、効率的にシステム全体の可視化を実現できます。