頭上からドル札が飛び出ているブタの貯金箱

はじめに

New RelicではAWS Cost and Usage Reports(AWS CUR)をLogとしてテレメトリデータプラットフォームに取り込むことで、New Relic 上でAWS コストの分析や可視化が出来るようになります。

本ブログではAWS CURをNew Relicに取り込む方法と取り込んだレポートの分析や可視化の例について紹介します。

前提条件

AWS CURをCSV形式でS3に定期的にエクスポートする設定が必要になります。エクスポートの方法はAWSの公式手順をご確認ください。

構成について

Amazon S3にAWS CURのCSVレポートが出力されたことをトリガーとしてAWS Lambda関数 NewRelic-log-ingestion-s3が起動し、New Relic Logsにレポートデータを転送する形になります。

AWS コストと使用状況レポート(CUR)を分析しやすく可視化_01

設定の流れ

設定は以下の流れで行います。

  1. AWS CURをログとして転送するためのAWS Lambda関数をデプロイする
  2. AWS Lambda関数へトリガーを追加する

AWS CURをログとして転送するためのAWS Lambda関数をデプロイする

Amazon S3に出力されるAWS CURのCSVレポートをNew Relicに転送するためにAWS Lambda関数をデプロイします。

AWS Lambda関数はAWS Serverless Application Repositoryで公開されているため、簡単にデプロイすることができます。

AWS Lambda関数のデプロイ方法は別ブログ Amazon S3に保存したログをNew Relicに取り込むに記載されている「Amazon S3ログ転送のためのAWS Lambda関数をデプロイする」の箇所をご確認ください。

AWS Lambda関数へトリガーを追加する

デプロイしたAWS Lambda関数がAmazon S3バケットにAWS CURが出力されたタイミングで起動されるように、当該AWS Lambda関数にトリガーを指定します。

1.「トリガーを追加」をクリックします。

AWS コストと使用状況レポート(CUR)を分析しやすく可視化_02

以下のパラメーターでトリガーを設定します。

  • ソース:S3
  • バケット: CURが出力されているバケットを選択
  • イベントタイプ:すべてのオブジェクト作成イベント
  • プレフィックス:cur/

トリガーが作成されました。

AWS コストと使用状況レポート(CUR)を分析しやすく可視化_04

これで、AWS側での転送の設定は完了になります。

続いて、New Relic LogsのUIで設定を行います。

Parsing Ruleを設定する

転送されるAWS CURのデータをNew Relic Logsで属性(キーと値のペア)して取り込むためにPasrsing Ruleの設定を行います。

Parsingすることで、ログのメッセージが生ログとして保存されるのではなく、属性(キーと値のペア)で保存されるため、その後のログの検索や可視化が非常にしやすくなります。

※Parsingの詳細については公式ドキュメントをご確認ください。

New Relic LogsのUIを開き、左ツリー「Manage data」の「Parsing」をクリックし、「Create parsing rule」をクリックします。

AWS コストと使用状況レポート(CUR)を分析しやすく可視化_05
AWS コストと使用状況レポート(CUR)を分析しやすく可視化_06

以下のParsing ruleを入力します。

  • Name: 任意のParsing Rule名(ex. AWS Cost Usage Report)
  • Filed to parse: message
  • Filter logs based on NRQL: `aws.s3_bucket_name` = ‘Amazon S3バケット名’(ex. `aws.s3_bucket_name` = 'billing-lbngwt1utu')

※今回の設定は本ブログの環境様にあわせたAmazon S3バケット名になっています。適宜バケット名を変更してください。

