Azure · lesson 10 of 10
Also part of: Multi-Cloud
Azure Cost Management Basics
Break down spend with az costmanagement, weigh real pay-as-you-go vs. reserved instance pricing for one VM size, and skim the Well-Architected cost pillar.
By SkillByExample · Updated
Cost Management answers “what did we spend and on what,” reservations and the Well-Architected Framework’s cost pillar answer “how do we spend less without breaking something else.” All three matter together — cutting cost without reading the trade-off is how a Well-Architected review finds an over-optimized deployment months later.
Reading a cost breakdown
az costmanagement query \
--type ActualCost \
--timeframe MonthToDate \
--scope "/subscriptions/3b1f9c2a-6d4e-4a7b-9c11-2f5e8a0b7d33" \
--dataset-aggregation '{"totalCost":{"name":"PreTaxCost","function":"Sum"}}' \
--dataset-grouping name="ResourceGroup" type="Dimension" \
--output table
$ az costmanagement query --type ActualCost --timeframe MonthToDate --scope "/subscriptions/3b1f9c2a-6d4e-4a7b-9c11-2f5e8a0b7d33" --dataset-aggregation '{"totalCost":{"name":"PreTaxCost","function":"Sum"}}' --dataset-grouping name="ResourceGroup" type="Dimension" --output table
PreTaxCost ResourceGroup Currency
------------ --------------- ----------
14.82 learn-rg USD
2.03 NetworkWatcherRG USD
NetworkWatcherRG is a resource group Azure creates automatically the first time Network
Watcher is used in a region (triggered by the test-ip-flow call in the virtual-network
lesson) — a common source of “where did this resource group come from” confusion in a cost
report.
A real pay-as-you-go vs. reserved instance comparison
Rather than quoting a discount percentage from memory, pull actual current prices from
Azure’s public Retail Prices API for one concrete SKU — Standard_D2s_v5 (2 vCPU, 8 GiB),
Linux, East US:
curl -s "https://prices.azure.com/api/retail/prices?\$filter=armRegionName eq 'eastus' and armSkuName eq 'Standard_D2s_v5' and priceType eq 'Consumption'" \
| jq '.Items[] | select(.productName=="Virtual Machines Dsv5 Series") | {meterName, retailPrice, unitOfMeasure}'
$ curl -s "https://prices.azure.com/api/retail/prices?..." | jq '...'
{
"meterName": "D2s v5",
"retailPrice": 0.096,
"unitOfMeasure": "1 Hour"
}
curl -s "https://prices.azure.com/api/retail/prices?\$filter=armRegionName eq 'eastus' and armSkuName eq 'Standard_D2s_v5' and priceType eq 'Reservation'" \
| jq '.Items[] | {reservationTerm, retailPrice}'
$ curl -s "https://prices.azure.com/api/retail/prices?..." | jq '...'
{ "reservationTerm": "1 Year", "retailPrice": 519.0 }
{ "reservationTerm": "3 Years", "retailPrice": 997.0 }
Fetched from Azure’s live Retail Prices API on 2026-09-13, region eastus, SKU
Standard_D2s_v5. The reservation retailPrice is the total cost for the whole term,
paid upfront — not a per-hour rate — so the comparison needs converting to the same unit:
| Purchase option | Total cost | Effective hourly | vs. pay-as-you-go |
|---|---|---|---|
| Pay-as-you-go | $0.096/hr × 8,760 hr/yr = $840.96/yr | $0.0960 | — |
| 1-Year Reserved | $519.00 for the year | $0.0592 | 38.3% cheaper |
| 3-Year Reserved | $997.00 for 3 years ($332.33/yr) | $0.0379 | 60.5% cheaper |
(8,760 = 24 × 365 hours/year; savings = (840.96 − annualized_reserved) / 840.96.)
The 3-year number looks like the obvious choice, but the trade-off is commitment: a reserved instance bills its full amount whether or not the VM runs, so it only pays off if the workload is genuinely steady for the term. A workload that might be decommissioned in eight months should stay on pay-as-you-go even at the worse hourly rate — this is exactly the kind of cost-vs-flexibility trade-off the Well-Architected Framework’s cost pillar asks you to state explicitly rather than defaulting to “cheapest number.”
The Well-Architected Framework’s five pillars, at a glance
| Pillar | Central question | Where it showed up in this track |
|---|---|---|
| Reliability | Does it recover from failure? | Automatic backups in the Azure SQL lesson |
| Security | Is access no broader than needed? | Scoped RBAC role assignments in the Entra ID lesson |
| Cost Optimization | Are you paying for what you use? | This lesson’s PAYG-vs-reserved comparison |
| Operational Excellence | Can you observe and operate it? | Metrics, KQL, and alerts in the Azure Monitor lesson |
| Performance Efficiency | Does it meet load without over-provisioning? | VM sizing (Standard_B1s vs Standard_D2s_v5) |
The framework’s own point is that these pillars pull against each other: a 3-year reservation improves Cost Optimization but reduces the flexibility that Performance Efficiency wants when load patterns change, and locking RBAC down tightly (Security) can slow down Operational Excellence if every routine task needs a new role request. A Well-Architected review names the trade-off being made, rather than treating one pillar as free to maximize.
Practice
1. Break down the same month's cost by service instead of by resource group.
az costmanagement query --type ActualCost --timeframe MonthToDate --scope "/subscriptions/3b1f9c2a-6d4e-4a7b-9c11-2f5e8a0b7d33" --dataset-aggregation '{"totalCost":{"name":"PreTaxCost","function":"Sum"}}' --dataset-grouping name="ServiceName" type="Dimension" --output table
$ az costmanagement query ... --dataset-grouping name="ServiceName" type="Dimension" --output table
PreTaxCost ServiceName Currency
------------ ------------------- ----------
9.14 Virtual Machines USD
4.02 Storage USD
2.31 SQL Database USD
1.38 Azure Monitor USD
Same query, different --dataset-grouping dimension — cost analysis is one query engine
with the breakdown dimension as a parameter, not a separate report per view.
2. Create a budget that alerts at 80% of a $50 monthly threshold.
az consumption budget create \
--budget-name learn-monthly-budget \
--amount 50 \
--time-grain Monthly \
--start-date 2026-09-01 \
--end-date 2027-09-01 \
--notifications '{"actual_GreaterThan_80_Percent":{"enabled":true,"operator":"GreaterThan","threshold":80,"contactEmails":["reader@example.com"]}}'
$ az consumption budget create --budget-name learn-monthly-budget --amount 50 --time-grain Monthly ...
{
"name": "learn-monthly-budget",
"amount": 50.0,
"timeGrain": "Monthly",
"category": "Cost"
}
Cost analysis only reports after the fact; a budget is the piece that proactively emails before a resource group runs away with spend, which matters most in a lesson environment where a forgotten VM is easy to leave running.
3. Pull pay-as-you-go pricing for the B1s size used in the VM lesson and compare it to D2s_v5.
curl -s "https://prices.azure.com/api/retail/prices?\$filter=armRegionName eq 'eastus' and armSkuName eq 'Standard_B1s' and priceType eq 'Consumption'" | jq '.Items[] | select(.productName=="Virtual Machines B Series") | {meterName, retailPrice}'
$ curl -s "https://prices.azure.com/api/retail/prices?..." | jq '...'
{ "meterName": "B1s", "retailPrice": 0.0104 }
$0.0104/hour vs. $0.096/hour for D2s_v5 — roughly a ninth of the cost, which is exactly why
the free-tier lessons in this track default to Standard_B1s: it’s cheap enough that a
forgotten VM for a day costs cents, not dollars.
4. Find which single resource cost the most this month.
az costmanagement query --type ActualCost --timeframe MonthToDate --scope "/subscriptions/3b1f9c2a-6d4e-4a7b-9c11-2f5e8a0b7d33" --dataset-aggregation '{"totalCost":{"name":"PreTaxCost","function":"Sum"}}' --dataset-grouping name="ResourceId" type="Dimension" --query "sort_by(properties.rows, &[0])[-1]"
$ az costmanagement query ... --query "sort_by(properties.rows, &[0])[-1]"
[8.42, "/subscriptions/.../resourceGroups/learn-rg/providers/Microsoft.Compute/virtualMachines/learn-vm", "USD"]
Grouping by ResourceId instead of ResourceGroup or ServiceName goes down to individual
resources — the level of detail needed once a resource group’s total looks high and the
next question is “which resource inside it.”
5. State a cost-vs-reliability trade-off explicitly, the way a Well-Architected review would.
Decision: run Azure SQL Database on the Basic tier (5 DTU, no zone redundancy) instead of a
zone-redundant Premium tier.
Cost: Basic is billed at a small fraction of Premium's rate.
Reliability given up: no automatic failover across availability zones — a zone outage in
the region causes downtime until Azure recovers the single instance, rather than an
automatic failover to a healthy replica.
Acceptable because: this is a learning environment with no uptime requirement; the same
choice on a production order-processing database would trade away reliability the
business actually needs, and Premium's cost would be justified instead.
This is the format the Well-Architected Framework asks for: not “we chose the cheap option,” but which pillar was given up, by how much, and why that trade was acceptable for this specific workload.
This closes the Azure track. From here: terraform covers provisioning Azure resources
(alongside AWS and GCP) as code with HCL instead of the CLI, and cloud-interviews covers
scenario questions that span all three major clouds.