1
1

More than 1 year has passed since last update.

Pythonで Azureの請求アカウントIDから請求データを取得してみました - CostManagementモジュール編

Last updated at Posted at 2022-10-18

概要

Azureの請求アカウントIDとその配下の顧客ID(テナントID)から請求データを取得するPythonプログラムです。
本プログラムは CostManagementモジュール を利用して作成されていますが、以下の問題がございます(私の力量不足かもしれません)。

  1. リクエストの返り値が1000件を超える場合、CostManagementモジュールを利用できない
  2. CostManagementモジュールで利用するAPIバージョンを指定することができず、古いバージョンのため取得できない項目がある

REST-API利用する場合、上記2点の問題は発生しません。

実行環境

macOS Monterey 12.3.1
python 3.8.12

事前準備

  • Pythonプログラムの実行に必要なサービスプリンシパルの作成

    • Azure AD にて サービスプリンシパル を作成し、以下のアクセス許可を付与する
      • Microsoft Graph/アプリケーションの許可/Directory.AccessAsRead.All
      • Microsoft Pertner Center/アプリケーションの許可/user_impersonation
      • Azure Service Management/アプリケーションの許可/user_impersonation
  • ロールの割当

    • 上記サービスプリンシパルに「課金管理者」のロールを割り当てる
    • 「コストの管理と請求」サービスのIAMにおいて、上記サービスプリンシパルに閲覧者のロールを割り当てる
  • 各種情報の入手

    • 請求アカウントID
    • 請求書アカウントが属するテナントのID
    • 上記サービスプリンシパルのアプリケーションIDとシークレット
    • 顧客ID(顧客のテナントID)

実行プログラム

CM_QueryTimePeriodByCustomerId.py
import os
import argparse
import pandas as pd
from datetime import *
import time
from azure.mgmt.costmanagement import CostManagementClient
from azure.mgmt.costmanagement.models import QueryTimePeriod
from azure.identity import ClientSecretCredential

# 請求管理者権限を付与されたサービスプリンシパル情報
TENANT_ID = os.environ['APC_TENANT_ID']
CLIENT_ID = os.environ['APC_CLIENT_ID']
CLIENT_KEY = os.environ['APC_CLIENT_KEY']

# 請求アカウントID
BILLING_ACCOUNT_ID = os.environ['BILLING_ACCOUNT_ID']

# 顧客ID
CUSTOMER_ID = 'xxxxxxxx-1111-2222-3333-xxxxxxxxxxxx'
# CUSTOMER_ID = 'yyyyyyyy-4444-5555-6666-yyyyyyyyyyyy'
# CUSTOMER_ID = 'zzzzzzzz-7777-8888-9999-zzzzzzzzzzzz'


# CostManagement情報 を操作するオブジェクトを取得
def GetCostManagementObject():
    credentials = ClientSecretCredential(
       tenant_id = TENANT_ID,
       client_id = CLIENT_ID,
       client_secret = CLIENT_KEY
    )
    return CostManagementClient(credentials)


# SCOPEの定義
def define_scope() :
    def_scope = f'/providers/Microsoft.Billing/billingAccounts/{BILLING_ACCOUNT_ID}/customers/{CUSTOMER_ID}'
    return def_scope


# Time_Periodの定義
def define_period(fm_tp, to_tp) :
    fm_datetime = datetime.strptime(fm_tp, '%Y-%m-%d')
    to_datetime = datetime.strptime(to_tp, '%Y-%m-%d')
    def_period = QueryTimePeriod(from_property = fm_datetime, to = to_datetime)
    return def_period


# クエリの定義
def define_query(fm_tp, to_tp) :

    # データ取得期間の定義
    def_period = define_period(fm_tp, to_tp)

    # クエリの作成
    def_query = {
        "type": "Usage",
        "timeframe": "Custom",
        "time_period": def_period,
        "dataset": {
            "granularity": "None",
            "aggregation": {
                "totalCost": {"name": "PreTaxCost", "function": "Sum"},
                "totalQuantity": {"name": "UsageQuantity", "function": "Sum"}
            },
            "grouping": [
                # {"type": "Dimension", "name": "ResellerMPNId"},
                # {"type": "Dimension", "name": "InvoiceId"},
                # {"type": "Dimension", "name": "ResourceGroup"},
                {"type": "Dimension", "name": "ServiceFamily"},
                {"type": "Dimension", "name": "ServiceName"}
                # {"type": "Dimension", "name": "MeterCategory"},
                # {"type": "Dimension", "name": "Meter"},
                # # {"type": "Dimension", "name": "MeterSubcategory"},
                # {"type": "Dimension", "name": "Product"},
                # {"type": "Dimension", "name": "UnitOfMeasure"},
                # {"type": "Dimension", "name": "ProductOrderName"}
             ]
        }
    }
    return def_query


