Pythonで CostExplorer を利用してアカウント毎の当月の累計請求情報と日別請求情報を取得してみました

## 概要

AWS CostExplorer を利用してアカウント毎の当月の累計請求情報と日別請求情報を取得する Pythonプログラムを作成してみました。親組織の下に複数の子組織がぶら下がっており、そのすべてのアカウントの請求情報を取得しています。表示は請求金額の多いアカウントから降順にソートしています。

- [このプログラム](https://qiita.com/turupon/items/309bc3963e59cd0bdaf2) を組み込み、USDで取得した課金情報をJPYに換算しています。
- [このプログラム](https://qiita.com/turupon/items/e5e0d9f040099e8531ad) を組み込み、アカウント名を取得しています。

### 実行環境

macOS Monterey 12.3.1 
python 3.8.12

### 実行プログラム

```python:CostExplorer_Account_All.py
from datetime import datetime, timedelta, date
import boto3
import pandas as pd
from pandas_datareader.data import get_quote_yahoo


# 実行月の初日を取得
def get_begin_of_month() -> str:
    return date.today().replace(day=1).isoformat()

# 実行日を取得
def get_today() -> str:
    return date.today().isoformat()

# 請求金額取得対象期間の取得
def get_total_cost_date_range() -> (str, str):
    start_date = get_begin_of_month()
    end_date = get_today()

    # get_cost_and_usage()のstartとendに同じ日付は指定不可のため、
    # 「今日が1日」なら、「先月1日から今月1日（今日）」までの範囲にする
    if start_date == end_date:
        end_of_month = datetime.strptime(start_date, '%Y-%m-%d') + timedelta(days=-1)
        begin_of_month = end_of_month.replace(day=1)
        return begin_of_month.date().isoformat(), end_date

    return start_date, end_date


# 請求金額データの取得
def get_billing_data(parent_acct):

    # データ取得期間の取得
    (start, end) = get_total_cost_date_range()

    # CostExplorerへのセッションオープン
    session = boto3.session.Session(profile_name=parent_acct)
    client = session.client('ce')

    # 請求データ（アカウント毎の日々の請求データ）の取得
    response = client.get_cost_and_usage(
        TimePeriod={
            'Start': start,
            'End' :  end,
        },
        Granularity='DAILY',
        Metrics=[
            'NetUnblendedCost'
        ],
        GroupBy=[
            {
                'Type': 'DIMENSION',
                'Key': 'LINKED_ACCOUNT'
            }
        ]
    )
    # pprint.pprint(response)
    return response


# 組織の全アカウント情報の取得：辞書型
def get_organization_accounts(master_acct):

    # 親組織へ接続する
    session = boto3.session.Session(profile_name=master_acct)
    organizations = session.client('organizations')

    # 全組織のアカウント情報を取得する
    pagenator = organizations.get_paginator('list_accounts')
    response_iterator = pagenator.paginate()

    # アカウント情報用リストの定義
    row_id = []
    row_name = []

    # 各アカウントの Account ID と Account Name の取得
    for response in response_iterator:
        for acct in response['Accounts']:

            # 取得するアカウント情報のリスト化
            row_id.append(acct['Id'])
            row_name.append(acct['Name'])

    # 取得した全アカウント情報を辞書型へ
    accounts = pd.DataFrame({"Id":row_id, "Name":row_name})
    return accounts


#　組織の全Accountの請求金額データの集計
def get_account_all_billings(master_acct, crate):

    # 組織の全アカウント情報の取得
    accounts = get_organization_accounts(master_acct)
    # print(accounts, "\n")

    # 請求金額データの取得
    response = get_billing_data(master_acct)

    # 整形する請求データの入れ物
    merged_cost = pd.DataFrame(
        columns=['Id']
    )

    # 請求データの整形
    for item in response['ResultsByTime']:

        # ['Groups']のデータ取得（アカウント毎の請求データ）
        normalized_json = pd.json_normalize(item['Groups'])

        # アカウントIDの取得
        split_keys = pd.DataFrame(
            normalized_json['Keys'].tolist(),
            columns=['Id']
        )
        
        # アカウントID毎の請求金額の取得
        cost = pd.concat(
            [split_keys, normalized_json['Metrics.NetUnblendedCost.Amount']],
            axis=1
        )

        # 請求金額データのカラムタイプの変更 object -> float
        cost['Metrics.NetUnblendedCost.Amount'] = cost['Metrics.NetUnblendedCost.Amount'].astype('float')
        # 請求金額を日本円に換算
        cost['Metrics.NetUnblendedCost.Amount'] = round(cost['Metrics.NetUnblendedCost.Amount']*crate, 2)

        # ['TimePeriod']['Start']のデータ取得（請求日データ）
        renamed_cost = cost.rename(
            columns={'Metrics.NetUnblendedCost.Amount': item['TimePeriod']['Start']}
        )

        # アカウント毎に請求データと請求日のマージ（右側への列追加）
        merged_cost = pd.merge(merged_cost, renamed_cost, on='Id', how='right')

    # アカウントIDをキーにしてアカウント名を列結合
    merged_cost = pd.merge(merged_cost, accounts, on="Id", how="left")
    # アカウント名の列を先頭列に作成
    merged_cost.insert(loc=0, column='AccountName', value=merged_cost['Name'])
    # 不要カラムの削除
    merged_cost.drop(['Id', 'Name'], axis=1, inplace=True)

    # 各行の合計を最後の列に追加
    merged_cost['Total'] = round(merged_cost.sum(numeric_only=True, axis=1), 2) 

    # ['Total']列からか請求金額の総合計を求める
    grand_total = round(merged_cost['Total'].sum(), 2)  

    # 合計で降順ソートし、インデックスとしてアカウント名を指定する
    merged_cost.sort_values(by='Total',ascending=False, inplace=True)
    merged_cost.set_index("AccountName", inplace=True)

    # print(merged_cost, "\n")
    return merged_cost, grand_total


# 請求金額データの通知表示への変更
def post_billings(cost_df, grand_total, crate):

    # タイトルメッセージの表示
    post_msg = "総合計 : " + str(grand_total) + "\t\tUSD-JPY : " +  str(crate)
    print("\n", post_msg, "\n")

    # 行と列の入替え
    cost_df = cost_df.transpose()

    # Total行（最後の行）の取得
    df_total = cost_df.tail(1)
    df_total = df_total.transpose()

    # 通知データの表示
    print(df_total, "\n")
    print(cost_df, "\n")
    # print(df_total.to_markdown(), "\n")
    # print(cost_df.to_markdown(), "\n")


# 現在の USD - JPY の為替レートの取得
def get_currency_rate() :
    res1 = get_quote_yahoo('USDJPY=X')
    res2 = res1["price"].values
    crate = res2[0] 
    return crate


# メイン
if __name__ == '__main__':

    # 今の為替レートを取得する
    crate = get_currency_rate()

    # 組織の全Accountの請求金額データの日別集計
    merged_cost, grand_total = get_account_all_billings('aws-master', crate)

    # 請求金額データの通知表示への変更
    post_billings(merged_cost, grand_total, crate)
```

### プログラムの実行

```
## 取得した請求情報
$ python CostExplorer_Account_All.py

 総合計 : 343731.76		USD-JPY : 138.466 

                Total
AccountName          
tech-west01  60901.42
tech-sss0    45757.98
tech-nnn     45605.74
tech-aaa     39343.08
tech-sss1    37918.29
tech-sss2    37123.14
tech-msc     33269.26
tech-ppp2    15901.64
tech-ppp1     9992.31
tech-ccc      8767.92
tech-iiii     3844.35
aws-master    2714.93
tech-west02   2591.41
tech-west03      0.29 

AccountName  tech-west01  tech-sss0  tech-nnn  tech-aaa  tech-sss1  tech-sss2  tech-msc  tech-ppp2  tech-ppp1  tech-ccc  tech-iiii  aws-master   tech-west02  tech-west03
2022-08-01       7593.18   6052.79   4932.19   4879.19    4654.60    4324.92   3002.95    5973.81    1351.75   1074.39     523.46       326.44       317.31         0.01
2022-08-02       2057.32   1504.15   1109.00   1236.32    1206.34     971.21    874.90     339.63     478.58    278.21      34.67        78.37        81.92         0.01
2022-08-03       2057.31   1533.33   1124.64   1233.67    1208.55     970.13    479.49     339.64     197.96    278.24      34.66        78.60        81.92         0.01
2022-08-04       2057.31   1497.49    966.46   1233.01    1209.94    1040.24    479.44     340.45     198.02    278.20      34.66        76.85        81.92         0.01
2022-08-05       1910.21   1471.02    606.73   1233.64    1209.91    1007.24    535.00     339.64     379.39    278.22      34.67        76.74        81.92         0.01
2022-08-06       1900.15   1117.67    606.72   1236.91    1210.63    1023.21    479.43     339.64     197.95    278.21      34.66        76.56        81.92         0.01
2022-08-07       1900.15   1108.01   1096.33   1238.06    1210.65    1206.18    479.43     339.63     197.99    278.20      36.11        76.86        81.92         0.01
2022-08-08       1900.15   1509.14   1644.58   1235.06    1210.61    1238.48    479.51     339.63     242.53    278.22      67.09        77.65        81.92         0.01
2022-08-09       1900.15   1362.44   1207.37   1238.57    1210.61    1395.15    480.46     339.65     197.95    278.22     197.87        78.13        81.92         0.01
2022-08-10       1900.16   1534.39   1728.75   1240.72    1210.62    1329.57    479.47     339.78     198.00    278.21     175.28        77.71        81.92         0.01
2022-08-11       1900.19   1157.14   1762.85   1236.29    1210.71    1030.80    479.53     339.64     198.04    278.21      35.13        78.44        81.92         0.01
2022-08-12       1900.15   1142.52   1832.74   1235.03    1210.60    1061.85    479.54     339.64     198.08    278.21      35.14        77.23        81.92         0.01
2022-08-13       1890.46   1137.89   1779.75   1237.30    1210.62    1289.60    479.42     339.64     198.13    278.22      90.34        78.06        81.92         0.01
2022-08-14       1900.15   1137.89   1749.45   1231.07    1210.63    1386.38    479.55     339.64     198.18    278.21     221.71        77.86        81.92         0.01
2022-08-15       1900.15   1137.88   1769.66   1232.67    1210.64    1324.38    479.57     339.63     198.22    278.21      51.77        77.65        81.92         0.01
2022-08-16       1900.15   1139.29   1780.08   1232.36    1210.64    1029.58    479.52     339.63     198.27    278.22      51.83        78.04        81.93         0.01
2022-08-17       1900.15   1464.42   1780.93   1237.39    1210.60    1103.22    479.58     354.43     387.35    278.21     107.98        78.46        81.92         0.01
2022-08-18       1900.15   1356.63   1937.99   1229.80    1216.85    1316.66    667.99     357.37     305.02    278.21     173.08        78.30        81.92         0.01
2022-08-19       1890.46   1146.20   1815.77   1229.56    1231.82    1383.88    479.97     357.38     154.55    278.21     173.08        78.19        81.92         0.01
2022-08-20       1900.21   1137.89   1783.08   1242.34    1210.62    1374.06    479.42     393.39     154.60    278.20     189.27        79.25        81.92         0.01
2022-08-21       1900.15   1140.11   1746.58   1250.46    1210.60    1026.26    479.47     380.98     154.65    278.21     197.57        78.94        81.92         0.01
2022-08-22       1900.15   2029.57   1736.61   1251.68    1210.60    1096.59    479.50     378.18     154.70    278.22     173.08        92.26        81.92         0.01
2022-08-23       1900.15   1715.23   1487.77   1248.20    1210.62    1278.06    479.47     399.01     883.99    278.22     175.76       110.99        81.92         0.01
2022-08-24       1900.15   2244.65   1335.71   1251.72    1210.61    1364.39    571.73     422.49    2179.95    278.22     173.07       105.10        81.92         0.01
2022-08-25       1890.45   1787.77   1366.54   1245.16    1200.92    1321.06  15451.43     364.34     269.73    278.22     173.07       170.70        81.92         0.01
2022-08-26       1900.16   2258.65   1357.86   1253.39    1210.64    1026.41   1098.25     357.37     154.59    278.23     173.07        79.50        81.92         0.01
2022-08-27       1900.15   1152.57   1390.85   1257.33    1210.60    1136.54    924.22     357.43     154.62    278.22     173.08        80.94        81.92         0.01
2022-08-28       1900.15   1139.32   1460.57   1252.34    1210.64    1241.27    487.31     441.84     154.67    278.23     171.13       137.46        81.92         0.01
2022-08-29       1551.60   1641.93    708.18    983.84     566.87     825.82    543.71     268.11      54.85    181.72     132.06        53.65        62.25         0.01
Total           60901.42  45757.98  45605.74  39343.08   37918.29   37123.14  33269.26   15901.64    9992.31   8767.92    3844.35      2714.93      2591.41         0.29 
```

### まとめ

この Pythonプログラムを利用することにより、検証環境のAWSの請求金額の日々の推移を管理し、少しでも利用料抑制に役立てればと考えています。この日々の推移を Microsoft Teams に通知するプログラムを追加実装する予定です。

### 参考記事

以下の記事を参考にさせていただきました。感謝申し上げます。
[Cost Explorer API でアカウント毎に日別の請求額を取得する](https://qiita.com/hayao_k/items/3034456883c08325e398)
[pandas結合](https://qiita.com/roadto93ds/items/44eac7c725eac0a6b6a4)