AWS明細データの歩き方

こんにちは、AWS担当のwakです。今年もよろしくお願いいたします。ずいぶん間が空いてしまいましたが、今年はペースアップしていこうと思います。

f:id:nurenezumi:20190110101608j:plain
今年は猫年(だったはず)

AWS料金明細データ

さて、今回はAWSの利用料金明細データの話をします。AWSの現時点での料金、過去数ヶ月の料金などは 請求ダッシュボードAWS Cost Explorer などで参照することができますが、この元となる詳細なデータは、事前に「リソースとタグを含む詳細な請求レポート」を出力するように 設定 しておくことでS3にCSVファイルとして出力することができます。

このファイルは

999999999999-aws-billing-detailed-line-items-with-resources-and-tags-2019-01.csv.zip

という書式の名前のzipファイル*1で、費目・1時間ごとに1行記載される形となっています。たとえばEC2を1ヶ月起動していると、1時間ごとに

  1. EC2の利用料金
  2. EC2のネットワークI/O料金(IN)
  3. EC2のネットワークI/O料金(OUT)
  4. EBSの利用料金

の4行*2が発生し、1ヶ月=720時間では数千行に達します。今回はこのファイルを細かく集計してみたい人に向けて明細データの読み方について書きます。

ファイル概要

CSV形式

標準的なカンマ区切りのテキストファイル(UTF-8エンコーディング、改行はLF)です。数値を含め、全てのフィールドはダブルクオートでくくられています。また "$0.0037 per 10,000 GET and all other requests" のようにカンマを含むフィールドが多数存在するため、ダブルクオートを無視してカンマ区切りで読み込もうとすると悲しいことになります。awkなどで処理したい場合は "," を区切り文字とすれば良いでしょう。

記載されている内容

毎月月初にAWSから請求書(PDF形式)がメールで届きますが、明細データにはこの1ヶ月分の請求書の内容がまとめて出力されています。RIを購入した場合は別途請求が発生するのでこちらには含まれません。

カラム数は固定(ただし設定次第)

カラム数は固定です。ただし一括請求を有効にすると Rate, Cost カラムは BlendedRate, BlendedCost, UnBlendedRate, UnBlendedCost カラムになります(後述)。

また、「コスト配分タグ」でタグ(たとえば IsDev タグと Project タグ)を追加すると、行の末尾に user:IsDev, user:Project カラムが追加されます(アルファベット順にソートされるようです)。このタグの値をもとに AWS Cost Explorer などで料金を集計することができます。設定を変更するとカラム数も変わってしまいます。

請求書番号

明細データの最初のカラムは InvoiceID で、請求書の上部に記載されているInvoice Numberが書いてあります。月の途中の場合は請求書がまだ発行されていない(=Invoice Numberが確定していない)ため全ての行で "Estimated" になります。

レコードID

各行には RecordId というフィールドがあり、行ごとにユニークな値が振られています。IDは整数形式ですが、 155050012134381272590339883 (=1.55*1026、287) のように非常に大きな値で、かつレコード間で差が10~20前後しかないものがあるので、大抵の場合は文字列として扱う必要があります(64bitのfloat型などではまるで精度が足りません)。

末尾には特殊な行がある

ファイルの末尾には

を記載した行があります。集計する場合はここを考慮する必要があります。(料金総額は除いておく、サポート・消費税は利用料金に含めておくなど)

レコードの内容

具体的に1レコード(1行)の内容を見ていきます。

#カラム名概要
1InvoiceID前述。請求書に書いてあるInvoice Number
2PayerAccountId料金を支払うアカウントのアカウント番号
3LinkedAccountId料金を発生させたアカウントのアカウント番号。一括請求を使わない場合は(2)と同じ
4RecordTypeほとんどが"LineItem"(後述)
5RecordId前述。レコードに振られたユニークなID
6ProductNameプロダクト名。 "Amazon Simple Storage Service"、"AWS Lambda" など
7RateId-
8SubscriptionId-
9PricingPlanId-
10UsageType費目1
11Operation費目2
12AvailabilityZoneAZ名
13ReservedInstanceRIが適用されているなら"Y"、そうでないなら"N"
14ItemDescriptionこの行の説明
15UsageStartDateこの行のタイムスタンプ("2019-01-02 03:00:00"の書式。1時間単位)
16UsageEndDate同上(その1時間後)
17UsageQuantity使用量
18BlendedRate単価1。後述
19BlendedCost料金1((17)×(18)に等しい)
20UnBlendedRate単価2。後述
21UnBlendedCost料金2((17)×(20)に等しい)
22ResourceIdリソースIDまたはARN
23以降user:XXXX出力するように設定したタグの値