AWS コストと使用状況レポート(CUR)を分析しやすく可視化_07
%{GREEDYDATA:log:csv({"columns": ["identity/LineItemId","identity/TimeInterval","bill/InvoiceId","bill/InvoicingEntity","bill/BillingEntity","bill/BillType","bill/PayerAccountId","bill/BillingPeriodStartDate","bill/BillingPeriodEndDate","lineItem/UsageAccountId","lineItem/LineItemType","lineItem/UsageStartDate","lineItem/UsageEndDate","lineItem/ProductCode","lineItem/UsageType","lineItem/Operation","lineItem/AvailabilityZone","lineItem/ResourceId","lineItem/UsageAmount","lineItem/NormalizationFactor","lineItem/NormalizedUsageAmount","lineItem/CurrencyCode","lineItem/UnblendedRate","lineItem/UnblendedCost","lineItem/BlendedRate","lineItem/BlendedCost","lineItem/LineItemDescription","lineItem/TaxType","lineItem/LegalEntity","product/ProductName","product/abdInstanceClass","product/alarmType","product/availability","product/availabilityZone","product/backupservice","product/bundle","product/bundleDescription","product/bundleGroup","product/callingType","product/capacitystatus","product/ciType","product/classicnetworkingsupport","product/clockSpeed","product/country","product/cputype","product/currentGeneration","product/dataTransferQuota","product/databaseEdition","product/databaseEngine","product/datatransferout","product/dedicatedEbsThroughput","product/deploymentOption","product/description","product/directorySize","product/directoryType","product/directoryTypeDescription","product/durability","product/ecu","product/endpointType","product/engine","product/engineCode","product/enhancedNetworkingSupported","product/equivalentondemandsku","product/eventType","product/feeCode","product/feeDescription","product/findingGroup","product/findingSource","product/findingStorage","product/freeTrial","product/freeUsageIncluded","product/fromLocation","product/fromLocationType","product/fromRegionCode","product/fusfCategory","product/gpuMemory","product/group","product/groupDescription","product/highAvailability","product/insightstype","product/instance","product/instanceFamily","product/instanceType","product/instanceTypeFamily","product/intelAvx2Available","product/intelAvxAvailable","product/intelTurboAvailable","product/invocation","product/license","product/licenseModel","product/lineType","product/location","product/locationType","product/lockeprofiles","product/logsDestination","product/marketoption","product/maxIopsBurstPerformance","product/maxIopsvolume","product/maxThroughputvolume","product/maxVolumeSize","product/maximumStorageVolume","product/memory","product/memorytype","product/messageDeliveryFrequency","product/messageDeliveryOrder","product/minVolumeSize","product/minimumStorageVolume","product/networkPerformance","product/normalizationSizeFactor","product/operatingSystem","product/operation","product/physicalProcessor","product/platopricingtype","product/platostoragetype","product/platousagetype","product/platovolumetype","product/preInstalledSw","product/primaryplaceofuse","product/processorArchitecture","product/processorFeatures","product/productFamily","product/protocol","product/queueType","product/region","product/regionCode","product/requestDescription","product/requestType","product/resourceType","product/rootvolume","product/routingTarget","product/routingType","product/runningMode","product/scanType","product/servicecode","product/servicename","product/sku","product/softwareIncluded","product/sourcetype","product/standardGroup","product/standardStorage","product/steps","product/storage","product/storageClass","product/storageMedia","product/storageType","product/tenancy","product/tenancySupport","product/tickettype","product/tiertype","product/timeWindow","product/toLocation","product/toLocationType","product/toRegionCode","product/transactionType","product/transferType","product/usageVolume","product/usagetype","product/uservolume","product/vcpu","product/version","product/volumeApiName","product/volumeType","product/vpcnetworkingsupport","pricing/RateCode","pricing/RateId","pricing/currency","pricing/publicOnDemandCost","pricing/publicOnDemandRate","pricing/term","pricing/unit","reservation/AmortizedUpfrontCostForUsage","reservation/AmortizedUpfrontFeeForBillingPeriod","reservation/EffectiveCost","reservation/EndTime","reservation/ModificationStatus","reservation/NormalizedUnitsPerReservation","reservation/NumberOfReservations","reservation/RecurringFeeForUsage","reservation/StartTime","reservation/SubscriptionId","reservation/TotalReservedNormalizedUnits","reservation/TotalReservedUnits","reservation/UnitsPerReservation","reservation/UnusedAmortizedUpfrontFeeForBillingPeriod","reservation/UnusedNormalizedUnitQuantity","reservation/UnusedQuantity","reservation/UnusedRecurringFee","reservation/UpfrontValue","savingsPlan/TotalCommitmentToDate","savingsPlan/SavingsPlanARN","savingsPlan/SavingsPlanRate","savingsPlan/UsedCommitment","savingsPlan/SavingsPlanEffectiveCost","savingsPlan/AmortizedUpfrontCommitmentForBillingPeriod","savingsPlan/RecurringCommitmentForBillingPeriod","resourceTags/user:Env","resourceTags/user:Name","resourceTags/user:STAGE","resourceTags/user:Use","resourceTags/user:company","resourceTags/user:team","resourceTags/user:user:Owner"], "noPrefix": false})}

上記Parsing ruleはAWS CURのCSVのヘッダー行に出力されているカラム名をキーとして取り込む設定になっています。

今回は全ての属性を保存するようなルールになっていますが、不要なルールがある場合には、属性名のところでアンダースコア(_)を指定してください。

データの取り込み確認

転送したAWS CURが正しく取り込まれているか、New Relic UI上でNRQLを実行して確認します。AWS Lambda関数経由でAmazon S3バケットのデータを転送している場合、New Relic Logsに取り込まれたデータにAmazon S3バケット名が付与されているので、そのAmazon S3バケット名でフィルターしてデータが取り込まれていることを確認することができます。

