【New Relic】CloudWatch GetMetricData イベントを可視化してみる

記事タイトルとURLをコピーする

はじめに

こんにちは、マネージドサービス部の福田です。

Amazon CloudWatchのGetMetricData APIがAWS CloudTrailのイベントロギングをサポートするようになりましたので New Relicで可視化してみたというブログになります。

Amazon CloudWatch GetMetricData APIの新機能について

Amazon CloudWatchのGetMetricData APIがAWS CloudTrailのデータイベントロギングをサポートされ CloudTrailのデータイベントとして記録されるようになりました

aws.amazon.com

メリット

以下のようなメリットが挙げられます

セキュリティの強化
GetMetricData APIの呼び出しがCloudTrailに記録されることで、誰がいつどのメトリクスデータを取得したかを詳細に確認することができます。
これにより、不正アクセスの検出やセキュリティインシデントの調査が容易になります。

コスト管理の改善
GetMetricData APIの使用頻度が増加すると、関連するコストも増加する可能性があります。
CloudTrailのデータイベントロギングを活用することで、APIの使用状況を詳細に把握し、コスト管理を効率化することができます。
特に、料金が跳ね上がった場合に迅速に原因解明に対応することが可能になります。

本ブログで得られること

  • CloudTrail によるAmazon CloudWatch の GetMetricData イベント取得方法
  • Lambdaを用いたNew Relic Logsへのログ連携方法
  • NewRelic に送られたAmazon CloudWatch の GetMetricData イベントのダッシュボードの例

CloudTrail のイベントについて

CloudWatch GetMetricDataは標準のイベントではなくデータイベントとして記録されます。

  • 標準イベント
    AWSリソースの管理操作をデフォルトで記録し、追加料金は発生しません。
  • データイベント
    データ操作を記録し、オプトインが必要で追加料金が発生します。
    GetMetricData APIの呼び出しはデータイベントとして記録され、1,000メトリクスごとに0.01ドルの費用がかかります。

repost.aws

設定内容

構成図

CloudTrailのデータイベント取得方法
(GetMetricDataを収集する)

  • AWSのマネコンからCloudTrail証跡に移動
  • データイベントにチェックつける
  • データイベントを以下のように設定
    • データイベントタイプ:CloudWatch metric
    • ログセレクターテンプレート:カスタム
    • セレクター名:任意
    • 高度なイベントセレクター
      • フィールド:eventName
      • オペレーター:equals
      • 値:GetMetricData

CloudWatch Logsに送られた
CloudTrailのデータイベントを確認

以下のLogs Insights クエリを実行するとGetMetricDataデータがログとして記録されていることが確認できました。

filter eventName='GetMetricData'
| fields @timestamp, @message, userIdentity.sessionContext.sessionIssuer.userName, awsRegion, eventName, userIdentity.sessionContext.sessionIssuer.accountId
| parse @message /"namespace":"(?<namespace>[^"]+)"/
| display @timestamp, userIdentity.sessionContext.sessionIssuer.userName, awsRegion, eventName, userIdentity.sessionContext.sessionIssuer.accountId, namespace

ログ送信用Lambdaの設定

New Relicの設定はこちらになります。 docs.newrelic.com

サブスクリプションフィルターの設定

Lambdaのトリガー設定もしくは CloudWatchLogsのサブスクリプションフィルターから作成できると思いますが今回はLambdaコンソール画面のトリガーから設定をしていきます。

  • AWSのマネコンからLambdaに移動
  • トリガーを追加をクリック
  • 以下のように設定
    • ソース:CloudWatchLogs
    • ロググループ:CloudTrailのデータイベントが送られるCloudWatchロググループを指定
    • フィルター名:任意
    • フィルターパターン - オプション:{ $.eventName = "GetMetricData" }
      • .eventName = GetMetricDataに該当するログのみをトリガー対象とする

New Relicに送られたCloudTrailのデータイベントを確認