RecordType について

実際の料金が記載された行(ほとんどがこれ)は LineItem となっています。集計に使う場合は LineItem の行だけ抜き出せばいいです。それ以外の行はファイル末尾にしかありません。

UsageType について

あまり綺麗なデータではありません。たとえばEC2に限っても、 APN1-BoxUsage, APN1-BoxUsage:m5.large, APN1-EBSOptimized:m5.large のようにインスタンスサイズがあったりなかったりします。

末尾のタグについて

たとえば Project というタグを指定した場合、23カラム目にはその行のリソース(EC2インスタンス、RDSインスタンス、S3バケット……)の Project タグの値が入ってきます。

すべてのレコードにこのタグが出力されるわけではありません。タグを付け忘れたリソースの行にはもちろん空文字が入ります。また(徐々に対応するサービスが増えてはいますが)2019年1月現在、CloudWatchやAPI Gatewayにはタグを付けることができないため、ここは空文字が入ります。別途 ProductName, ResourceId で集計するなどの工夫が必要になります。

(Un)BlendedCost, (Un)BlendedRate について

AWSの料金のほとんどは使えば使うほど割安になっていきます。たとえば2019年1月現在、東京リージョンのS3標準ストレージの料金(ストレージ使用分)は次の通りです。

  • 最初の50TBまでは $0.025/GB・月
  • 次の450TBまでは $0.024/GB・月
  • 500TB以上は $0.023/GB・月

ではアカウントが2個(A・Bとします)あり、それぞれS3を100TBずつ利用しており、さらに一括請求を利用している場合の料金はどうなるでしょうか。 公式ドキュメント によれば、1個のアカウントで200TBを使ったときと同じ料金になります。つまり、

  • 最初の50TB分($0.025×50,000=$1,250)
  • 残りの150TB分($0.024×150,000=$3,600)

で合計 $4,850 ということになります。

ところが明細データはアカウント別にレコードが出力されますので、これを無理やりアカウントAとBに分けなければなりません。そこで、明細データの上では

  • 便宜上 アカウントAで最初の50TBを利用($0.025×50,000=$1,250)
  • アカウントAで残りの50TBを利用($0.024×50,000=$1,200)
  • アカウントBでさらに100TBを利用($0.024×100,000=$2,400)

というデータが出力されます(AとBどちらが選ばれるかは謎です)。ここで適用される単価が UnBlendedRate で、それをもとに算出された料金が UnBlendedCost です。しかし、まったく同じようにS3を使っているのにアカウントAとBで料金が違う(ように算出される)のでは困ります。そこで、一括請求を前提としてこれを平準化した単価が BlendedRate 、それをもとに算出された料金が BlendedCost です。

UnBlendedRate, UnBlendedCost の良いところは、単価がAWSの公表しているものと一致しているので検証しやすいことです。悪いところは実態と合わない(アカウント・リソース間で割引の有無の差異が発生して料金に差が出てしまう)ことです。総額は一致するので、どちらか使いやすい方を選びましょう。

EC2 RIについて

EC2 RIについても触れておきます(以下、OSはすべてLinuxとします)。

サポート利用料は月の請求に載る

RIを購入するたびに別途請求書が発行され、支払いはその都度行います。しかしサポート料金*3は月の料金に応じて決まるため、サポート利用料は月ごとの請求書の方に載ります。ここを忘れていると、RIで高額な支払いを済ませた翌月に料金が跳ね上がっていてびっくりすることになります。

RIは特定のインスタンスに紐付くわけではない

RIはあくまで「特定のインスタンスタイプのインスタンスを安く利用できる仕組み」です。さらに(ありがたいながらも)ややこしいことに、 RIの柔軟性 という仕組みがあります。これがどういうことが例示します。

まず m3.2xlarge の全額前払いRIを4個購入したとします。また、

を起動していたとします。

RIは1時間ごとにA~Gの いずれか に適用されます。したがって、

  • A~Dに適用される(A~Dが無料になる)

というケースもありますが、

  • A~CにRIが3個分適用される(A, B, Cは無料になる)
  • 残る1個のRIが次のように分配される
    • Dには25%分適用される(Dの料金は25%引きになる)
    • E には50%分適用される(Eの料金は m3.2xlarge の50%なのでEは無料になる)
    • F, G にはそれぞれ12.5%分適用される(F, Gの料金は m3.2xlarge の12.5%なのでF, Gは無料になる)

