はじめに
ココネでサービスインフラを担当しているインフラエンジニアのYです。
弊社ではAWS EC2の各リージョンにおける全インスタンスタイプの料金をAWS Athenaやスプレッドシートにまとめて見積もり計算、コスト分析などに活用しているのですが、料金に変更が入ったり新しいインスタンスタイプが追加されるタイミングでの手動更新が大変なので、AWS CLIとPythonを使って料金表の作成を自動化してみました。
料金取得バッチについて
EC2インスタンスの料金情報をAWS CLIを使用してjsonファイルに出力する
AWS CLIでAWS製品の料金情報を検索できるPrice List Query APIが提供されています。
参考:https://docs.aws.amazon.com/ja_jp/awsaccountbilling/latest/aboutv2/using-price-list-query-api.html
aws pricing get-productsコマンドにいくつかのオプションを指定して取得できます。
今回は以下の条件で取得してみます。
- EC2オンデマンドインスタンス
- 東京リージョン
- LinuxとWindows
# AWS CLIでLinuxの価格情報を取得
aws pricing get-products --service-code AmazonEC2 \
--filters 'Type=TERM_MATCH,Field=marketoption,Value=OnDemand' \
'Type=TERM_MATCH,Field=location,Value=Asia Pacific (Tokyo)' \
'Type=TERM_MATCH,Field=operatingSystem,Value=Linux' \
--region us-east-1 --output json > result_linux.json
# AWS CLIでWindowsの価格情報を取得
aws pricing get-products --service-code AmazonEC2 \
--filters 'Type=TERM_MATCH,Field=marketoption,Value=OnDemand' \
'Type=TERM_MATCH,Field=location,Value=Asia Pacific (Tokyo)' \
'Type=TERM_MATCH,Field=operatingSystem,Value=Windows' \
--region us-east-1 --output json > result_windows.json
オプションの説明は以下です。
- -service-code: サービスコードを指定。サービスコード一覧は以下のコマンドでも確認できます
aws pricing describe-services --output=text --region us-east-1 | grep Amazon
- –filters: jsonのフィールドを指定して検索出来ます。調査不足かもですが、そこまで柔軟に指定できないので大まかに取得します。
- –region: APIが情報を取得しに行くリージョンを指定。Price List Query APIは us-east-1 に情報を取得しに行っているのでここは固定になります。
- –output: AWS CLI出力フォーマット。json, yaml, text, table形式を選べます。今回はjsonファイルを生成したいのでjsonを指定します。
少し時間がかかりますが、完了すると以下のようなjsonが取得できます。実際には1万行ほど出ましたので割愛してます。
ひたすら長くて読みづらいですね……情報が多すぎるのでPythonで整形しましょう。
{
"PriceList": [
"{\"product\":{\"productFamily\":\"Compute Instance\",\"attributes\":{\"enhancedNetworkingSupported\":\"Yes\",\"intelTurboAvailable\":\"Yes\",\"memory\":\"32 GiB\",\"dedicatedEbsThroughput\":\"Up to 2120 Mbps\",\"vcpu\":\"8\",\"classicnetworkingsupport\":\"false\",\"capacitystatus\":\"AllocatedCapacityReservation\",\"locationType\":\"AWS Region\",\"storage\":\"EBS only\",\"instanceFamily\":\"General purpose\",\"operatingSystem\":\"Linux\",\"intelAvx2Available\":\"Yes\",\"regionCode\":\"ap-northeast-2\",\"physicalProcessor\":\"Intel Xeon Platinum 8175\",\"clockSpeed\":\"3.1 GHz\",\"ecu\":\"37\",\"networkPerformance\":\"Up to 10 Gigabit\",\"servicename\":\"Amazon Elastic Compute Cloud\",\"instancesku\":\"DEHCSWHPKRSAJY9U\",\"gpuMemory\":\"NA\",\"vpcnetworkingsupport\":\"true\",\"instanceType\":\"m5.2xlarge\",\"tenancy\":\"Shared\",\"usagetype\":\"APN2-Reservation:m5.2xlarge\",\"normalizationSizeFactor\":\"16\",\"intelAvxAvailable\":\"Yes\",\"processorFeatures\":\"Intel AVX; Intel AVX2; Intel AVX512; Intel Turbo\",\"servicecode\":\"AmazonEC2\",\"licenseModel\":\"No License required\",\"currentGeneration\":\"Yes\",\"preInstalledSw\":\"NA\",\"location\":\"Asia Pacific (Seoul)\",\"processorArchitecture\":\"64-bit\",\"marketoption\":\"OnDemand\",\"operation\":\"RunInstances\",\"availabilityzone\":\"NA\"},\"sku\":\"22H5AXKAGJS2MEGG\"},\"serviceCode\":\"AmazonEC2\",\"terms\":{\"OnDemand\":{\"22H5AXKAGJS2MEGG.JRTCKXETXF\":{\"priceDimensions\":{\"22H5AXKAGJS2MEGG.JRTCKXETXF.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"$0.00 per Reservation Linux m5.2xlarge Instance Hour\",\"appliesTo\":[],\"rateCode\":\"22H5AXKAGJS2MEGG.JRTCKXETXF.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"0.0000000000\"}}},\"sku\":\"22H5AXKAGJS2MEGG\",\"effectiveDate\":\"2024-06-01T00:00:00Z\",\"offerTermCode\":\"JRTCKXETXF\",\"termAttributes\":{}}}},\"version\":\"20240703191414\",\"publicationDate\":\"2024-07-03T19:14:14Z\"}",
"{\"product\":{\"productFamily\":\"Compute Instance\",\"attributes\":{\"enhancedNetworkingSupported\":\"Yes\",\"intelTurboAvailable\":\"Yes\",\"memory\":\"32 GiB\",\"dedicatedEbsThroughput\":\"Up to 10000 Mbps\",\"vcpu\":\"16\",\"classicnetworkingsupport\":\"false\",\"capacitystatus\":\"UnusedCapacityReservation\",\"locationType\":\"AWS Region\",\"storage\":\"1 x 950 NVMe SSD\",\"instanceFamily\":\"Compute optimized\",\"operatingSystem\":\"Linux\",\"intelAvx2Available\":\"Yes\",\"regionCode\":\"ap-northeast-2\",\"physicalProcessor\":\"Intel Xeon 8375C (Ice Lake)\",\"clockSpeed\":\"3.5 GHz\",\"ecu\":\"NA\",\"networkPerformance\":\"Up to 12500 Megabit\",\"servicename\":\"Amazon Elastic Compute Cloud\",\"instancesku\":\"HFXMVTYJJR7MRUB4\",\"gpuMemory\":\"NA\",\"vpcnetworkingsupport\":\"true\",\"instanceType\":\"c6id.4xlarge\",\"tenancy\":\"Shared\",\"usagetype\":\"APN2-UnusedBox:c6id.4xlarge\",\"normalizationSizeFactor\":\"32\",\"intelAvxAvailable\":\"Yes\",\"processorFeatures\":\"Intel AVX; Intel AVX2; Intel AVX512; Intel Turbo\",\"servicecode\":\"AmazonEC2\",\"licenseModel\":\"No License required\",\"currentGeneration\":\"Yes\",\"preInstalledSw\":\"SQL Std\",\"location\":\"Asia Pacific (Seoul)\",\"processorArchitecture\":\"64-bit\",\"marketoption\":\"OnDemand\",\"operation\":\"RunInstances:0004\",\"availabilityzone\":\"NA\"},\"sku\":\"22JR4UBHY7FY47V2\"},\"serviceCode\":\"AmazonEC2\",\"terms\":{\"OnDemand\":{\"22JR4UBHY7FY47V2.JRTCKXETXF\":{\"priceDimensions\":{\"22JR4UBHY7FY47V2.JRTCKXETXF.6YS6EN2CT7\":{\"unit\":\"Hrs\",\"endRange\":\"Inf\",\"description\":\"$2.844 per Unused Reservation Linux with SQL Std c6id.4xlarge Instance Hour\",\"appliesTo\":[],\"rateCode\":\"22JR4UBHY7FY47V2.JRTCKXETXF.6YS6EN2CT7\",\"beginRange\":\"0\",\"pricePerUnit\":{\"USD\":\"2.8440000000\"}}},\"sku\":\"22JR4UBHY7FY47V2\",\"effectiveDate\":\"2024-06-01T00:00:00Z\",\"offerTermCode\":\"JRTCKXETXF\",\"termAttributes\":{}}}},\"version\":\"20240703191414\",\"publicationDate\":\"2024-07-03T19:14:14Z\"}"
]
}
今回はリージョン、OS、インスタンスタイプ、1時間あたりの料金、説明(description)の4項目を取得します。
Pythonを使ってjsonファイルをCSVファイルに変換する
Pythonに作成したjsonファイルを渡して、必要な項目のみに絞りCSVファイルを作成します。
import_json.py
import json
import csv
import sys
# 引数としてJSONファイルのパスを受け取る
linux_file = sys.argv[1]
windows_file = sys.argv[2]
# JSONファイルを読み込む
with open(linux_file, 'r') as file:
linux_data = json.load(file)
with open(windows_file, 'r') as file:
windows_data = json.load(file)
# PriceListの各項目をパースする
parsed_linux_data = [json.loads(item) for item in linux_data['PriceList']]
parsed_windows_data = [json.loads(item) for item in windows_data['PriceList']]
# 抽出する項目
fields = ['regionCode', 'operatingSystem', 'instanceType', 'pricePerUnit', 'description']
# 必要なデータを抽出
extracted_data = []
for item in parsed_linux_data + parsed_windows_data:
try:
region_code = item['product']['attributes']['regionCode']
operating_system = item['product']['attributes']['operatingSystem']
instance_type = item['product']['attributes'].get('instanceType', '')
on_demand_key = list(item['terms']['OnDemand'].keys())[0]
price_dimension_key = list(item['terms']['OnDemand'][on_demand_key]['priceDimensions'].keys())[0]
price_per_unit = item['terms']['OnDemand'][on_demand_key]['priceDimensions'][price_dimension_key]['pricePerUnit']['USD']
description = item['terms']['OnDemand'][on_demand_key]['priceDimensions'][price_dimension_key]['description']
# SQL ServerやDedicated Instance, Reservation Instance等も含まれている場合があるので除外
if ("On Demand Linux" in description or
"On Demand Windows" in description or
"On Demand Windows BYOL" in description):
if "with SQL" not in description and "Dedicated" not in description and "Reservation" not in description:
extracted_data.append({
'regionCode': region_code,
'operatingSystem': operating_system,
'instanceType': instance_type,
'pricePerUnit': price_per_unit,
'description': description
})
except KeyError as e:
print(f"キーエラー: {e} が見つかりませんでした。JSONデータの構造を確認してください。")
except TypeError as e:
print(f"型エラー: {e} が発生しました。JSONデータの構造を確認してください。")
# CSVファイルに書き込む
with open('output.csv', mode='w', newline='') as file:
writer = csv.DictWriter(file, fieldnames=fields)
# ヘッダーを書き込む
writer.writeheader()
# データを書き込む
for item in extracted_data:
writer.writerow(item)
print("CSVファイルが作成されました。")
AWS CLIとPythonをシェルスクリプトにまとめます。
せっかくなのでリージョンを引数としてシェルスクリプトに渡せるようにしてみます。
get_ec2_ondemand_cost.sh
#!/bin/sh
# 引数として地域を受け取る。デフォルトは "Asia Pacific (Tokyo)"
REGION=${1:-"Asia Pacific (Tokyo)"}
# AWS CLIコマンドでLinuxの価格情報を取得
aws pricing get-products --service-code AmazonEC2 \
--filters 'Type=TERM_MATCH,Field=termType,Value=OnDemand' \
'Type=TERM_MATCH,Field=location,Value='"${REGION}" \
'Type=TERM_MATCH,Field=operatingSystem,Value=Linux' \
--region us-east-1 --output json > result_linux.json
# AWS CLIコマンドでWindowsの価格情報を取得
aws pricing get-products --service-code AmazonEC2 \
--filters 'Type=TERM_MATCH,Field=termType,Value=OnDemand' \
'Type=TERM_MATCH,Field=location,Value='"${REGION}" \
'Type=TERM_MATCH,Field=operatingSystem,Value=Windows' \
--region us-east-1 --output json > result_windows.json
# Pythonスクリプトを実行してJSONをCSVに変換
python import_json.py result_linux.json result_windows.json
実行
# 引数なしの場合は東京リージョン
$ sh get_ec2_ondemand_cost.sh
# 引数でリージョン指定できる
$ sh get_ec2_ondemand_cost.sh "Asia Pacific (Seoul)"
output.csvが出力されればOK。
$ more output.csv
regionCode,operatingSystem,instanceType,pricePerUnit,description
ap-northeast-2,Linux,r5dn.24xlarge,9.5520000000,$9.552 per On Demand Linux r5dn.24xlarge Instance Hour
ap-northeast-2,Linux,c6i.large,0.0960000000,$0.096 per On Demand Linux c6i.large Instance Hour
ap-northeast-2,Linux,p2.8xlarge,11.7200000000,$11.72 per On Demand Linux p2.8xlarge Instance Hour
ap-northeast-2,Linux,c6gn.medium,0.0487500000,$0.04875 per On Demand Linux c6gn.medium Instance Hour
ap-northeast-2,Linux,i4i.metal,12.8830000000,$12.883 per On Demand Linux i4i.metal Instance Hour
ap-northeast-2,Linux,c5d.4xlarge,0.8800000000,$0.88 per On Demand Linux c5d.4xlarge Instance Hour
ap-northeast-2,Linux,r5d.24xlarge,8.3040000000,$8.304 per On Demand Linux r5d.24xlarge Instance Hour
ap-northeast-2,Linux,m6g.8xlarge,1.5040000000,$1.504 per On Demand Linux m6g.8xlarge Instance Hour
ap-northeast-2,Linux,c6gd.4xlarge,0.7040000000,$0.704 per On Demand Linux c6gd.4xlarge Instance Hour
ap-northeast-2,Linux,m5.xlarge,0.2360000000,$0.236 per On Demand Linux m5.xlarge Instance Hour
ap-northeast-2,Linux,r6gd.4xlarge,1.1072000000,$1.1072 per On Demand Linux r6gd.4xlarge Instance Hour
あとはCSVをスプレッドシートやAWS Athenaにインポートして自由に活用出来ます。
おわりに
EC2インスタンスの料金をバッチ1発で取得しCSVに出力する方法をまとめました。
フィルタ条件などを調整すれば別サービスの料金等も調べられますし、Price List Query APIは活用の幅が広いと思います。
AWSのコスト分析など担当されている皆様のお役に立てれば幸いです。