{
  "aws.logGroup": "aws-cloudtrail-logs-xxxxx-xxx",
  "aws.logStream": "xxxxx_CloudTrail_ap-northeast-1_4",
  "awsRegion": "ap-northeast-1",
  "eventCategory": "Data",
  "eventID": "xxxxxf",
  "eventName": "GetMetricData",
  "eventSource": "monitoring.amazonaws.com",
  "eventTime": "2024-06-09T13:08:30Z",
  "eventVersion": "1.09",
  "managementEvent": false,
  "newrelic.source": "api.logs",
  "plugin.type": "lambda",
  "plugin.version": "1.0.3",
  "readOnly": true,
  "recipientAccountId": "xxxxx",
  "requestID": "xxx",
  "requestParameters.endTime": "Jun 9, 2024, 1:03:00 PM",
  "requestParameters.metricDataQueries": "[{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Invocations\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"SampleCount\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Invocations\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Average\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Invocations\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Sum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Invocations\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Minimum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Invocations\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Maximum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Errors\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"SampleCount\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Errors\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Average\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Errors\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Sum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Errors\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Minimum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Errors\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Maximum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Duration\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"SampleCount\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Duration\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Average\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Duration\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Sum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Duration\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Minimum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Duration\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Maximum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"SampleCount\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Average\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Sum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Minimum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Maximum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"SampleCount\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Average\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Sum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Minimum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Maximum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"SampleCount\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Average\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Sum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Minimum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Maximum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"SampleCount\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Average\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Sum\"}},{\"id\":\"xxx\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"SampleCount\"}},{\"id\":\"m_d87f827467f4139b6240dfd1967e05c3e864ac1b9e033251a47d1603ae8f8217\",\"metricStat\":{\"metric\":{\"namespace\":\"AWS/Lambda\",\"metricName\":\"Throttles\",\"dimensions\":[{\"name\":\"FunctionName\",\"value\":\"xxxxx\"}]},\"period\":60,\"stat\":\"Average\"}},{\"id\":\"m_xxx",
㴽  "requestParameters.scanBy": "TimestampAscending",
  "requestParameters.startTime": "Jun 9, 2024, 12:53:00 PM",
  "resources": "[{\"type\":\"AWS::CloudWatch::Metric\"}]",
  "responseElements": "null",
  "sourceIPAddress": "xxx",
  "timestamp": 1717938605329,
  "tlsDetails.cipherSuite": "xxxxx",
  "tlsDetails.clientProvidedHostHeader": "monitoring.ap-northeast-1.amazonaws.com",
  "tlsDetails.tlsVersion": "xxxxx",
  "userAgent": "aws-sdk-java/xxx Linux/5.xxx.amzn2.aarch64 OpenJDK_64-Bit_Server_VM/17.0.11+9-LTS java/17.0.11 kotlin/1.9.10 vendor/Amazon.com_Inc. cfg/retry-mode/legacy cfg/auth-source#unknown, NewRelic/1.0.0",
  "userIdentity.accessKeyId": "xxxxx",
  "userIdentity.accountId": "xxxxx",
  "userIdentity.arn": "arn:aws:sts::xxxxx:assumed-role/NewRelicxxxxx/newrelic-infrastructure",
  "userIdentity.principalId": "xxxxx:newrelic-infrastructure",
  "userIdentity.sessionContext.attributes.creationDate": "2024-06-09T12:33:20Z",
  "userIdentity.sessionContext.attributes.mfaAuthenticated": "false",
  "userIdentity.sessionContext.sessionIssuer.accountId": "xxxxx",
  "userIdentity.sessionContext.sessionIssuer.arn": "arn:aws:iam::xxxxx:role/NewRelicxxxxx",
  "userIdentity.sessionContext.sessionIssuer.principalId": "xxxxx",
  "userIdentity.sessionContext.sessionIssuer.type": "Role",
  "userIdentity.sessionContext.sessionIssuer.userName": "NewRelicxxxxx",
  "userIdentity.type": "AssumedRole"
}

メトリクス情報は「requestParameters.metricDataQueries」の値に含まれているようです。
メトリクス名を抽出するのは難しいそうだったため、メトリクス名前空間を以下のようにparsingRuleによって抽出しました。

docs.newrelic.com

参考情報:New RelicのData Partition機能

ログを特定の属性ごとにグループ分けすることができる機能になります。
これにより、特定の条件に一致するログのみを分離して管理することが可能です。

例えば、「eventName = 'GetMetricData'」という条件に一致するログだけを他のログと混ぜたくない場合、 Data Partitionを設定することで、その条件に一致するログのみを確認できるようにすることができます。

newrelic.com

作成したダッシュボード

  • ログ転送用Lambdaの実行数および失敗割合
  • GetMetricDataが発生しているメトリクス名前空間の割合
  • GetMetricDataを実行しているエンティティ名(IAMロールなど)
  • GetMetricData発生推移
  • GetMetricData詳細情報(ログ情報)

Lambdaの費用について

項目 無料枠 料金
リクエスト数 1か月あたり100万リクエスト 0.20 USD/100万リクエスト
実行時間 1か月あたり400,000 GB-秒 0.00001667 USD/GB-秒

aws.amazon.com

上記を踏まえると5分間に 37回 Lambdaを実行すると、1か月で無料枠を超えることになりそうです。
以下計算内容になります。

  • 計算の前提条件
    • 1か月は30日と仮定します。
    • 1回の実行時間は2秒、メモリは128MBと仮定します。
    • 1ドル = 140円と仮定します。
    • NewRelicからのAPIポーリング間隔は5分とします
  • 実行時間の計算
    • 1回の実行時間は2秒、メモリは128MBなので、1回の実行で消費するGB/秒は
      • 128MB×2 =0.256GB
    • 1か月に無料で実行できる回数
      • 400GB/0.256GB = 1,562,500
  • 5分間に実行できる回数
    • 1か月の総分数
      • 30 ×24 ×60 =43,200
    • 5分間に無料で実行できる回数:
      • 1,562,500 /43,200 = 36.16 5

アラート通知について

GetMetricData API の呼び出し回数を可視化できました。
しかし、GetMetricData API の呼び出し回数が増えると、それに伴って Lambda が実行されるため、コストが増加する可能性があります。

以下流れにすることで運用コストを抑えることが出来ると思います。

  • GetMetricData APIの呼び出し回数を可視化し、普段の呼び出し回数を把握します。
  • Lambdaを停止し、アラート通知のためにメトリクスフィルターを作成します。
    • メトリクスフィルター→New Relicへの転送方法の候補はCloudWatchアラーム or Kinesis経由が挙げられます
    • しきい値は把握した呼び出し回数を元に設定
    • メトリクスフィルターのフィルター条件に対象のエンティティ名を指定する(例:New Relicで使用しているIAMロール)
      • 指定した場合、アラート発生時にNew Relicによる呼び出し回数が増えたことが確認できます
メトリクスフィルターの例:  
$.eventName = "GetMetricData" && $.userIdentity.sessionContext.sessionIssuer.userName = "★New Relicで使用するIAMロール名★" }

まとめ

New Relicを使用してCloudWatch GetMetricData API のイベントを可視化することが出来ました。
データイベントの追加料金など考慮は必要そうですが誰が呼び出したこともわかりコスト見直し等に役立ちそうですね。

福田 圭 (記事一覧)

マネージドサービス部

2023 New Relic Partner Trailblazer

X @soundsoon25