ということもあり得ます(いずれも料金の総額は変わりません)。後者の場合、次のようなレコードが出力されます。

ResourceIdItemDescription(概要)ReservedInstanceUsageQuantityUnBlendedRate
Am3.2xlargeのRI適用Y10
Bm3.2xlargeのRI適用Y10
Cm3.2xlargeのRI適用Y10
Dm3.2xlargeのRI適用Y0.250
D標準料金 ($.077/h)N0.750.77
Em3.2xlargeのRI適用Y10
Fm3.2xlargeのRI適用Y10
Gm3.2xlargeのRI適用Y10

インスタンスE, F, Gのインスタンスタイプは m3.xlarge, m3.medium なのに( UsageType カラムにはそれぞれ APN1-BoxUsage:m3.xlarge, APN1-BoxUsage:m3.medium と書いてあります)、 ItemDescription には「m3.2xlargeのRIを適用した」と書いてあるのがポイントです。

BlendedRate, BlendedCost はRIでも働いていて、こういった料金を平準化した料金が記載されています。

調整したい場合は?

とはいえ、「自分はこのインスタンスの料金を前払いしたんだ」という気持ちでRIを購入することもあるでしょう(たとえば上のケースですとインスタンスA~Dの料金を常に0とし、E~Gの ReservedInstanceY となっている行に標準料金を加算したいといった感じです)。そのような場合は、

  • RIの柔軟性 に記載されているインスタンスサイズと係数のテーブル
  • UsageType カラムに記載されているそのインスタンスのサイズやOSから求めた標準料金
  • ItemDescription カラムに記載されている適用されたRIのタイプとサイズ

などを組み合わせて求める形に修正してやる必要があります。非常に面倒なのでオススメはしません……

スナップショットの料金

EBSのスナップショットはS3に保存され、1時間ごとにストレージ利用料金が加算されていきます。 ResourceId カラムにはスナップショットのARNが記載されています:

arn:aws:ec2:ap-northeast-1:999999999999:snapshot/snap-0123456789abcdef0

2019年1月時点では、AMIを作成した際に生成されるスナップショットにはタグが付きません(手動で追加することはできます)。タグの値が空ですとこのスナップショットの正体が分かりません。料金がどのEBSから発生したものかを知りたければ、このARNからスナップショットを取得して作成元のEBSを特定する必要があります。しかし、AMIやスナップショットを定期的に作成・削除するような運用をしている場合、料金集計をしようとした時点では既にそのスナップショットが存在しないということがあり得ます。AMI/スナップショットを作るたびにタグを追加するか、Snapshot IDとEBS Volume IDの対応表を定期的に取得して保存しておくような処理を仕込んでおくと良いでしょう(LambdaとDynamoDBを使うと楽です)。

RDSのストレージ利用料金

RDSのバックアップに利用しているストレージ利用料金は、(なぜか)それがどのインスタンスのものかは分からず、 ResourceId が空文字になります。これはどうしようもないので、

  • ResourceId が空文字以外のものについてRDSの料金を集計する
  • RDSの利用料金に応じてストレージ利用料金を按分する

という手で振り分けてしまうのがいいと思います。

集計!

というわけで、たとえば Project というタグの値に基づいて明細データの料金集計をしたい場合、次のような流れになります。

  • zipファイルをS3から取得、解凍
  • RDSやRなどに読み込む
  • RecordTypeLineItem の行だけ抜き出す
  • Cost または BlendedCost の値を user:Project の値に応じて合算する
    • user:Project の値が空文字のレコードについては2パターンがある(タグを付けるのを忘れていたもの、タグを付けられないもの)
    • タグを付け忘れていたものについてはデータ修正を行って再集計する
    • それ以外のものは ProductName, ResourceId の値に応じて Cost または BlendedCost の値を合算する

以上、明細データの見方についてハマりやすいところを簡単にまとめてみました。正しくコストを把握して正しくAWSを利用していきましょう。今年もよろしくお願いします。

*1:展開すると.zipが取れる

*2:VPCをまたいだ通信があったりEIPを使ったりするとネットワークI/Oの行はそれぞれ2行ずつ増えます。EBSが複数存在すればその分も増えます

*3:ビジネスサポート以上の場合