Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save buzztaiki/d53dcd62c8d4a93ca42de8aebf63a61f to your computer and use it in GitHub Desktop.
Save buzztaiki/d53dcd62c8d4a93ca42de8aebf63a61f to your computer and use it in GitHub Desktop.
Azure VM の料金やスポット退去率を出すスクリプト

Azure VM の料金やスポット退去率を出すスクリプト

このへんを見ながら作ったやつ

❯❯ azure-vm-prices -o /tmp/vm_prices/ /tmp/vm_prices/ D4as_v5 D4ads_v5
Retrieving VM spec and price informations to /tmp/vm_prices/...
Retrieving VM spec and price informations to /tmp/vm_prices/...done
❯❯ ls -l /tmp/vm_prices
total 16
-rw-r--r-- 1 taiki taiki 131 Jul  8 02:30 disk_prices.tsv
-rw-r--r-- 1 taiki taiki 779 Jul  8 02:30 spots_azgraph_query.txt
-rw-r--r-- 1 taiki taiki 120 Jul  8 02:30 vm_prices.tsv
-rw-r--r-- 1 taiki taiki 131 Jul  8 02:30 vm_specs.tsv

spots_azgraph_query.txt は Azure Portal の Azure Resource Graph Explorer にコピペしてCSVでエクスポートして使う。az graph query だと SpotResources からの情報が取れなかった。

取れる情報

こんな感じのが取れる

vm_specs

sku zones vcpus memory_gb ephemeral_os_disk_gb premium_io
Standard_D4ads_v5 1,3,2 4 16 150 True
Standard_D4as_v5 1,3,2 4 16 0 True

vm_prices

sku price_usd reserved_price_usd
Standard_D4ads_v5 0.268 0.15810502283105024
Standard_D4as_v5 0.224 0.13219178082191782

spots

sku price_usd eviction_rate
standard_d4as_v5 0.03584 5-10
standard_d4ads_v5 0.04288 10-15

disk_prices

