概要
Azureの請求アカウントIDとその配下の顧客ID(テナントID)から請求データを取得するPythonプログラムです。
本プログラムは CostManagementモジュール を利用して作成されていますが、以下の問題がございます(私の力量不足かもしれません)。
- リクエストの返り値が1000件を超える場合、CostManagementモジュールを利用できない
- 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
- Azure AD にて サービスプリンシパル を作成し、以下のアクセス許可を付与する
-
ロールの割当
- 上記サービスプリンシパルに「課金管理者」のロールを割り当てる
- 「コストの管理と請求」サービスの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型で渡す必要があります、、、、