AWS Lambda ベースのサーバーレスオブザーバビリティ
分散システムとサーバーレスコンピューティングの世界では、アプリケーションの信頼性とパフォーマンスを確保するためにオブザーバビリティが重要です。 これは従来のモニタリング以上のものを含んでいます。 Amazon CloudWatch や AWS X-Ray などの AWS オブザーバビリティツールを活用することで、サーバーレスアプリケーションの洞察を得て、問題のトラブルシューティングを行い、アプリケーションのパフォーマンスを最適化できます。 このガイドでは、Lambda ベースのサーバーレスアプリケーションのオブザーバビリティを実装するための重要な概念、ツール、ベストプラクティスについて学びます。
インフラストラクチャやアプリケーションのオブザーバビリティを実装する前の最初のステップは、主要な目標を決定することです。 それは、ユーザーエクスペリエンスの向上、開発者の生産性の向上、Service Level Objective (SLO) の達成、ビジネス収益の増加、またはアプリケーションの種類に応じた他の特定の目標かもしれません。 そのため、これらの主要な目標を明確に定義し、それらをどのように測定するかを確 立してください。 そこから Working Backwards してオブザーバビリティ戦略を設計します。 詳細については、「Monitor what matters」を参照してください。
オブザーバビリティの柱
オブザーバビリティには 3 つの主要な柱があります:
- ログ:アプリケーションやシステム内で発生した障害、エラー、状態変更などの個別のイベントを記録したタイムスタンプ付きの記録
- メトリクス:様々な時間間隔で測定された数値データ(時系列データ)、SLI(リクエストレート、エラーレート、所要時間、CPU 使用率など)
- トレース:複数のアプリケーションやシステム(通常はマイクロサービス)にまたがる単一のユーザーの行動を表すもの
AWS は、AWS Lambda アプリケーションの実用的なインサイトを得るために、ネイティブおよびオープンソースのツールを提供し、ログ記録、メトリクスのモニタリング、トレースを可能にします。
ログ
オブザーバビリティのベストプラクティスガイドのこのセクションでは、以下のトピックについて詳しく説明します:
- 非構造化ログと構造化ログの比較
- CloudWatch Logs Insights
- ログの相関 ID
- Lambda Powertools を使用したコードサンプル
- CloudWatch Dashboards を使用したログの可視化
- CloudWatch Logs の保持期間
ログは、アプリケーション内で発生した個別のイベントです。 これには、障害、エラー、実行パス、その他のイベントが含まれます。 ログは、非構造化、半構造化、または構造化された形式で記録できます。
非構造化ログと構造化ログ
開発者は、アプリケーション内で print や console.log ステートメントを使用した単純なログメッセージから始めることがよくあります。
これらは、プログラムによる解析が難しく、特に異なるロググループにわたって多くのログメッセージを生成する AWS Lambda ベースのアプリケーションでは、スケールに応じた解析が困難です。
その結果、CloudWatch でこれらのログを統合することは困難で、分析が難しくなります。
ログ内の関連情報を見つけるには、テキストマッチや正規表現を使用する必要があります。
以下は、非構造化ログの例です:
[2023-07-19T19:59:07Z] INFO Request started
[2023-07-19T19:59:07Z] INFO AccessDenied: Could not access resource
[2023-07-19T19:59:08Z] INFO Request finished
ご覧のように、ログメッセージには一貫した構造がなく、有用な洞察を得ることが困難です。 また、コンテキスト情報を追加することも難しくなっています。
一方、構造化ログは、情報を一貫したフォーマット(多くの場合 JSON)でログに記録する方法です。 これにより、ログをテキストではなくデータとして扱うことができ、クエリとフィルタリングが簡単になります。 開発者はプログラムによってログを効率的に保存、取得、分析することができます。 また、デバッグも容易になります。 構造化ログは、ログレベルを通じて異なる環境でのログの詳細度を簡単に変更する方法を提供します。 ログレベルに注意を払ってください。 過度なログ出力はコストを増加させ、アプリケーションのスループットを低下させます。 ログを記録する前に、個人を特定できる情報が編集されていることを確認してください。 以下は、構造化ログの例です:
{
"correlationId": "9ac54d82-75e0-4f0d-ae3c-e84ca400b3bd",
"requestId": "58d9c96e-ae9f-43db-a353-c48e7a70bfa8",
"level": "INFO",
"message": "AccessDenied",
"function-name": "demo-observability-function",
"cold-start": true
}
トランザクション、異なるコンポーネント間の相関識別子、アプリケーションのビジネス結果に関する運用情報を出力するには、CloudWatch logs への構造化された一元的なログ記録を推奨します。
CloudWatch Logs Insights
CloudWatch Logs Insights を使用すると、JSON 形式のログのフィールドを自動的に検出できます。 さらに、JSON ログを拡張して、アプリケーション固有のカスタムメタデータをログに記録することができ、これを使用してログの検索、フィルタリング、集計が可能です。
ログの 相関 ID
たとえば、API Gateway から受信した HTTP リクエストの場合、相関 ID は requestContext.requestId パスに設定されており、Lambda Powertools を使用してダウンストリームの Lambda 関数で簡単に抽出してログに記録できます。
分散システムでは、多くの場合、複数のサービスやコンポーネントが連携してリクエストを処理します。 そのため、相関 ID をログに記録し、それをダウンストリームシステムに渡すことは、エンドツーエンドのトレースとデバッグに重要です。
相関 ID は、リクエストの最初の段階で割り当てられる一意の識別子です。 リクエストが異なるサービスを通過する際、相関 ID がログに含まれることで、リクエストの全体的な経路をトレースできます。
AWS Lambda のログに相関 ID を手動で挿入するか、AWS Lambda Powertools のようなツールを使用して、API Gateway から相関 ID を簡単に取得し、アプリケーションログと共に記録することができます。 たとえば、HTTP リクエストの相関 ID は、API Gateway で開始され、Lambda 関数などのバックエンドサービスに渡されるリクエスト ID とすることができます。
Lambda Powertools を使用したコードサンプル
ベストプラクティスとして、リクエストのライフサイクルの早い段階、できれば API Gateway やアプリケーションロードバランサーなどのサーバーレスアプリケーションのエントリーポイントで、相関 ID を生成します。 分散システム全体でリクエストを追跡するために、UUID やリクエスト ID、その他の一意の属性を使用します。 相関 ID をカスタムヘッダー、ボディ、またはメタデータの一部として、各リクエストと共に渡します。 ダウンストリームサービスのすべてのログエントリとトレースに相関 ID が含まれていることを確認します。
Lambda 関数のログの一部として相関 ID を手動でキャプチャして含めるか、AWS Lambda Powertools のようなツールを使用することができます。 Lambda Powertools を使用すると、サポートされているアップストリームサービスの事前定義されたリクエストパスマッピングから相関 ID を簡単に取得し、アプリケーションログと共に自動的に追加できます。 また、障害が発生した場合に簡単にデバッグし、根本原因を特定して元のリクエストに関連付けられるように、すべてのエラーメッセージに相関 ID が追加されていることを確認してください。
以下のサーバーレスアーキテクチャにおける 、相関 ID を使用した構造化ログと CloudWatch での表示方法を示すコードサンプルを見てみましょう:

// Initializing Logger
Logger log = LogManager.getLogger();
// Uses @Logger annotation from Lambda Powertools, which takes optional parameter correlationIdPath to extract correlation Id from the API Gateway header and inserts correlation_id to the Lambda function logs in a structured format.
@Logging(correlationIdPath = "/headers/path-to-correlation-id")
public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
...
// The log statement below will also have additional correlation_id
log.info("Success")
...
}
この例では、Java ベースの Lambda 関数が Lambda Powertools ライブラリを使用して、API Gateway リクエストから送られてくる correlation_id をログに記録しています。
コードサンプルの CloudWatch ログの例:
{
"level": "INFO",
"message": "Success",
"function-name": "demo-observability-function",
"cold-start": true,
"lambda_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72",
"correlation_id": "<correlation_id_value>"
}_