AWS Lambdaをよく知らないという方のために書くと、それは、イベントに応答したり自動的にコンピュートリソースを管理してくれるコンピュートサービスです。それは、サーバの準備や管理、メンテナンスを、あなた自身がする必要がないことを意味します。これが、AWS Lambdaや類似のサービスが "サーバーレス "と呼ばれる理由です。

これはちょっとした難問もたらします。サーバがないのに、どうやってサーバをNew Relicで監視するのでしょう?これには、2つの方法があります。

  1. サーバーレス監視
    • これにより、AWS Lambda関数の内部を確認することができます。詳細な継続時間、コールドスタート、例外、トレースバックなどのパフォーマンスデータを含む、すべての呼び出しを監視します。
  2. Synthetics:
    • ウェブサイトで人が生成したデータをオーガニックトラフィックとすると、ロボットが生成されたデータはSyntheticです。New Relic OneのSynthetic監視を使用すると、テストをスクリプト化し、AWS Lambda関数が外部イベントに対してどのように応答するかを監視することができます。

まず最初に、テスト用のLambda関数を作成しましょう。

デモをすぐに試したい場合は、ソースをGitHubで公開していますので、そちらをご利用ください。このアプリケーションをローカルで実行するには、Python 3.9.0とvirtualenvがアクティブであることを確認してから、ターミナルで以下のコマンドを実行します。

pip install -r requirements.txt

uvicorn main:app

serverless CLIを設定し、serverless.yamlファイルを完成させたら、次のコマンドで、AWS Lambdaにアプリケーションをデプロイします。

npm install

sls deploy --stage staging

serverless.yamlファイルを設定していない場合は、このブログの後半でその方法を紹介します。

PythonとFastAPIによるAWS Lambda関数の作成

HTTP GETリクエストに対して、JSON形式でランダムなUUID(Universally Unique Identifier)を返す FastAPI アプリケーションを作成します。

import uuid
from fastapi import FastAPI
from mangum import Mangum

app = FastAPI(title="InsideOutDemoApp")


@app.get("/uuid")
def index():
    return {"uuid": uuid.uuid4()}


handler = Mangum(app)

上のコードでは、InsideOutDemoAppというFastAPIを作成しており、このFastAPIは単一のエンドポイント /uuid を持っています。そしてこのアプリを、AWS LambdaやAPI GatewayでASGI(Asynchronous Server Gateway Interface)アプリケーションを使用するためのPythonパッケージであるMangumでラップしています。

このアプリケーションをローカルで実行し、ブラウザでアクセスすると、JSONの文字列が表示され、更新する都度、新たに生成されたUUIDが表示されます。

AWS Lambdaへデプロイ

これで、コードをAWS Lambdaにデプロイする準備が整いました。PythonコードをAWS Lambdaにデプロイするには、様々な方法があります。この例では、serverless.comのCLIツールを使用します。これはJavaScriptのアプリケーションで、npm経由でインストールできます。コマンドラインで以下を実行します。

npm install -g serverless

また、コマンドラインで以下を実行して、AWSの認証情報でserverlessを設定する必要があります。

serverless config credentials --provider aws --key <YOUR_KEY> --secret <YOUR_SECRET>

serverlessがインストールされ、AWSアカウントにアクセスできるようになったので、続いてAWS Lambda関数を設定します。

service: inside-out-demo-app

package:
  individually: true

provider:
  name: aws
  runtime: python3.8
  region: eu-west-1
  stage: ${opt:stage, "dev"}

plugins:
  - serverless-python-requirements

custom:
  pythonRequirements:
    dockerizePip: true
    layer:
      name: inside-out-app-demo-layer
      description: Inside Out Demo App
      compatibleRuntimes:
        - python3.8

functions:
  app:
    package:
      include:
        - "main.py"
      exclude:
        - "requirements.txt"
        - "package.json"
        - "package-lock.json"
        - ".serverless/**"
        - "__pycache__/**"
        - "node_modules/**"

    handler: main.handler
    environment:
      STAGE: ${self:provider.stage}
    layers:
      - { Ref: PythonRequirementsLambdaLayer }
    events:
      - http:
          method: any
          path: /uuid

上記のYAMLファイルでは、コードが実行されるAWSリージョン、使用するPythonランタイム、サポートするイベントなど、必要なものをすべて指定できていることがわかります。利用可能なオプションの詳細については、サーバーレスのドキュメントを確認してください。

また、このデプロイメントにはserverless-python-requirementsプラグインが必要であることに気付いたかもしれません。このプラグインもnpmからインストールできます。

npm install serverless-python-requirements

これで、デプロイを実行することができます。

sls deploy --stage staging

AWS Lambda関数のURLがターミナルに出力されているはずです。ブラウザでアクセスすると、ローカル版と全く同じように見えるはずです。ページを読み込むたびに変化するUUIDを持つJSON文字列です。

あなたのコードはAWS Lambda上で実行されていますが、まだインストルメントされていません。

サーバーレスモニタリングによるAWS Lambdaのモニタリング

プロセスを効率化するために、New Relic は サーバーレスフレームワークのプラグインと Setup AWS Lambda monitoring Nerdlet を用意しています。

NerdletはNew Relic Oneの「Add more data」>「Cloud and platform technologies」>「Lambda」からアクセスできます。画面に表示される確認にしたがってServerlessの監視を有効にし、以下のような回答をしてください。

 

  • Are you using the Serverless framework?
    • Yes
  • Do you have a Node or Python Lambda Function to instrument?
    • Yes
  • Do you want to deliver your function’s telemetry with our Lambda Extension, or through a Cloudwatch Logs subscription?
    • Lambda Extension

