静的WebサイトからLambdaを実行する様子をNew Relicの分散トレーシングで計測してみた

2020.05.15

最近だと、軽量なサーバーサイドの処理はAWS Lambdaなどのサーバーレスを利用するのに加えて、フロントエンド側もSPAで構成し、そこから直接API Gatewayなどを通してラムダ関数を呼び出す構成にすることもあるのではないでしょうか。Observabilityと言う観点で言うと、フロントエンド側の描画パフォーマンス、クライアント側からみた呼び出しパフォーマンス、サーバーレス関数そのもののパフォーマンス、と様々な要素を計測する必要があります。New Relicではこれらの要素を全て計測できる上に、ブラウザ起点の分散トレーシングが利用できます。しかし、この構成は多くの場合SPAをホストするドメインとサーバーレスを呼び出すドメインが異なるため、CORSの設定が必要となり手順が複雑になりがちです。そこで、今回は、S3にホストした静的なWebサイトからAPI Gatewayを経由してラムダを呼び出すサンプルアプリを作り、それをNew Relic BrowserNew Relic Serverless Monitoring for AWS Lambdaで計測し、分散トレーシングの設定まで行う手順を紹介したいと思います。

なお、API Gatewayとラムダ関数のCORS設定については、こちらの記事が非常に参考になった(半分くらいはこの記事の手順そのまま)ので、合わせて読むことをおすすめします。

API Gateway の Lambda プロキシ統合のCORS対応をまとめてみる

Serverless Monitoring for AWS Lambdaの準備をする

New Relic Serverless Monitoring for AWS Lambda のインストール手順についてはここにまとまっています。

https://docs.newrelic.co.jp/docs/serverless-function-monitoring/aws-lambda-monitoring/get-started/enable-new-relic-monitoring-aws-lambda#connect-aws このドキュメントを参考にして、ステップ1とステップ2にある、newrelic-lambda-cliのインストールとAWSへの接続を完了させてください。

Serverless Frameworkでラムダ関数を作成し展開する

次に監視対象となるラムダ関数を作成します。Serverless Frameworkを使うとラムダ関数のテンプレートを作れる上に、プラグインを使ってS3へのアップロードもできます。さらに、New Relic Lambda Monitoringもインストールできます。今回はServerless Frameworkを使って作業を進めていきます。

$ sls -v
Framework Core: 1.69.0
Plugin: 3.6.10
SDK: 2.3.0
Components: 2.30.9

今回はPythonのテンプレートをそのまま使います。

$ sls create -t aws-python3 -p hello-python-http-layer

生成されたserverless.ymlを次のように編集します。

service: hello-python-http-layer

provider:
  name: aws
  runtime: python3.8
  region: ap-northeast-1

functions:
  hello:
    handler: handler.hello
    events:
      - http: 
          path: greet
          method: get
          cors:
            origin: "*"
            headers: 
              - newrelic
              - Content-Type
              - X-Amz-Date
              - Authorization
              - X-Api-Key
              - X-Amz-Security-Token
              - X-Amz-User-Agent
    environment:
      NEW_RELIC_DISTRIBUTED_TRACING_ENABLED: true
      NEW_RELIC_LOG: stdout
      NEW_RELIC_LOG_LEVEL: debug
      NEW_RELIC_ACCOUNT_ID: <アカウントID>
      NEW_RELIC_TRUSTED_ACCOUNT_KEY: <親アカウントID>
      
custom:
  webSiteName: s3-spa-site
  s3Sync:
    - bucketName: ${self:custom.webSiteName}
      localDir: static
  newRelic:
      accountId: <アカウントID>

  resources:
  Resources:
    StaticSite:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:custom.webSiteName}
        AccessControl: PublicRead
        WebsiteConfiguration:
          IndexDocument: index.html
    StaticSiteS3BucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket:
          Ref: StaticSite
        PolicyDocument:
          Statement:
            - Sid: PublicReadGetObject
              Effect: Allow
              Principal: "*"
              Action:
              - s3:GetObject
              Resource:
                Fn::Join: ["", ["arn:aws:s3:::",{"Ref": "StaticSite"},"/*"]]
