AWS SAMで定義されたLambda関数をNew Relicでモニタリングできるように設定してみました
初めに
New Relicを利用したLambda関数のモニタリングする方法として標準のメトリクスを利用するのみではなくインテグレーションを利用することでより詳細な情報を取得することができます。
New Relicのインテグレーションの導入はいくつかあるようであればZipタイプの関数であればLambda Layerを直接Lambda側で指定する方法やnewrelic-lambda
コマンドを利用して導入する方法が手軽そうです。
自分の場合は普段SAMを利用していますのでこちらを利用する形でLambdaをモニタリングできるように設定してみます。
アカウントリンク
まずはNew Relic側でAWSアカウント(正確にはその中のLambda?)をリンクさせます。
こちらはアカウント全体の設定となるためSAMではなくコマンドを利用して個別に設定します。
ローカルからnewrelic-lambda
コマンドを実行しますがそのためにAPI Keyが必要なのでまずはこちらを発行します。
発行はNew Relicの画面左下の自身のユーザ名の部分のAPI Keyを開いて「Create a key」をクリックします。
API Keyにはいくつか種類がありますがUser
で問題ないようです。
発行後はAPI Keyの一覧画面に戻りますので先ほど発行したキーの一番右の「...」部分を開いて「Copy Key」からAPI Keyを取得し控えておきます。
その後newrelic-lambda
を利用して初期設定を行います。
newrelic-lambda
をpipでインストールしnewrelic-lambda integrations install
を実行します。
% pip install newrelic-lambda ... % newrelic-lambda integrations install --nr-account-id {{your-new-relic-account-id}} --nr-api-key {{your-api-key}} Validating New Relic credentials Retrieving integration license key Creating the AWS role for the New Relic AWS Lambda Integration Waiting for stack creation to complete... ✔️ Done ✔️ Created role [NewRelicLambdaIntegrationRole_xxxxxxxx] in AWS account. Linking New Relic account to AWS account ✔️ Cloud integrations account [New Relic AWS Integration - xxxxxxxx] was created in New Relic account [xxxxxxxx] with IAM role [arn:aws:iam::xxxxxxxx:role/NewRelicLambdaIntegrationRole_xxxxxxxx]. Enabling Lambda integration on the link between New Relic and AWS ✔️ Integration [id=xxxxxx, name=Lambda] has been enabled in Cloud integrations account [New Relic AWS Integration - xxxxxxxx] of New Relic account [xxxxxxxx]. Creating the managed secret for the New Relic License Key Setting up NewRelicLicenseKeySecret stack in region: ap-northeast-1 Creating change set: NewRelicLicenseKeySecret-CREATE-xxxxxx Waiting for change set creation to complete, this may take a minute... Waiting for change set to finish execution. This may take a minute... ✔️ Done Creating newrelic-log-ingestion Lambda function in AWS account Setting up CloudFormation Stack NewRelicLogIngestion in region: ap-northeast-1 Fetching new CloudFormation template url Creating change set: NewRelicLogIngestion-CREATE-xxxxxx Waiting for change set creation to complete, this may take a minute... Waiting for change set to finish execution. This may take a minute... ✔️ Done ✨ Install Complete ✨
完了すると3つのスタックが作成されます。ざっと見連携用?のLambda関数やIAMロール、New RelicのAPI Key含まれるシークレットが作成されるようです。
なお最初API Key発行の際にUser
ではなくINGEST - LICENSE
で発行してしまったのですがこの場合はコマンド実行時にエラーとなりました。
% newrelic-lambda integrations install --nr-account-id xxxxx --nr-api-key xxxxx Validating New Relic credentials Retrieving integration license key Usage: newrelic-lambda integrations install [OPTIONS] Try 'newrelic-lambda integrations install --help' for help. Error: Invalid value for New Relic Account ID: Could not retrieve license key from New Relic. Check that your New Relic Account ID is valid and try again.
設定が完了すると「Infrastructure > AWS」の部分で連携されていることが確認できます。
また「Serverless Functions」を見るとアカウント上のLambda関数の一覧の一部が確認できます。
SAM追加設定
ベーステンプレート
以前以下の記事で利用したテンプレートをします。
本体のHelloWorldFunction
とAuthorizer用のAuthFunction
があるので両方に対して追加します。
テンプレートを抜粋します。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Globals: Function: Timeout: 60 MemorySize: 128 Resources: Api: Type: AWS::Serverless::Api Properties: StageName: dev MethodSettings: - ResourcePath: / HttpMethod: GET Auth: DefaultAuthorizer: LambdaAuthorizer Authorizers: LambdaAuthorizer: FunctionArn: !GetAtt AuthFunction.Arn FunctionPayloadType: TOKEN Identity: Headers: - Authorization HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.9 Architectures: - arm64 Events: HelloWorld: Type: Api Properties: Path: /hello Method: get RestApiId: !Ref Api AuthFunction: Type: AWS::Serverless::Function Properties: CodeUri: auth/ Handler: app.lambda_handler Runtime: python3.9 Architectures: - arm64
追加設定
New Relicに関する設定はGlobalsセクションに書いておくと全てのFunctionリソースに一括で適用されるので、テンプレート内の関数を複数監視する場合はこちらに記載してしまうのが良さそうです。
Pythonのバージョンや記載位置の変更等はありますがハイライト部分が今回の主な追加要素となります。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Parameters: # New RelicのアカウントID NewRelicAccountId: Type: String # 導入したいNew RelicのLambda LayerのARN NewRelicLayerArn: Type: String # 先のCFnでNEW_RELIC_LICENSE_KEYというシークレットが生成されているのでそのARN NewRelicSecretsArn: Type: String Globals: Function: Timeout: 60 MemorySize: 128 Architectures: - arm64 Runtime: python3.12 Handler: newrelic_lambda_wrapper.handler Environment: Variables: NEW_RELIC_LAMBDA_HANDLER: app.lambda_handler NEW_RELIC_ACCOUNT_ID: !Ref NewRelicAccountId NEW_RELIC_EXTENSION_SEND_FUNCTION_LOGS: true Layers: - !Ref NewRelicLayerArn # これはできない # Policies: # - Version: "2012-10-17" # Statement: # - Effect: Allow # Action: secretsmanager:GetSecretValue # Resource: !Ref NewRelicSecretsArn Resources: Api: Type: AWS::Serverless::Api Properties: StageName: dev MethodSettings: - ResourcePath: / HttpMethod: GET Auth: DefaultAuthorizer: LambdaAuthorizer Authorizers: LambdaAuthorizer: FunctionArn: !GetAtt AuthFunction.Arn FunctionPayloadType: TOKEN Identity: Headers: - Authorization HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Events: HelloWorld: Type: Api Properties: Path: /hello Method: get RestApiId: !Ref Api Policies: - Version: "2012-10-17" Statement: - Effect: Allow Action: secretsmanager:GetSecretValue Resource: !Ref NewRelicSecretsArn AuthFunction: Type: AWS::Serverless::Function Properties: CodeUri: auth/ Policies: - Version: "2012-10-17" Statement: - Effect: Allow Action: secretsmanager:GetSecretValue Resource: !Ref NewRelicSecretsArn
上記のテンプレートができれば通常通りのデプロイでOKです。
なおPoliciesもGlobalsセクションに指定できれば新しい関数追加時に開発者が意識せずとも自動的にに必要な権限やレイヤーが追加されて良さそうでしたが、残念ながらSAMの仕様として権限の最小化を優先し意図しない権限が当てられないようにGlobalsセクションにPoliciesは指定できないように制限されていました。
Lambda LayerによるNew Relicの連携に利用するAPI Keyは特に指定がなければNEW_RELIC_LICENSE_KEY
という名称のシークレットを自動で読み込みこみますが、このシークレットは先にnewrelic-lambda
を実行した時に合わせて作成されているので読み込み権限だけ与えればOKです。
余談ですがうまくログが飛ばずデバッグしていた時にCloudTrailを眺めていたところParameter Store->Secrets Managerの順に取得しようとする処理が走っていたので、ドキュメント上はSecrets Managerを利用するような記載がありますが適切に設置すればそちらでも良さそうです。
"userAgent": "aws-sdk-go/1.44.288 (go1.20.14; linux; arm64)", "errorCode": "AccessDenied", "errorMessage": "User: arn:aws:sts::xxxx:assumed-role/sam-app-lambda-auth-AuthFunctionRole-xxxx/sam-app-lambda-auth-AuthFunction-xxxxx is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:ap-northeast-1:xxxxx:parameter/NEW_RELIC_LICENSE_KEY because no identity-based policy allows the ssm:GetParameter action",
少し話が飛びましたが実際にデプロイされたLambda関数を確認すると指定したレイヤーが追加されています。
またnewrelic-lambda functions list
コマンドを実行することでもアカウント内のLambda関数でインテグレーションの適用状態を確認できます。
% newrelic-lambda functions list Function Name Runtime Installed --------------------------------------------------------------- ---------- ----------- dmarc-rua-report-searcher-RuaConverterFunction-xxxxxxxxxxxx python3.12 No sam-app-lambda-auth-AuthFunction-xxxxxxxxxxxx python3.12 Yes ... sam-app-lambda-auth-HelloWorldFunction-xxxxxxxxxxxx python3.12 Yes ... newrelic-log-ingestion-xxxxxxxxxxxx python3.11 No
実行
curlを利用してAPI Gateway経由で何度かLambda関数を実行してみます。
% curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello -H "Authorization: Succes" {"Message":"User is not authorized to access this resource with an explicit deny"}% % curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello -H "Authorization: Success" {"message": "hello world"}%
MetricsやDistributed Tracingを見てみると情報が取れていることを確認できます。
(本当はもう少し中身があればDistributed Tracingをもう少し詳しく見ても良かったのですが実行したLambda関数内容があまりにも無さすぎてみても...という感じでした)
ログについては時間を置いても来ない...と思って別途print文を埋め込んで再度実行したところ出力が確認できました。
どうやらLambdaがシステム的に出力するようなSTART RequestId:xxxxx
のような部分のログは出力はとらないようでアプリ側で出力しているログのみを取るようです。
今回はLambda関数の環境変数にNEW_RELIC_EXTENSION_SEND_FUNCTION_LOGS=ture
を設定することによりCloudWatch Logsを利用せずLambda上のNew Relicのインテグレーション側で収集された情報を送信するような仕組みを利用しましたが、その仕組みの関係上アプリレイヤーではないシステムログ的な部分は取れないのかもしれません(別途CloudWatch Logsのデータを読む形にすることで対応可能?)。
https://docs.newrelic.com/jp/docs/serverless-function-monitoring/aws-lambda-monitoring/enable-lambda-monitoring/instrument-example/
例題をテストしていると、テレメトリーがすぐに送信されないことがあることに気づくかもしれません。AWS Lambdaのライフサイクルでは、エージェントとLambda Extensionの実行に一定の制約があります。さらに、貴重なプラットフォームの遠隔測定は、呼び出しが完了した後にのみ利用できます。New Relic Extension は、一定期間テレメトリをバッファリングし、次の呼び出し時(またはシャットダウン時)に一括して New Relic に配信することで、全体のパフォーマンスとタイムリーなテレメトリ配信の必要性とのバランスを取っています。
また仕組み的に即時性なくは内容で反映まで多少ラグがあるようです。
ログがNew Relicに到達した後にCloudWatch Logs側を見ていると実行から6分ほど時間を置いた後にログを送信しているような処理が走ってるのでこのタイミングかもしれません。
終わりに
AWS SAMで構築されたLambda関数に良い感じにNew Relicによるモニタリングを導入してみました。
SAMで構築されている環境の場合はGlobalsセクションでほとんどの設定をまとめて定義することができるため、うまく活用すれば将来的なバージョンの更新(ランタイム・レイヤーいずれも)があった場合も特定の関数のみ更新漏れといったことも発生しづらいです。 (少し回り道をすればできそうな気もしますが)良くも悪くもセキュリティ確保のためにSAMの仕組み上ポリシーの一括追加ができず完全に一括で管理できないのがやや痛いところではあります。
別のサードパーティ製の関数が含まれており一部関数を例外としたい場合はIgnoreGLobalsポリシーを利用することで適用除外も可能ですので選択肢としてみてください。
補足: もう少し意味のある分散トレーシングを見る
今回実行された関数は単純に値比較出力のみで分散トレーシング(Distributed Trasing)はほとんど意味のないようなものとなってしまったのであまり詳細に書いていませんが、外部呼び出しのあるような関数を見るとこのような感じになります(私が個人的に情報収集用にデイリーで流しているLambda関数です)。
呼び出しが外部サービスのためシンプルなつながりではありますが外部サービスの呼び出しにかかっている時間周りが別途ログ埋め込みなしで取れるので良さそうです。
(自前の関数で呼び出しあっていればもっと色々見れそうではありますが)