Nerdletが完成したら、新しい値をserverless.yamlにコピーしてください。完成したファイルは以下のようになります。

service: inside-out-demo-app

package:
  individually: true

provider:
  name: aws
  runtime: python3.8
  region: eu-west-1
  stage: ${opt:stage, "dev"}

plugins:
  - serverless-python-requirements
  - serverless-newrelic-lambda-layers

custom:
  pythonRequirements:
    dockerizePip: true
    layer:
      name: inside-out-app-demo-layer
      description: Inside Out Demo App
      compatibleRuntimes:
        - python3.8
  newRelic:
    accountId: <NR_ACCOUNT_ID>
    apiKey: <NR_API_KEY>
    enableExtension: true
    enableIntegration: true
    logEnabled: true

functions:
  app:
    package:
      include:
        - "main.py"
      exclude:
        - "requirements.txt"
        - "package.json"
        - "package-lock.json"
        - ".serverless/**"
        - "__pycache__/**"
        - "node_modules/**"

    handler: main.handler
    environment:
      STAGE: ${self:provider.stage}
    layers:
      - { Ref: PythonRequirementsLambdaLayer }
    events:
      - http:
          method: any
          path: /uuid

次のコマンドを実行し、New Relic Serverless framework プラグインをインストールします。

npm install serverless-newrelic-lambda-layers

そうすれば、コードを再びデプロイすることができます。

sls deploy --stage staging

デプロイの都度、関数のURLが変わることに注意してください。

AWS Lambda関数のパフォーマンスとヘルスをNew Relicで表示する

コードのデプロイが完了した後、URLにトラフィックが発生すると、New Relicに関数に関する情報が表示されるようになります。

それぞれの起動については豊富な情報が用意されており、それらはすべてNRQLでクエリー可能なので、アラートの作成など、必要に応じて利用することができます。

ここでは、実行に2ミリ秒以上かかる呼び出しが、標準偏差で3を超えた場合にトリガーされるアラートを作成しています。私のアプリケーションは高いパフォーマンスを発揮したいと思っています。

AWS Lambdaのエラー処理とスタックトレース

パフォーマンスデータだけでなく、AWS Lambda関数でエラーが発生した場合は、スタックトレースとともにNew Relicに取り込まれます。

Syntheticsでのテスト

しかし、すべてのバグやエラーが例外を発生させるわけではありません。例えば、アプリケーションが突然「自分はティーポットである」と宣言したとしても、例外は発生しませんが、それでも知っておきたいことではあるでしょう。

Syntheticモニターの最もシンプルなタイプはPingです。pingモニターは、指定されたURLに対してHEADリクエストを行い、成功したか(HTTP 200)、失敗したか(その他のHTTPステータス)を記録します。しかし、お使いのAWS Lambda関数はHEADリクエストをサポートしていません。pingモニターの「Advanced options」にある「Bypass HEAD request」オプションを変更して、代わりにGETリクエストを送信するようにします。

Syntheticテストの結果は、データエクスプローラーにも表示されるので、素早くクエリーしたり、他のデータと比較したり、アラートを作成したりすることができます。

Scripted API tests

最後に考慮すべき状況は、AWS Lambda関数やその他の監視すべきAPIが、例外を発生させず、HTTPステータス200を送信しているが、予想外のレスポンスボディを返している場合だ。200 OKのステータスを受け取り、ボディにエラーメッセージが含まれていることは、GraphQLの開発者であればよく知っていることでしょう

@app.get("/uuid")
def index():
    return {"uuid": "n0t-a-val1d-uu1d"}

ここでは、常に無効な UUID を返すように FastAPI アプリの URL ハンドラを変更しました。例外は発生せず、有効な応答であるため、FastAPI は HTTP 200 OK ステータスを返します。この例では、トレースに何の問題も見られず、Ping Syntheticが失敗を記録することもありません。

その代わり、レスポンスボディを調べて、正しいデータが返されていることを確認する必要があります。これには、スクリプト API Syntheticモニタを使用します。

var assert = require('assert');

$http.get('https://nr.execute-api.eu-west-1.amazonaws.com/staging/uuid',
  function (err, response, body) {
    assert.equal(response.statusCode, 200, 'Expected a 200 OK response');
    data = JSON.parse(body)

    assert.equal(data.uuid.length, 36, "Expected a 36 character UUID")
    assert.equal(/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(data.uuid), true, "Expected a valid UUID")
  }
);

このSynthetics用のコードでは、AWS Lambda関数にGETリクエストを発行した後、関数が有効な値を返していることを確認するためにいくつかのアサーションを行っています。

  • HTTP 200ステータスコードを返していますか?
  • レスポンスボディは、有効なJSONとして解析できていますか?
  • レスポンスボディのJSONはオブジェクトであり、UUIDという属性を持っていますか?
  • UUID属性の値は、UUID4として正しい長さですか?
  • UUID属性の値は、有効な文字で構成された正しい長さのオクテット数を含んでいますか?

これらのチェックを経て、APIが有効なUUIDを返していることを合理的に確認することができます。

まとめ

Syntheticモニターは、人間のユーザーと同じように、HTTP GETリクエストを介してAWS Lambda関数をトリガーしているので、Syntheticモニターのリクエストをサーバーレスのモニタリングイベントで確認することができます。