AWS Lambda関数のオブザーバビリティ(可観測性)を得られるNew Relicサーバーレスモニタリングfor AWS LambdaはAWS Lambda拡張に対応したことで使いやすくなったともに、テレメトリーデータの収集方法についても改善することができました。その詳細について「Under the Hood of New Relic’s Lambda Extension」の抄訳にてお届けします。あわせて「Lambda Extensionに対応した、New Relic サーバーレスモニタリングを試してみた」もご一読ください。

前回のブログ記事「Ingest AWS Lambda Logs Directly to Reduce Cloud Spend」でも触れましたが、New RelicがAWS Lambda Extensionsと統合することで、Amazon CloudWatchとは別にAWS Lambdaのログストリームに直接アクセスできるようになります。この統合により、エンジニアとしてはオブザーバビリティ(可観測性)を損なうことなくクラウドの支出を管理・最適化することができ、Lambda関数のログやテレメトリーデータをNew Relic OneのTelemetry Data Platformに直接取り込むだけですむというメリットがあります。

この記事では、New Relic Lambda拡張が、AWS Lambda 関数から New Relic One へテレメトリーデータを収集、強化、転送するための軽量ツールとしてどのように機能するかを探ります。

基本的なことから始める

まずは、Lambda環境の基礎知識と、ステートレスでエフェメラルな関数からテレメトリーデータを取り出すための課題について説明します。Lambdaのテレメトリは、以下のような様々なイベントで構成されています。

  • 呼び出しイベント(AwsLambdaInvocation)
  • エラーイベント(AwsLambdaInvocationError)
  • 分散トレース(Spanイベント)
  • 開発者が作成する任意のカスタムイベント

このデータはまず、計測したい関数に統合された New Relic One のコードによって収集されます。簡単にするために、これを言語エージェントと呼びますが、これは Node、Python、Go にのみ適用されます。Javaや.NETでは、OpenTracing規格に基づいたコードを使用して遠隔測定を行います。

Lambda実行環境の各関数は、カスタムの実行環境で動作するコンテナイメージです。これらのコンテナのベースイメージは、Amazon Linux(通常はバージョン2)です。各コンテナイメージには、ハンドラのライフサイクルを実装したbootstrapという実行ファイルが含まれています。

起動時、Lambdaサービスはbootstrapを実行し、ローカルのHTTPサーバーを利用可能にします。このサーバーは、/nextと呼ばれるAPIへのブロッキングされたlong pollingなHTTP GETリクエストに応答してイベントペイロードを返します。関数が扱うべきイベントがすぐに利用できない場合、/nextへの呼び出しはブロックされます。(long-pollリクエストは、HTTPクライアントとサーバーの相互作用を同期させ、サーバーがクライアントにイベントを送信することを目的としたブロッキングリクエストです)

ここからが面白いのですが、コンテナ内で実行されているプロセスは、新しいイベントが到着するまで、どのプロセスもスケジュールされません。また、イベントを待っている間、コンテナはいつ終了されるかわかりません。したがって、関数は必然的にステートレスとなります。

この状況はLambda Extension APIでは多少異なり、呼び出しイベントの通知を行うだけでなく、SHUTDOWNなどのライフサイクルイベントを受け取り、ログイベントも登録できます。これにより、関数は耐久性のない状態を維持したままJavaのファイナライザのようにファイナライザ処理を実行することができます。ただし、タイミングは予測できないので、時間に敏感なロジックの実装には使えないAPIです。

この基本的なライフサイクルAPIに加えて、Lambdaは、異なる言語でハンドラをホストできるbootstrap実行ファイルを事前構築した実装である、いくつかのランタイムを提供しています。bootstrapはイベント処理をこれらのハンドラに委ねます。

テレメトリーをコンテナから取り出す

Lambdaの実行環境はステートレスなので、テレメトリーを収集してすぐに実行コンテナから取り出す必要があります。AWSには、AWS Identity and Access Management (IAM)というリッチで柔軟なパーミッションシステムがあり、各関数は他のサービスとのやりとりを管理する実行ロールを適用しています。しかし、「リッチで柔軟」という言葉は、常に複雑さの代名詞です。

複雑なパーミッションを必要とせずにコンテナから遠隔測定を行うためには、3つの選択肢があります。

  • 各呼び出しの最後にHTTPリクエストを行う(リクエストの応答がくるまでブロックしなければならない)
  • 標準出力にテレメトリを出力する。デフォルトでは、stdoutstderrのファイルディスクリプターに書き込まれたものはすべてCloudWatch Logsに送られます。
  • 関数にステートを追加するためにエクステンションを使用します。テレメトリーは収集され、バッファに保存され、一括して送信されます。