plugins:
  - serverless-newrelic-lambda-layers
  - serverless-s3-sync

funtcions>events>http の項目はラムダをAPI Gatewayで呼び出せるようにする設定です。重要なのは、http>cors>headersでここでCORSの設定を行います。originの指定(ここでは*で任意のオリジンを許可)と、許容するヘッダーを追加します。ここで、New RelicのBrowserからの分散トレーシングを利用する場合は、newrelicと言うキーでヘッダーを追加します。そのため許可するヘッダーに追加しておく必要があります。環境変数は、ラムダでの分散トレーシングを許可するために必要なものになります。親アカウントIDは、利用しているNew Relicアカウントが子アカウント場合は親アカウントのIDを、親アカウントの場合は上と同じ自分のアカウントIDを指定します。 customとresourcesおよびpluginsの項目は、S3へのアップロードとNew Relic Lambda Monitoringの設定になります。なお、この手順は先ほどのNew Relic Serverless Monitoringのステップ3のオプション#1の手順になります。オプション#1と#2の手順を使う場合、ステップ4のログ転送の設定は自動でやってくれるため行う必要がありません。

次にラムダ関数本体である、hander.pyを編集します。

import json

def hello(event, context):
    body = {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event
    }

    response = {
        "statusCode": 200,
        "headers": {
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Headers": "newrelic, Content-Type"
        },
        "body": json.dumps(body)
    }

    return response

デフォルトからの変更項目はheadersを追加しているところです。これもCORSの設定で、別ドメインでホストされているHTMLから呼び出すためと、New Relicの分散トレーシングのヘッダーを受け付けるために設定しています。

次にserverless.ymlなどがあるディレクトリにstaticと言うディレクトリを作り、その中にindex.htmlというファイルを作ります。このファイルはいったんデプロイして、API Gatewayのドメイン名がわからないと編集できないのでいったん空のHTMLファイルにしておきます。

ラムダ関数を展開して、New Relic Serverless Monitoringが動いていることを確認する

ここでいったん、デプロイします。

$ sls deploy -v

すると、次のようにAPI GatewayのURLが表示され、ブラウザやcurlコマンドなのでアクセスすると正常にレスポンスが返ってくるはずです。

また、一度実行すると、New Relic Lambda Monitoringでの計測結果をNew Relicから確認できるようになります。New Relic OneのEntity Explorerにまずアクセスします。(AWS Lambda setupは、setupと言う名前の通りセットアップ手順を表すページを表示するだけです)

左側のペインでLambda functionsを選ぶと、ラムダ関数の一覧が表示されます。ここで表示されているのは、Lambda Monitoringの最初の手順でセットアップしたAWS Integrationが監視しているラムダ関数の一覧です。

その中で、INSTRUMENTEDがYesになっているのがLambda Monitoringが有効になっている関数です。今回作った「hello-python-http-layer-dev-hello」がYesとなっていることを確認しましょう。そして、EVENT SOURCEのアイコンがAPI Gatewayになっているように、どこから呼び出されたかもわかります。関数をクリックするとより詳細な情報が確認できます。Yesになっていない場合は、ここまでの手順のどこかで間違っている可能性があるので確認してみてください。

API GatewayでCORS設定を確認する

さてここで、CORSの設定がAPI Gatewayに反映されているから確認しましょう。AWS ConsoleからAPI Gatewayを開きます。そして作られたAPI Gatewayのoptionメソッドのテストを行ってみます。ここで、レスポンスヘッダーにnewrelicが含まれていることを確認してください。

ちなみに、API GatewayでCORSの有効化というメニューを開くと次の画面が出てきますが、この入力欄にnewrelicという文字が出てこないようです。しかし、テストで動いていれば問題ありません。(この入力欄は現在の設定ではなく、入力する際のデフォルト値が入っている模様)

New Relic Browserアプリを作成し、index.htmlを完成させる