SELECT
    `message`
FROM
    Log
WHERE
    allColumnSearch('ここにAmazon S3バケット名を入力', insensitive : true) SINCE 1 day ago
limit 10

リストに出力されたログをクリックするとログが構造化されて取り込まれていることを確認することができます。

コスト配分タグの取り込みについて

AWS コスト配分タグを設定していると、AWS CURのレポートにコスト配分タグの値も出力されるようになるため、例えば以下グラフの様にサービスの環境毎(prod,stg,dev)にNew Relic上でコストを分析することができるようになります。

本記事のParsing Ruleの設定例では、レポートに出力されている以下のコスト配分タグをNew Relicに属性として取り込む設定を記述しています。

"resourceTags/user:Env","resourceTags/user:Name","resourceTags/user:STAGE","resourceTags/user:Use","resourceTags/user:company","resourceTags/user:team","resourceTags/user:user:Owner"

上記の設定をParsing Ruleに入れることで、New Relic Logsに以下の属性(キーと値)で取り込まれるようになります。

AWS コストと使用状況レポート(CUR)を分析しやすく可視化_11

設定例のParsing Ruleの最後の箇所のnoPrefixをfalseで設定するとキー名の頭に「log.」とプレフィックスがつく形になります。noPrefixをtrueに設定すると「log.」がキー名の頭に付かない形で取り込まれる形になります。

データの可視化

取り込んだデータをNRQLで分析・可視化したい形で取り出し、ダッシュボード化することで様々な観点でAWS Costを分析できるようになります。

幾つかNRQLのサンプルを記載します。

・コスト配分タグのEnv別の合計コストをPie Chartで可視化

SELECT
    max(total_cost)
FROM
    (
        SELECT
            sum(numeric(`log.lineItem/BlendedCost`)) as `total_cost`
        FROM
            Log
        WHERE
            `aws.s3_bucket_name` = 'xxxxx'
        and `log.resourceTags/user:Env` != ''
        and `log.resourceTags/user:Env` not like 'resourceTags%' FACET `log.resourceTags/user:Env` TIMESERIES 6 hours
    ) since 7 days ago  FACET `log.resourceTags/user:Env`

・コスト配分タグのTeam&AWSサービス別の合計コストをPie Chartで可視化

SELECT
    max(total_cost)
FROM
    (
        SELECT
            sum(numeric(`log.lineItem/BlendedCost`)) as `total_cost`
        FROM
            Log
        WHERE
            `aws.s3_bucket_name` = 'xxxxx'
        and `log.resourceTags/user:team` like 'Company%' FACET `log.product/ProductName`,
            `log.resourceTags/user:team` TIMESERIES 6 hours
    ) SINCE 7 days ago FACET `log.product/ProductName`,`log.resourceTags/user:team`

・AWSサービス別のコストの傾向をStacked Bar Chartで可視化

SELECT
    max(cost)
FROM
    (
        SELECT
            sum(numeric(`log.lineItem/BlendedCost`)) as cost
        FROM
            Log
        WHERE
            `aws.s3_bucket_name` = 'XXXXX' TIMESERIES 6 hour facet `log.product/ProductName`
    ) SINCE 7 days ago TIMESERIES 24 hours facet `log.product/ProductName`

・AWSサービス別コストをBar Chartで可視化

SELECT
    max(cost)
FROM
    (
        SELECT
            sum(numeric(`log.lineItem/BlendedCost`)) as cost
        FROM
            Log
        WHERE
            `aws.s3_bucket_name` = 'xxxxx' TIMESERIES 6 hour facet `log.product/ProductName`
    ) SINCE 7 days ago facet `log.product/ProductName`

また、例えばEC2やRDSのリソース稼働状況やAPMのデータ、サービスレベルなどもコストの可視化グラフと共にダッシュボード内に含めることで、サービスの実稼働データを踏まえた上で余剰AWSリソースをスケールダウンもしくは削除する際の判断材料として活用することもできるようになります。

注意点

AWS CURのCSVデータは1日に複数回生成されるため、New Relicに重複して請求データが取り込まれることになります。

NRQLで請求データを集計する際にサブクエリ内で最新の請求データのみ参照する形に変更することで重複して計算しないように工夫しています。

まとめ

AWS CURをNew Relicに取り込むことでコスト分析をNew Relicで観測している他データと組み合わせて、様々な観点でAWSコストの分析ができるようになります。

また、AWS Billing monitoring integration ではコスト配分タグの取り込みができないため、コスト配分タグをキーに分析をされたいケースの場合、本手順でNew Relic上でAWS コスト分析ができるようになりますので、是非お試しください。