# 指定した Subscription について CostManagement からコストを取得
def GetCostManagement(fm_tp, to_tp):

    # CostManagementを操作するオブジェクトの取得
    client = GetCostManagementObject()

    # SCOPEの定義
    def_scope = define_scope()

    # クエリの定義
    def_query = define_query(fm_tp, to_tp)

    # CostManagementから請求データの取得
    costmanagement = client.query.usage(scope = def_scope, parameters=def_query)
    client.close()
    return costmanagement


# とある顧客の指定した期間でもCostManagement情報を取得
def GetCustomerCsotManagement(fm_tp, to_tp):

    # CostManagement からコストを取得
    costmanagement = GetCostManagement(fm_tp, to_tp)

    # columnsデータを取得
    columns = list(map(lambda col: col.name, costmanagement.columns))

    # rowsデータを取得
    rows = costmanagement.rows
    
    # 請求データをDataframe型で取得
    df_cost = pd.DataFrame(rows, columns=columns)
    return df_cost


# 取得した全データの画面出力
def data_to_display(df_cost):
    # 取得した全データの表示
    df_cost = df_cost.drop(columns=['Currency'])
    print(df_cost.to_markdown(floatfmt=",.0f"))
    print(df_cost.dtypes)


# 取得した全データの保存
def data_to_csv(df_cost):
    # 取得した全データのcsvファイルへ保存
    now = datetime.now()
    filename = './data_customers_cost_by_billingid/' + now.strftime('%Y%m%d%H%M%S') + '.csv'
    print(filename)
    df_cost.to_csv(filename, index=False, encoding="utf-8")


# データ取得のための From-To 日付確認
def check_time_period(fm_tp, to_tp):
    try :
        fm_datetime = datetime.strptime(fm_tp, '%Y-%m-%d')
        to_datetime = datetime.strptime(to_tp, '%Y-%m-%d')

        if to_datetime < fm_datetime :
            print("Error ---> 開始日 ≦ 終了日 ")
            return False
        else :
            return True
    except ValueError as e :
        print("ValueError = ", e)
        return False


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='ある顧客の請求情報の取得')
    parser.add_argument('-f', '--fm', type=str, required=True, help='データを取得する開始日(yyyy-mm-dd)')
    parser.add_argument('-t', '--to', type=str, required=True, help='データを取得する終了日(yyyy-mm-dd)')
    parser.add_argument('--mode', type=str, default='lo', help='lo(取得データを画面出力) / csv(取得データをcsv保存)')
    args = parser.parse_args()

    if check_time_period(args.fm, args.to) == False:
        print("NG", "\n")
    else :
        start = time.time()
        df_cost = GetCustomerCsotManagement(args.fm, args.to)

        if args.mode == 'csv':
            # 取得した全データを保存
            data_to_csv(df_cost)
        else :
            # 取得した全データを出力
            data_to_display(df_cost)

        # 合計請求金額と件数の表示
        total = df_cost['PreTaxCost'].sum()
        count = len(df_cost)
        print("\n", "合計請求金額 : {:,.0f}".format(total), "\tデータ件数 : {:,.0f}".format(count))

        generate_time = time.time() - start
        print("\n 取得時間:{0}".format(generate_time) + " [sec] \n")

プログラムのHELP表示

$ python CM_QueryTimePeriodByCustomerId.py -h                         
usage: CM_QueryTimePeriodByCustomerId.py [-h] [-f FM] [-t TO] [--mode MODE]

ある顧客の請求情報の取得

optional arguments:
  -h, --help      show this help message and exit
  -f FM, --fm FM  データを取得する開始日(yyyy-mm-dd)
  -t TO, --to TO  データを取得する終了日(yyyy-mm-dd)
  --mode MODE     lo(取得データを画面出力) / csv(取得データをcsv保存)

プログラムの実行(画面表示)