無事CORSのテストができたところで、index.htmlの中身を書くために、まずNew Relic Browserの初期設定を行います。New Relic BrowserはサーバーサイドのアプリがHTMLを生成する場合はAPM Agentを利用して有効化することもできますが、SPAのような用途を想定してコードをコピーペーストして有効化することもできます。New Relic Browserの画面で「add more」をクリックし、新規追加画面を開きます。

「Copy/Paste Javascript code」を選び、Pro+SPA、No (Name your standalone app)を選びます。最後にNew Relic Browserで表示させたいアプリ名を入力しEnableをクリックします。するとJavaScriptのコードが表示されますが、いったん無視して、New Relic Browserのアプリ一覧に戻り、今作ったアプリを選択します。そして左側のメニューで「Applciation Settings」を選びます。

この画面でまず「Distributed Tracing」をONにします。そして、その下のCORSの設定もONにし、API GatewayのURL(https//から始まってxxx.amazonaws.comとドメイン名の終わりまで)をドメインリストに追加して、「Add origin」をクリックします。

このとき、Save application settingsボタンを押すまで右上のJavaScriptのコードは表示されません。Saveすると表示されます。このように、Copy/Pasteメソッドの場合、これらの設定を変更すると必ずJavaScriptのコードをアップデートする必要があります。そして、いったんこのJavaScriptのコードをコピーしておきます。

ようやく、index.htmlを作成します。今回はボタン一つと、ボタンをクリックするとAPI Gatewayを2回呼び出すだけのコードです。TODOの場所に先ほどコピーしたNew Relic Browserのコードを貼り付け、URLには実際に配置したAPI Gatewayのエンドポイントを貼り付けます。

<html>
    <head>
      <!-- TODO ここにNew Relic Browserのscriptタグのコードを貼り付ける -->                        
      <meta charset="utf-8" />
      <title>DemoForm</title> 
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
      <script>
        $(function() {
          var URL = 'https://xxxx.execute-api.ap-northeast-1.amazonaws.com/dev/greet';
          $('#submit').click(function() {
            myInteraction = newrelic.interaction()
            $.ajax({
              method: 'GET',
              url: URL
            })
              .done(function(msg) {
                console.log(msg);
              })
              .fail(function(msg) {
                console.log(msg);
              })
              .always(function() {
                $.ajax({
                  method: 'GET',
                  url: URL
                })
                  .done(function(msg) {
                    console.log(msg);
                  })
                  .fail(function(msg) {
                    console.log(msg);
                  })
                  .always(function() {
                    myInteraction.save()
                    alert('complete');
                  });
              });
          });
        });
      </script>            
    </head>
    <body>
        <input type="button" id="submit" value="Go" />
    </body>
</html>

注意点として、必ずmyInteraction = newrelic.interaction()としているように、BrowserのInteractionをカスタムで作成して、その中でAJAXによる呼び出しを行う必要があります。これはNew Relic Browserの分散トレーシングは必ずInteractionの中で呼ばれるAJAX通信を起点とするためです。

完成したHTMLにアクセスして、分散トレーシングを確認する

再度、sls deploy -vでデプロイするとこのindex.htmlにアクセスできるはずです。それではボタンを押してみましょう。今回通信エラーになってもcompleteと出るので、開発者ツールで正しく通信できているか確認してください。正しく呼び出されていることがわかったら、New Relic Lambdaのページに戻って、Distributed Tracingを開きます。

このようにEntitiesが2になっているTraceが表示されていれば正しく動いています。Browser側のSpanとラムダ処理のSpanで2つです。1と表示されているTraceはBrowser側の分散トレーシングがまだ有効になっていない時のものです。このTraceの一つをクリックすると次のように詳細が確認できます。

先ほど、Browserの分散トレーシングはInteractionと紐づくと言いましたが、このように記録されているため、ラムダ側の処理がBrowser側のどの処理がきっかけで実行されたかもわかります。

一通り作業が終わったら、次のコマンドでAWS上のリソースは削除できます。今回、アクセス制御などかけていないので公開したままにすると無闇にアクセスされてラムダの課金が発生する恐れがあります。

$ sls remove

ここまでが一通りの手順となります。New Relic BrowserとNew Relic Serverless Monitoring for Lambda、そして両者の分散トレーシングを一度体験してみてください。