Google Cloud Functions での OpenTelemetry 計装 - Cloud Trace との連携 ​

Google Cloud Platform(GCP)で実行される Cloud Functions アプリケーションに OpenTelemetry を導入することで、サーバーレス環境での分散トレーシングを実現できます。本記事では、Python と Node.js での実装方法、Cloud Trace との連携、そして Pub/Sub など GCP ネイティブサービスとの統合について解説します。

初めてサーバーレス環境での可視化に取り組む場合は、サーバーレス環境のObservability - OpenTelemetryによる計装の課題と解決策の記事で、サーバーレス環境特有の課題と対策パターンを先に理解されることをお勧めします。

GCP サーバーレス環境での OpenTelemetry ​

Google Cloud Functions(Cloud Functions)は GCP のサーバーレスコンピューティングサービスで、AWS Lambda に相当します。GCP では Cloud Trace が分散トレーシングの標準サービスで、OpenTelemetry との統合が充実しています。

Google Cloud Functions の特性 ​

Cloud Functions では、コードをアップロードするだけで実行環境が自動的に準備されます。HTTP トリガー、Pub/Sub トリガー、Cloud Storage トリガーなど、様々なイベントソースからの呼び出しに対応しています。

実行環境の制限は Lambda と類似しており、実行時間(標準は 60 秒、最大 540 秒)、メモリ(128MB から 16GB)、タイムアウト前のデータ送信確保が重要です。

Cloud Trace との連携メリット ​

Google Cloud Trace は GCP ネイティブの分散トレーシングサービスです。OpenTelemetry からのデータを直接 Cloud Trace に送信でき、GCP Console から一元的に監視できます。

特に、Google Cloud Trace は OpenTelemetry との統合が深く、自動的な Trace Context 伝播や、GCP リソースメタデータの自動付与など、サーバーレス環境に最適化された機能を提供しています。

Python での実装 ​

Python は GCP でよく使用される言語で、Cloud Functions でも標準的なランタイムです。

基本的なセットアップ ​

Python で OpenTelemetry を使用する場合、requirements.txt に必要なパッケージを記述し、Cloud Functions デプロイ時に自動的にインストールされます。

google-cloud-trace==1.11.0
opentelemetry-api==1.21.0
opentelemetry-sdk==1.21.0
opentelemetry-exporter-gcp-trace==1.5.0
opentelemetry-instrumentation==0.42b0
opentelemetry-instrumentation-flask==0.42b0

google-cloud-trace パッケージは GCP ネイティブのライブラリで、Cloud Trace へのデータ送信を簡潔に行えます。opentelemetry-exporter-gcp-trace は、OpenTelemetry の標準 API を使用しながら、GCP の認証と送信を自動的に処理します。

HTTP トリガー関数での実装 ​

Cloud Functions で HTTP リクエストを処理する関数の場合、Flask フレームワークが標準で使用されます。Flask に OpenTelemetry を統合する実装例を示します。

python
from google.cloud import trace_v2
from opentelemetry import trace
from opentelemetry.exporter.gcp_trace import CloudTraceExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
import google.auth
import functions_framework

# グローバル初期化(関数内で一度だけ実行)
_tracer = None

def initialize_tracer():
    global _tracer
    # GCP プロジェクト ID の自動取得
    _, project_id = google.auth.default()

    # CloudTraceExporter を使用して、自動的に Cloud Trace に送信
    exporter = CloudTraceExporter(project_id=project_id)
    trace.set_tracer_provider(TracerProvider())
    trace.get_tracer_provider().add_span_processor(
        BatchSpanProcessor(exporter)
    )
    _tracer = trace.get_tracer(__name__)