sku price_usd_month
E10 LRS 9.6
E15 LRS 19.2
E20 LRS 38.4
E30 LRS 76.8
P10 LRS 22.67
P15 LRS 43.720714
P20 LRS 84.2
P30 LRS 155.44
#!/bin/bash
#shellcheck disable=SC2002,SC2155
set -eu -o pipefail
declare -r standard_skus_cache=/tmp/vm-standard-skus.json
declare -r default_output_dir=.
declare -r default_location=japaneast
output_dir=$default_output_dir
location=$default_location
vm_skus=()
usage_exit() {
local exit_code="$1"
cat <<EOF
Usage: $0 [-d output_dir] [vm_sku ...]
Options:"
-o, --output-dir <dir> Directory to save output files (default: $default_output_dir).
-l, --location <loc> Location (default: $default_location).
-h, --help Show this help message and exit.
Example:
$0 -o /tmp/vm_prices D4ads_v6 D4as_v5 D4ds_v5 D4_v4
EOF
exit "$exit_code"
}
parse_args() {
local args=()
while [[ $# -ne 0 ]]; do
case "$1" in
-h|--help) usage_exit 0 ;;
-o|--output-dir) output_dir="$2"; shift ;;
-l|--location) location="$2"; shift ;;
-*) log "unknown option: $1"; usage_exit 1 1>&2 ;;
*) args+=("$1") ;;
esac
shift
done
vm_skus=("${args[@]}")
}
log() {
echo "$@" 1>&2
}
join() {
local delim="$1"; shift
printf '%s\n' "$@" | paste -s -d"$delim"
}
sort_without_header() {
awk 'NR==1 {print; next} {print | "sort"}'
}
vm_specs() {
local sku_re="Standard_($(join '|' "${vm_skus[@]}"))"
if ! [[ -f "$standard_skus_cache" ]]; then
log "fetching VM specs from Azure..."
az vm list-skus -l "$location" --size 'standard' --output json > "$standard_skus_cache"
log "fetching VM specs from Azure...done"
fi
#shellcheck disable=SC2016
local jq_query='
.[]
| select(.name | test($sku_re))
| (.capabilities | map({key:.name,value}) | from_entries) as $full_caps
| (
$full_caps | {
vCPUs, MemoryGB, PremiumIO, vCPUsAvailable,
EphemeralOSDiskGB:(.NvmeSizePerDiskInMiB//.MaxResourceVolumeMB)|tonumber/1024
}
) as $caps
| [
.name, (.locationInfo[0].zones|join(",")),
$caps.vCPUs, $caps.MemoryGB, $caps.EphemeralOSDiskGB, $caps.PremiumIO
]
| @tsv
'
join $'\t' sku zones vcpus memory_gb ephemeral_os_disk_gb premium_io
cat "$standard_skus_cache" | jq --arg sku_re "$sku_re" "$jq_query" -r | sort_without_header
}
spots_azgraph_query() {
local sku_re="Standard_($(join '|' "${vm_skus[@]}"))"
cat <<EOF
(
SpotResources
| where type =~ 'microsoft.compute/skuspotpricehistory/ostype/location'
| where properties.osType =~ 'linux'
| where sku.name matches regex "(?i)$sku_re"
| where location =~ "$location"
| project name = strcat(sku.name, "/", location), sku = tostring(sku.name), location, price_usd = properties.spotPrices[0].priceUSD
)
| join kind=leftouter (
SpotResources
| where type =~ 'microsoft.compute/skuspotevictionrate/location'
| project name = strcat(sku.name, "/", location), eviction_rate = tostring(properties.evictionRate)
) on name
| project sku, price_usd, eviction_rate = coalesce(eviction_rate, "<none>")
| sort by sku
EOF
}
vm_prices() {
local sku
for sku in "${vm_skus[@]}"; do
(
local arm_filter="
serviceName eq 'Virtual Machines'
and armRegionName eq '$location'
and isPrimaryMeterRegion eq true
and armSkuName eq 'Standard_$sku'"
curl -sSf https://prices.azure.com/api/retail/prices --url-query "\$filter=$arm_filter"
)
done | (
#shellcheck disable=SC2016
local jq_query='
[
.Items[]
| select((.type == "Consumption") or (.type == "Reservation" and .reservationTerm == "1 Year"))
| select(.productName|test("Windows|Cloud Services")|not)
| select(.skuName|test("Spot|Low Priority")|not)
]
| group_by(.armSkuName, .armRegionName)[]
| map(select(.type == "Consumption"))[0] as $cn
| map(select(.type == "Reservation"))[0] as $rs
| [$cn.armSkuName, $cn.unitPrice, $rs.unitPrice/365/24]|@tsv
'
join $'\t' sku price_usd reserved_price_usd
jq "$jq_query" -r
) | sort_without_header
}
disk_prices() {
(
local arm_filter="
serviceName eq 'Storage'
and (productName eq 'Premium SSD Managed Disks' or productName eq 'Standard SSD Managed Disks')
and armRegionName eq '$location'
and isPrimaryMeterRegion eq true
and type eq 'Consumption'
"
curl -sSf https://prices.azure.com/api/retail/prices --url-query "\$filter=$arm_filter"
) | (
local jq_query='
.Items[]
| select(.skuName|test("LRS"))
| select(.skuName|test("10|15|20|30"))
| select(.meterName|test("LRS Disk$"))
| [.skuName, .unitPrice]|@tsv
'
join $'\t' sku price_usd_month
jq "$jq_query" -r
) | sort_without_header
}
main() {
parse_args "$@"
log "Retrieving VM spec and price informations to $output_dir..."
vm_specs > "$output_dir/vm_specs.tsv"
spots_azgraph_query > "$output_dir/spots_azgraph_query.txt"
vm_prices > "$output_dir/vm_prices.tsv"
disk_prices > "$output_dir/disk_prices.tsv"
log "Retrieving VM spec and price informations to $output_dir...done"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment