AWS の Billing CSV を OnDemand 価格で集計したい

AWS を複数案件で利用してると、当然案件毎の利用料を分離して集計する必要が出てくると思う。

で、更にその際にありがちなケースだと思うんだけど、節約のためにリザーブドインスタンス買ったりスポットインスタンス使ったりするところまではいい。
ただ、この値引き価格は各案件の予算(費用)には適用したくなくて『各案件の利用料は利用時間*オンデマンド価格で集計したい』って事がよくある。あと FreeTier 分も無かったことにして集計したいとかね。
でも AWS の Billing Console ではそんな集計画面は無いし、Billingの詳細CSVをダウンロードしてみても、割引価格の分配方法の違いによる BlendedCost とか UnBlendedCost とかのカラムはあるけど、もしそれをオンデマンドで使ってた場合の参考価格みたいなのはCSVには一切登場しないので非常に困っていた。

でも今は [AWS Price List API](http://aws.typepad.com/aws_japan/2015/12/new-aws-price-list-api.html) が少し前にようやく公式からアナウンスされたことでオンデマンド価格を調べやすくなった。コレを使ってオンデマンド価格を BillingCSV にカラム追加しといてやれば、期待通りの集計に持っていくことが出来るわけだ。素晴らしい。


# サンプルスクリプト
で、とりあえずどんなデータが取れるのかとかどうやって探せばいいのかとかを実際にAPI叩いて試してたところ大体わかったのでとりあえず以下のようなスクリプトを作ってみた。

## 全ての料金JSONをダウンロード
まずは毎回APIにアクセスするには結構データがデカくて帯域と時間の無駄なので全部ローカルにダウンドードすることにした。

```bash:collect-aws-pricing-json.sh
#!/bin/bash
prefix="aws-pricing-"
endpoint="https://pricing.us-east-1.amazonaws.com"
# shellcheck disable=SC2016
{
  curl -sL "$endpoint/offers/v1.0/aws/index.json" | tee "${prefix}offeres.json" |
  jq -r '.offers[]|[.offerCode, .versionIndexUrl, .currentVersionUrl]|@sh' |
  while read -r vars; do
    eval "vars=($vars)"
    offerCode=${vars[0]}
    versionIndexUrl=${vars[1]}
    currentVersionUrl=${vars[2]}
    curl -sL "$endpoint$currentVersionUrl" > "${prefix}$offerCode-currentVersion.json"
    curl -sL "$endpoint$versionIndexUrl" | tee "${prefix}$offerCode-versionIndex.json" |
    jq -r '.versions|. as $v|keys[]|[., $v[.].offerVersionUrl]|@sh' |
    while read -r vars; do
      eval "vars=($vars)"
      version=${vars[0]}
      offerVersionUrl=${vars[1]}
      curl -sL "$endpoint$offerVersionUrl" > "${prefix}$offerCode-$version.json"
    done
  done
}
```

とりあえず `bash collect-aws-pricing-json.sh` を実行するとカレントディレクトリに `aws-pricing-*.json` がいっぱい出来る。337MBあるようだ。過去の価格が不用ならもう少し小さくなりそう。

<img width="751" alt="スクリーンショット 2016-07-05 16.01.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/18726/6df03eef-312e-a13a-ec38-b977989313f9.png">


# JSONからオンデマンド価格を探す

`Billing CSV` と `Price JSON` をどうやってひも付けて探せばいいかなーと思って色々項目見てたんだが、どうやら UsageType と Operation をキーに探せば良さそうということが分かった。
で、それをスクリプト化したのが↓これ。

```bash:get-ondemand-price.sh
#!/bin/bash
UsageType=$1
Operation=$2

cat aws-pricing-Amazon*-currentVersion.json |
jq \
  --arg UsageType "$UsageType" \
  --arg Operation "$Operation" \
  '. as $top|.products|to_entries[].value|. as $product|.attributes|
  select(.usagetype == $UsageType and .operation == $Operation)|
  {
    Product: $product,
    OnDemand: $top.terms.OnDemand[$product.sku]
  }'
```

あとは試しに `*-billing-detailed-*.zip` な料金CSVから適当なEC2とかRDSとかの行を探して、その行の `UsageType` と `Operation` をメモって以下のように実行してやれば良い。

<img width="793" alt="スクリーンショット 2016-07-05 16.08.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/18726/e29675bc-aaa8-ec00-6faf-a2e666e1d14e.png">

`Price List API` の JSON では `.products`（製品情報） と `.terms`(価格情報) が別のJSONオブジェクトとして分かれているのでそれらを紐付けしたデータを引っ張ってくるのに一手間必要だが、まぁ `jq` の中途情報の変数保存機能を使うと結構簡単にクエリすることが出来た。

初見だとデータ量も多いのでよくわからなかったが、データ構造さえ把握できればそんなに難しいものではなかった。今回はお試しなので `jq` で適当に探してみたが実際の集計に使う際には普通にプログラム書いてやればよいと思う。


今回はとりあえずお試しでカレントバージョンの価格JSONから探したが、実データを集計する場合はCSVのレコード日時を元に適切なバージョンのJSONを使って、その当時の価格を得ればOKな筈だ。コレは `*-versionIndex.json` をみればすぐ分かるレベルの難しくない話だ。