@functions_framework.http
def process_request(request):
    global _tracer

    if _tracer is None:
        initialize_tracer()

    # Flask の自動計装(HTTP リクエスト/レスポンスが自動的に記録される)
    with _tracer.start_as_current_span("process_logic") as span:
        span.set_attribute("request.method", request.method)
        span.set_attribute("request.path", request.path)

        # ビジネスロジック
        result = handle_business_logic(request)

    # 関数終了時にフラッシュ
    trace.get_tracer_provider().force_flush(timeout_millis=1000)

    return result

このコードの特徴は、CloudTraceExporter が GCP の認証を自動的に処理することです。Cloud Functions の実行ロールが Cloud Trace への書き込み権限を持っていれば、追加の認証設定が不要です。

このコードでは手動計装を使用して、HTTP リクエスト全体を明示的に計装しています。重要なリクエスト属性をスパンに設定することで、監視対象のメトリクスを記録できます。

Pub/Sub トリガー関数での実装 ​

Cloud Functions は Pub/Sub メッセージをトリガーとして実行されることも多いです。この場合、メッセージのトレースコンテキストを抽出して、前の関数からのトレースの流れを継続する必要があります。

python
from opentelemetry import trace
from opentelemetry.propagate import extract

def process_pubsub_message(event, context):
    """Pub/Sub トリガーの関数"""
    if _tracer is None:
        initialize_tracer()

    # Pub/Sub メッセージからトレースコンテキストを抽出
    message_attributes = event.get('attributes', {})
    ctx = extract(message_attributes)

    with trace.set_span_in_context(ctx):
        with _tracer.start_as_current_span("process_pubsub_message") as span:
            span.set_attribute("message.id", event.get('message_id'))
            # メッセージ処理
            process_message(event)

    trace.get_tracer_provider().force_flush(timeout_millis=1000)

メッセージの属性にトレースコンテキストが含まれている場合、extract 関数で抽出し、set_span_in_context で新しいスパンに継承させることで、Pub/Sub を経由した関数間のトレース継続が実現できます。

Node.js での実装 ​

Node.js は Cloud Functions で高速に動作し、Google Cloud のライブラリとも高い互換性があります。

基本的なセットアップ ​

Node.js での実装は、package.json に必要な依存関係を記述します。

json
{
  "dependencies": {
    "@google-cloud/trace-agent": "^7.0.0",
    "@opentelemetry/api": "^1.7.0",
    "@opentelemetry/sdk-node": "^0.44.0",
    "@opentelemetry/exporter-trace-otlp-http": "^0.44.0",
    "@opentelemetry/instrumentation-express": "^0.33.0",
    "@opentelemetry/instrumentation-http": "^0.44.0"
  }
}

Google Cloud の trace-agent ライブラリを使用することで、GCP ネイティブの統合を活用できます。

HTTP トリガー関数の計装 ​

Node.js での HTTP トリガー関数の実装例です。

javascript
const functions = require('@google-cloud/functions-framework');
const { NodeTracerProvider } = require("@opentelemetry/sdk-node");
const { BatchSpanProcessor } = require("@opentelemetry/sdk-trace-node");
const { SpanStatusCode } = require("@opentelemetry/api");
const { CloudTraceExporter } = require("@google-cloud/trace-agent");

let tracer;

function initializeTracer() {
  // CloudTraceExporter を使用して GCP Cloud Trace に送信
  const sdk = new NodeTracerProvider();
  const exporter = new CloudTraceExporter();
  sdk.addSpanProcessor(new BatchSpanProcessor(exporter));
  sdk.register();
  tracer = sdk.getTracer(__filename);
}

functions.http('processRequest', (req, res) => {
  if (!tracer) {
    initializeTracer();
  }

  const span = tracer.startSpan('processRequest');
  span.setAttributes({
    'http.method': req.method,
    'http.url': req.path,
  });

  try {
    // ビジネスロジック処理
    const result = handleBusinessLogic(req);
    span.end();
    res.json({ status: 'success', data: result });
  } catch (error) {
    span.recordException(error);
    span.setStatus({ code: SpanStatusCode.ERROR });
    span.end();
    res.status(500).json({ error: error.message });
  } finally {
    // フラッシュ
    sdk.getActiveSpanProcessor()?.forceFlush?.();
  }
});

