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.42b0google-cloud-trace パッケージは GCP ネイティブのライブラリで、Cloud Trace へのデータ送信を簡潔に行えます。opentelemetry-exporter-gcp-trace は、OpenTelemetry の標準 API を使用しながら、GCP の認証と送信を自動的に処理します。
HTTP トリガー関数での実装
Cloud Functions で HTTP リクエストを処理する関数の場合、Flask フレームワークが標準で使用されます。Flask に OpenTelemetry を統合する実装例を示します。
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 メッセージをトリガーとして実行されることも多いです。この場合、メッセージのトレースコンテキストを抽出して、前の関数からのトレースの流れを継続する必要があります。
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 に必要な依存関係を記述します。
{
"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 トリガー関数の実装例です。
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 トリガー関数でもトレースコンテキストの継続が可能です。
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 とログの相互参照が可能になります。
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 ではスパンに任意の属性を付与できます。これにより、ビジネスロジックに固有の情報を記録し、監視を強化できます。
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 での本番運用では、全トレースの記録はコストと性能に影響します。適切なサンプリング率を設定することが重要です。
from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
sampler = TraceIdRatioBased(rate=0.1) # 10% のリクエストを記録
trace.set_tracer_provider(TracerProvider(sampler=sampler))エラーハンドリング
関数内でエラーが発生した場合、スパンに例外を記録することで、エラーのスタックトレースが Cloud Trace に保存されます。
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 との連携により、サーバーレス環境でも従来のコンテナ環境と同等の可視性を確保できます。