$ python CM_QueryTimePeriodByCustomerId.py -f 2022-09-01 -t 2022-09-15
Datetime with no tzinfo will be considered UTC.
Datetime with no tzinfo will be considered UTC.
|    |   PreTaxCost |   UsageQuantity | ServiceFamily             | ServiceName                            |
|---:|-------------:|----------------:|:--------------------------|:---------------------------------------|
|  0 |           18 |               0 | AI + Machine Learning     | Cognitive Services                     |
|  1 |            0 |               0 | Analytics                 | Azure Data Factory v2                  |
|  2 |        5,725 |             407 | Compute                   | Azure App Service                      |
|  3 |            0 |             644 | Compute                   | Functions                              |
|  4 |       26,069 |           1,923 | Compute                   | Virtual Machines                       |
|  5 |           82 |              12 | Compute                   | Virtual Machines Licenses              |
|  6 |          971 |           1,340 | Containers                | Container Registry                     |
|  7 |           32 |              32 | Databases                 | Azure Cosmos DB                        |
|  8 |       14,250 |           1,095 | Databases                 | Azure Database for PostgreSQL          |
|  9 |          675 |             259 | Databases                 | Redis Cache                            |
| 10 |          316 |              15 | Databases                 | SQL Database                           |
| 11 |          440 |           1,844 | Integration               | Logic Apps                             |
| 12 |            0 |               0 | Integration               | Service Bus                            |
| 13 |          653 |             236 | Internet of Things        | Event Hubs                             |
| 14 |          245 |              51 | Management and Governance | Azure Monitor                          |
| 15 |            0 |               1 | Management and Governance | Log Analytics                          |
| 16 |       14,566 |             458 | Networking                | Azure Bastion                          |
| 17 |          606 |              10 | Networking                | Azure DNS                              |
| 18 |            0 |               4 | Networking                | Bandwidth                              |
| 19 |        3,015 |           1,480 | Networking                | Load Balancer                          |
| 20 |            0 |              31 | Networking                | Network Watcher                        |
| 21 |       11,065 |          16,494 | Networking                | Virtual Network                        |
| 22 |        7,947 |             360 | Networking                | VPN Gateway                            |
| 23 |        6,272 |             360 | Security                  | Azure Active Directory Domain Services |
| 24 |            0 |               0 | Security                  | Key Vault                              |
| 25 |          379 |              11 | Storage                   | Backup                                 |
| 26 |       56,861 |           2,438 | Storage                   | Storage                                |
PreTaxCost       float64
UsageQuantity    float64
ServiceFamily     object
ServiceName       object
dtype: object

 合計請求金額 : 150,187 	データ件数 : 27

 取得時間:14.547715187072754 [sec] 

プログラムの実行(csvファイル保存)

$ python CM_QueryTimePeriodByCustomerId.py -f 2022-09-01 -t 2022-09-15 --mode csv
Datetime with no tzinfo will be considered UTC.
Datetime with no tzinfo will be considered UTC.
./data_customers_cost_by_billingid/20221019004416.csv

 合計請求金額 : 150,187 	データ件数 : 27

 取得時間:10.971143245697021 [sec] 

作成された csvファイルの確認

## ファイル名の確認
$ ls -l data_customers_cost_by_billingid                                            
total 8
drwxr-xr-x  3 ituru  staff    96 10 19 00:44 ./
drwxr-xr-x  5 ituru  staff   160 10 18 17:03 ../
-rw-r--r--  1 ituru  staff  1657 10 19 00:44 20221019004416.csv

## ファイルの中身の確認
$ cat data_customers_cost_by_billingid/20221019004416.csv 
     1	PreTaxCost,UsageQuantity,ServiceFamily,ServiceName,Currency
     2	17.50078003120125,0.027188172043010755,AI + Machine Learning,Cognitive Services,JPY
     3	0.0,0.00048,Analytics,Azure Data Factory v2,JPY
     4	5725.319198374109,406.556664,Compute,Azure App Service,JPY
     5	0.0,643.543625,Compute,Functions,JPY
     6	26069.087660966645,1922.551374,Compute,Virtual Machines,JPY
     7	81.709678368424,11.766742,Compute,Virtual Machines Licenses,JPY
     8	970.5452294659273,1339.796035,Containers,Container Registry,JPY
     9	32.0,32.0,Databases,Azure Cosmos DB,JPY
    10	14249.570321745208,1094.7081044514973,Databases,Azure Database for PostgreSQL,JPY
    11	675.031348809556,259.21666666666664,Databases,Redis Cache,JPY
    12	316.2430939226519,15.000000000000014,Databases,SQL Database,JPY
    13	440.1111821965594,1844.051618530415,Integration,Logic Apps,JPY
    14	0.0,0.000692,Integration,Service Bus,JPY
    15	653.0466945110688,236.063313,Internet of Things,Event Hubs,JPY
    16	244.68841904459103,51.40737007168459,Management and Governance,Azure Monitor,JPY
    17	0.0,1.0262113914764017,Management and Governance,Log Analytics,JPY
    18	14566.386574041495,457.60956586782714,Networking,Azure Bastion,JPY
    19	606.2915004326267,10.468534810633235,Networking,Azure DNS,JPY
    20	0.0,4.077179137475253,Networking,Bandwidth,JPY
    21	3014.7857758902032,1479.826889735452,Networking,Load Balancer,JPY
    22	0.0,31.0,Networking,Network Watcher,JPY
    23	11064.862557472155,16494.0390586638,Networking,Virtual Network,JPY
    24	7947.0,360.0,Networking,VPN Gateway,JPY
    25	6271.999999999999,359.9,Security,Azure Active Directory Domain Services,JPY
    26	0.4986862848134524,0.2847,Security,Key Vault,JPY
    27	378.9713721445092,11.07577962670219,Storage,Backup,JPY
    28	56861.17015396144,2437.87818,Storage,Storage,JPY

まとめ

これで CostManagementモジュール を利用して Azureの請求データを取得することができました。あとは要望に合わせてクエリ・リクエスト文を編集してください。
なお、CostManagementモジュール を利用する場合の time_period には datetime型で渡す必要があります、、、、

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1