Pub/Sub トリガーでのトレース継続 ​

Node.js での Pub/Sub トリガー関数でもトレースコンテキストの継続が可能です。

javascript
const functions = require('@google-cloud/functions-framework');
const { context, propagation } = require("@opentelemetry/api");

functions.cloudEvent('processPubSubMessage', (cloudEvent) => {
  if (!tracer) {
    initializeTracer();
  }

  // Pub/Sub メッセージからトレースコンテキストを抽出
  const messageData = cloudEvent.data;
  const traceContext = messageData.attributes?.traceparent || '';

  // W3C Trace Context を抽出
  const ctx = propagation.extract(
    context.active(),
    messageData.attributes
  );

  context.with(ctx, () => {
    const span = tracer.startSpan('processPubSubMessage');
    try {
      // メッセージ処理
      processMessage(messageData);
      span.end();
    } catch (error) {
      span.recordException(error);
      span.end();
      throw error;
    } finally {
      tracer.getActiveSpanProcessor()?.forceFlush?.();
    }
  });
});

Cloud Trace との統合 ​

トレースの自動マッピング ​

OpenTelemetry からの Trace データは、CloudTraceExporter または OTLP エクスポーター経由で Cloud Trace に送信されると、自動的に Cloud Trace 形式にマッピングされます。スパン属性も自動的に Cloud Trace のフィールドにマッピングされるため、追加の設定は不要です。

ログの統合 ​

Cloud Functions では CloudLogging がロギングを処理します。OpenTelemetry の Trace ID をログに含めることで、Cloud Trace とログの相互参照が可能になります。

python
import logging
import json

def log_with_trace(message):
    """Trace ID を含むログを出力"""
    span = trace.get_current_span()
    trace_id = span.get_span_context().trace_id

    log_entry = {
        'message': message,
        'logging.googleapis.com/trace': format(trace_id, '032x'),  # 128-bit hex
    }
    logging.warning(json.dumps(log_entry))

Cloud Logging は JSON 形式のログから自動的に trace_id を認識し、Cloud Console での統合表示を実現します。

カスタム属性の活用 ​

Cloud Trace ではスパンに任意の属性を付与できます。これにより、ビジネスロジックに固有の情報を記録し、監視を強化できます。

python
with _tracer.start_as_current_span("process_payment") as span:
    span.set_attribute("customer.id", customer_id)
    span.set_attribute("order.amount", amount)
    span.set_attribute("payment.method", method)
    # 処理

ベストプラクティス ​

サンプリング戦略 ​

GCP での本番運用では、全トレースの記録はコストと性能に影響します。適切なサンプリング率を設定することが重要です。

python
from opentelemetry.sdk.trace.sampling import TraceIdRatioBased

sampler = TraceIdRatioBased(rate=0.1)  # 10% のリクエストを記録
trace.set_tracer_provider(TracerProvider(sampler=sampler))

エラーハンドリング ​

関数内でエラーが発生した場合、スパンに例外を記録することで、エラーのスタックトレースが Cloud Trace に保存されます。

python
from opentelemetry.trace.status import Status, StatusCode

try:
    result = risky_operation()
except Exception as e:
    span.record_exception(e)
    span.set_status(Status(StatusCode.ERROR))
    # リトライやエラーハンドリング

関数間のメタデータ伝播 ​

複数の Cloud Functions が連携する場合、トレースコンテキストだけでなく、ビジネスロジックに必要なメタデータも伝播させることが重要です。OpenTelemetry のバゲージ機能を活用することで、これが実現できます。

Google Cloud Functions での OpenTelemetry の導入は、GCP のネイティブサービスとの統合により、効率的に実現できます。Cloud Trace との連携により、サーバーレス環境でも従来のコンテナ環境と同等の可視性を確保できます。