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>"
}_
CloudWatch Dashboards を使用したログの可視化
構造化された JSON 形式でデータをログに記録すると、CloudWatch Logs Insights は JSON 出力の値を自動的に検出し、メッセージをフィールドとして解析します。
CloudWatch Logs Insights は、ログストリームを検索およびフィルタリングするための専用の SQL ライクなクエリ 言語を提供します。
glob や正規表現のパターンマッチングを使用して、複数のロググループに対してクエリを実行できます。
また、カスタムクエリを作成して保存し、毎回作り直すことなく再実行することもできます。

CloudWatch Logs Insights では、1 つ以上の集計関数を使用してクエリから折れ線グラフ、棒グラフ、積み上げ面グラフなどの可視化を生成できます。
これらの可視化を CloudWatch Dashboards に簡単に追加できます。
以下のサンプルダッシュボードは、Lambda 関数の実行時間のパーセンタイルレポートを示しています。
このようなダッシュボードにより、アプリケーションのパフォーマンス改善に注力すべき箇所を素早く把握できます。
平均レイテンシーは重要なメトリクスですが、平均レイテンシーではなく p99 の最適化を目指すべきです。

CloudWatch 以外の場所に(プラットフォーム、関数、拡張機能の)ログを送信するには、Lambda Extensions と Lambda Telemetry API を使用できます。
多くの パートナーソリューション が、Lambda Telemetry API を使用し、システムとの統合を容易にする Lambda レイヤーを提供しています。
CloudWatch Logs Insights を最大限活用するには、構造化ログの形式でどのようなデータをログに取り込むべきかを考え、それによってアプリケーションの健全性をより適切に監視できるようにすることが重要です。
CloudWatch Logs のリテンション
デフォルトでは、Lambda 関数の標準出力に書き込まれたすべてのメッセージが Amazon CloudWatch のログストリームに保存されます。
Lambda 関数の実行ロールには、CloudWatch ログストリームを作成し、ストリームにログイベントを書き込む権限が必要です。
CloudWatch は取り込まれたデータ量とストレージ使用量に基づいて課金されることを認識しておくことが重要です。
そのため、ログの量を減らすことでコストを最小限に抑えることができます。
デフォルトでは CloudWatch ログは無期限に保持され、有効期限が切れることはありません。ログストレージのコストを削減するために、ログ保持ポリシーを設定することをお勧めします。このポリシーをすべてのロググループに適用してください。
環境ごとに異なる保持ポリシーを設定することもできます。
ログの保持期間は AWS コンソールで手動で設定できますが、一貫性とベストプラクティ スを確保するために、Infrastructure as Code (IaC) デプロイメントの一部として設定する必要があります。
以下は、Lambda 関数のログ保持期間を設定する方法を示す CloudFormation テンプレートのサンプルです:
Resources:
Function:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Runtime: python3.8
Handler: main.handler
Tracing: Active
# Explicit log group that refers to the Lambda function
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/lambda/${Function}"
# Explicit retention time
RetentionInDays: 7
この例では、Lambda 関数と対応するロググループを作成しています。
RetentionInDays プロパティは 7 日間 に設定されており、このロググループ内のログは 7 日間保持された後、自動的に削除されます。
これにより、ログストレージのコストを管理することができます。
メトリクス
オブザーバビリティのベストプラクティスガイドのこのセクションでは、以下のトピックについて詳しく説明します:
- すぐに使えるメトリクスの監視とアラート設定
- カスタムメトリクスの発行
- 埋め込みメトリクスを使用してログからメトリクスを自動生成
- CloudWatch Lambda Insights を使用してシステムレベルのメトリクスを監視
- CloudWatch アラームの作成