当初、New Relicはstdout/CloudWatchオプションを使用して、CloudWatchロググループを設定し、フィルタリングされたログバッチを別のLambda関数など他の場所に送信していました。私たちは、ログを解析し、テレメトリを抽出し、それをすべてNew Relic Oneに送信するLambda関数を提供しました。しかし、この方法にはいくつかのデメリットがありました。

  • AWS Serverless Application Repositoryで公開されているLambda関数を使う必要がありました。
  • ログサブスクリプションのフィルターが限られていた。
  • CloudWatchインジェストによりコストが増大しました。
  • 可視化までの時間を短くしたいお客様にとっては、レイテンシーが問題となる可能性がありました。

エレガントなソリューションの紹介

New Relic Lambda 拡張は、テレメトリーデータの収集、強化、および転送に関するこれらの課題を克服します。この拡張機能は、関数のコンテナ内で実行され、テレメトリーをバッファリングし、定期的に送信します。テレメトリーを複数回の関数の呼び出しにわたって蓄積してNew Relic Oneに送信するようにした代わりに、関数の実行をブロックしないようにすみました。エージェントは言語に依存しないシンプルな IPC メカニズムを使用してテレメトリーを拡張機能 (名前付きパイプ) に送信するので、すべての言語エージェントに対応するために必要な拡張機能のコードベースは 1 つで済みます。

このアプローチの主な欠点は、Lambdaの実行環境におけるコンテナのライフサイクルです。数回の呼び出しに相当するテレメトリーのバッファを蓄積しても、関数が呼び出しの応答を停止してしまう可能性があるのです。この蓄積されたバッファを送信する機会は、その関数が最終的に呼び出されるか、シャットダウンされるまでありません。

いずれの場合も、New Relic One に送信されるテレメトリには、顧客に代わって自分を識別し、認証するための New Relic のライセンスキーが必要です。前回の記事で紹介したログインジェストLambdaの場合、これは簡単です。リージョンごとに1つしかないので、環境変数にライセンスキーを持たせることは簡単です。テレメトリー拡張については、ライセンスキーを管理するためのより良いソリューションが必要であることは明らかでした。その答えは、AWSの秘密管理サービスです。このサービスを New Relic Lambda 拡張に統合し、ライセンスキー取得のデフォルトの方法としました。その結果、セットアップ時(リージョン毎)に、ライセンスキーのシークレットを作成することになります。その後、各関数の実行ロールにそのシークレットを読み取る権限を含める必要があります。シークレットは関数のコールドスタート時にのみ読み込まれるため、AWS Secrets ManagerのAPIレイテンシーに関する懸念を回避し、AWSのコストを最小限に抑えることができます。

テレメトリーを装飾する

テレメトリデータを New Relic のインジェストパイプラインに取り込むための課題を解決したところで、メタデータの装飾について説明します。

よく見ると、AwsLambdaInvocation イベントには、エージェントが生成したときよりもクエリを実行したときの方が、より多くのフィールドが表示されています。これはなぜでしょうか?それはメタデータの装飾の結果です。その仕組みは以下の通りです。

New Relic One は、AWS アカウント内のすべての Lambda 関数のインベントリを維持しています (これがアカウントペアリングのステップが必要な理由です)。単純な識別情報に加えて、ランタイム、タグ、最大メモリ、タイムアウトなど、Lambdaの設定情報を収集します。これらの情報はNew Relic Oneに保存されており、対象の関数がインストルメント化しているかどうかに関わらずNew Relic ExplorerやEntity Explorerで利用できます。

このメタデータのほとんどは関数自体からは参照できないため、書き込み時の呼び出しイベントにこのメタデータを追加することで別途収集し、現在の状態ではなく、呼び出された時の関数の状態の記録を保持できるようにしています。

インジェストパイプラインの仕事の一部は、最新の収集された関数メタデータで呼び出しイベントを「装飾」することなので、カスタムクエリ、ダッシュボード、またはNRQLアラートで、関数メタデータを使ってイベントデータをスライス&ダイシングすることができます。

自社のサーバーレスアプリケーションを理解する

Lambdaからのログとテレメトリーの両方をNew Relic Oneに直接送信することで、サーバーレスアプリケーションの動作やパフォーマンスを観察し、理解することができます。CloudWatchをバイパスするNew Relic Lambda拡張を使用することで、レイテンシーを最小限に抑え、クラウド費用を最適化しながらこれを行うことができます。

New Relic Lambda エクステンションの詳細については、Gitリポジトリーにてご確認ください(contributeも歓迎です)。