Cost management

Visibility, reservations, right-sizing, waste.

Advanced25 min · lesson 15 of 15

Running servers in your own building is like renting an office: the rent is the same whether every desk is full or the place is empty. Azure works like metered electricity instead. Every VM (virtual machine), every disk, every public IP address (the internet-facing address people connect to) is an appliance with its own meter, and that meter spins whether or not anyone is using the thing. That is why cost is an *administration* job and not something finance sorts out later. The people who create resources are the only people who can make the bill go down. Azure Cost Management is the built-in tool set for the work: Cost Analysis to explore where the money went, budgets to shout at you when a threshold is crossed, scheduled exports to feed your own reporting. Three habits make it work. Read the meters. Move predictable work onto a cheaper tariff. Unplug whatever is idle. This last lesson turns those habits into commands.

How a charge actually happens

Every resource quietly files *usage records* against a meter, and a meter is a priced unit of something, the same way your electricity bill prices kilowatt-hours. Azure's version reads like "D2s v5 compute, per hour, West Europe" or "Hot LRS blob storage, per GB-month" (LRS is locally redundant storage, the cheapest way Azure keeps copies of your data). A rating engine multiplies each record by the unit price on your subscription's price sheet, and that sheet is not the same for everyone: it changes with the offer type, whether that is pay-as-you-go, an Enterprise Agreement, or a CSP (Cloud Solution Provider, a reseller you buy Azure through). The result lands in Cost Management. Two things follow. The first is latency. Usage normally takes 8 to 24 hours to appear on Enterprise Agreement and Microsoft Customer Agreement accounts, and as long as 72 hours on pay-as-you-go, so "current spend" is always yesterday's news. The second is attribution. A charge carries a tag only from the moment that tag exists. Tagging a VM today does nothing to last month's spend, which is why the tagging policy from the governance lesson has to be running on day one. Cost Analysis in the portal is the everyday face of this data. From the CLI (command line interface), the costmanagement extension now only wraps scheduled exports, so ad-hoc questions go straight to the Cost Management Query API (application programming interface, the endpoint the portal itself calls), and az rest signs the request for you:

month-to-date spend by resource group
# actual cost so far this month, grouped by resource group —
# the same query Cost Analysis runs, called directly
SUB=$(az account show --query id -o tsv)
az rest --method post \
--url "https://management.azure.com/subscriptions/$SUB/providers/Microsoft.CostManagement/query?api-version=2025-03-01" \
--body '{
"type": "ActualCost",
"timeframe": "MonthToDate",
"dataset": {
"granularity": "None",
"aggregation": {"totalCost": {"name": "PreTaxCost", "function": "Sum"}},
"grouping": [{"name": "ResourceGroup", "type": "Dimension"}]
}
}' \
--query "properties.{columns: columns[].name, rows: rows}"
# {
# "columns": ["PreTaxCost", "ResourceGroup", "Currency"],
# "rows": [
# [412.07834212, "rg-prod-web", "USD"],
# [187.55120133, "rg-prod-data", "USD"],
# [96.20411806, "rg-dev", "USD"],
# [3.41200914, "networkwatcherrg", "USD"]
# ]
# }

The type field earns its keep on the exam and in real spend reviews. ActualCost shows charges the way they were billed, so a three-year reservation lands as one enormous spike on the day you bought it. AmortizedCost smears that same purchase evenly across the three years, and that is the view you want when the question is whether a team's monthly run rate looks healthy. Change the grouping to the ServiceName dimension, or to {"name": "cost-center", "type": "TagKey"}, and you stop asking *where* the money sits and start asking *who* owns it.

Budgets are alarms, not brakes

A budget is a smoke alarm. It is not a circuit breaker. Technically it is a spend threshold set at a scope you choose (a management group, a subscription, or a single resource group) with a monthly, quarterly, or annual reset, and Azure re-checks it against fresh cost data roughly once a day. When actual spend or *forecasted* spend crosses a percentage you picked, say 80% actual and 100% forecasted, Cost Management sends notifications to email addresses or to an action group, the same alerting plumbing you wired up in the Azure Monitor lesson. Forecasted alerts are the half everybody ignores. They tell you in the middle of the month that you are on track to blow past the cap, while there is still time to do something about it. Now the part the exam likes to poke at: a budget never caps spending. The one feature that genuinely stops consumption is the spending limit, and it exists only on credit-based subscriptions such as a free trial or Visual Studio benefits. Never on pay-as-you-go. Never on an Enterprise Agreement.

create a subscription budget
# a monthly budget on the current subscription
# (start date must be the first of a month; the consumption
# command group is in preview, but it is the CLI's budget surface)
az consumption budget create \
--budget-name bgt-sub-monthly \
--amount 1500 \
--category cost \
--time-grain monthly \
--start-date 2026-07-01 \
--end-date 2028-06-30
# {
# "amount": 1500.0,
# "category": "Cost",
# "name": "bgt-sub-monthly",
# "timeGrain": "Monthly",
# "timePeriod": {
# "endDate": "2028-06-30T00:00:00+00:00",
# "startDate": "2026-07-01T00:00:00+00:00"
# },
# "type": "Microsoft.Consumption/budgets"
# }

The plain create command builds the shell of the budget. The notification blocks (thresholds, recipients, action group) are attached afterwards in the portal, passed to az consumption budget update --notifications, or declared in a Bicep Microsoft.Consumption/budgets resource. Bicep is Azure's own infrastructure-as-code language, and if you want the budget reviewed and version controlled like any other change, that is where it belongs, exactly as it did for policy assignments.

A budget will not stop the spending
Cost data runs 8 to 24 hours behind, up to 72 hours on pay-as-you-go, and all a budget does is send notifications. A wrongly sized GPU (graphics processing unit) VM, or a Log Analytics workspace swallowing far more logs than anyone expected, can burn through hundreds of dollars before the first email lands in anyone's inbox. If a dev/test subscription needs a real backstop, point the budget's action group at an Automation runbook or an Azure Function that deallocates tagged VMs. And keep in mind that a deallocated VM still bills you for its disks and its static public IP addresses.

Paying less for the same compute

Visibility tells you where the money went. Pricing levers change how much leaves in the first place, and they behave like the tariffs on any utility bill. Reservations are the fixed contract: commit to a VM family in a region for one or three years and pay up to about 72% less than pay-as-you-go. Savings plans for compute commit you to an hourly dollar amount instead of a particular machine, so the discount floats across VM families, regions, and App Service. You give up a little discount (up to roughly 65%) in exchange for that freedom. The knob AZ-104 (the Azure Administrator Associate exam) cares about is scope. A reservation can apply to one resource group, one subscription, a management group, or be *shared* across the whole billing context. Shared scope wrings the most out of a commitment, because any matching VM anywhere can consume it. Azure Hybrid Benefit stacks on top of all of it: bring Windows Server or SQL Server licenses that are covered by Software Assurance and you stop paying the license slice of the VM rate. Then there is work that can die halfway through and nobody cries, things like batch jobs, CI (continuous integration) build agents, and video rendering. Spot VMs sell Azure's leftover capacity for up to about 90% off, with one catch. Azure can take the machine back on 30 seconds' notice whenever it wants that capacity for a paying customer.

a Spot VM for interruptible work
az vm create \
--resource-group rg-dev \
--name vm-batch-01 \
--image Ubuntu2204 \
--size Standard_D2s_v5 \
--priority Spot \
--eviction-policy Deallocate \
--max-price -1 \
--admin-username azureuser \
--generate-ssh-keys
# {
# "powerState": "VM running",
# "publicIpAddress": "20.234.16.87",
# ...
# }
# confirm the billing profile stuck:
az vm show -g rg-dev -n vm-batch-01 \
--query "{priority:priority, evictionPolicy:evictionPolicy, maxPrice:billingProfile.maxPrice}"
# { "priority": "Spot", "evictionPolicy": "Deallocate", "maxPrice": -1.0 }

--max-price -1 means "charge me up to the normal pay-as-you-go rate and never evict me over price". Eviction for capacity can still hit you at any second. --eviction-policy Deallocate keeps the disks so the job can pick up where it stopped. Delete costs less because it throws them away. The rule of thumb is short. Steady round-the-clock baseline, commit. Variable business-hours load, pay-as-you-go. Interruptible, Spot.

Hunting waste

Azure Advisor is a free recommendation engine, a bit like the utility company writing to point out that the second fridge in your garage is running empty. It watches utilization telemetry and flags cost defects. It reads the last seven days of CPU and outbound network metrics (you can stretch that lookback to 14, 30, 60, or 90 days per subscription), then recommends shutting down VMs that are doing essentially nothing, and resizing ones whose real load would fit a cheaper SKU (stock keeping unit, Azure's word for a specific size and tier). It also suggests reservation purchases your own usage history already justifies. Advisor is good at finding the *oversized*. The CLI is how you find the *orphaned*. Two silent leaks turn up again and again. Unattached managed disks bill at their full provisioned size even when no VM exists anywhere near them. Unassociated public IP addresses bill by the hour while pointing at nothing at all. Both are the debris left behind when someone deletes a VM but not the pieces hanging off it. Round it off with auto-shutdown on dev/test VMs: deallocating them overnight stops the compute charge, though the disks keep billing. Deallocation is not deletion.

the waste hunt
# Advisor's cost recommendations
az advisor recommendation list --category Cost \
--query "[].{impact:impact, problem:shortDescription.problem}" -o table
# Impact Problem
# -------- --------------------------------------------------------------------
# High Right-size or shutdown underutilized virtual machines
# Medium Buy virtual machine reserved instances to save money over pay-as-you-go costs
# disks nothing is attached to (still billed every month)
az disk list \
--query "[?diskState=='Unattached'].{name:name, rg:resourceGroup, GiB:diskSizeGB, sku:sku.name}" \
-o table
# Name Rg GiB Sku
# ---------------- -------- ----- ------------
# vm-old-01_osdisk rg-dev 128 Premium_LRS
# public IPs no NIC or load balancer is using
az network public-ip list --query "[?ipConfiguration==null].name" -o tsv
# pip-decommissioned-app
# stop the dev VM burning money overnight (time is UTC)
az vm auto-shutdown -g rg-dev -n vm-dev-01 \
--time 1930 --email "[email protected]"

Before you delete anything that looks orphaned, read its owner tag and go ask that person. A detached disk is sometimes a snapshot source someone is keeping on purpose. That is the quiet payoff from the governance lesson. Tags turn a frightening delete into a one-line message to the right human.

The loop, and the course

Cost work is a loop, not a project with a finish line. You look at the spend, commit to the part that is genuinely steady, right-size whatever the telemetry says is too big, and switch off what nothing uses. Every pass changes the inputs to the next one. Today's right-sizing shrinks the baseline, and a smaller baseline changes what is worth committing to next quarter. Run the loop once a month. The commands above are the entire toolkit.

Choosing a pricing lever for compute
How will this compute run?
Match the tariff to the usage pattern
Steady 24×7 baseline
Commit: Reservation or Savings Plan
Up to ~72% off with a reservation (locks VM family and region), ~65% with a savings plan (floats); shared scope gets the most out of it
Variable, business-hours load
Pay-as-you-go
No commitment; add auto-shutdown so idle dev/test VMs stop billing compute overnight
Interruptible (batch, CI, render)
Spot VM
Up to ~90% off; 30-second eviction notice; the Deallocate policy keeps disks so the job can resume
Azure Hybrid Benefit stacks on any of these: bring Windows or SQL Server licenses with Software Assurance and drop the license portion of the rate.

A bigger loop closes here too. Over fifteen lessons you built an estate the way Azure expects one to be built. Microsoft Entra ID (the identity service, once called Azure AD) says who exists. RBAC (role-based access control) says what those people may do. Policy, locks, and tags keep every deployment inside the rules. VMs, App Service, and containers run the workloads. Storage accounts, Blob lifecycle rules, and Azure Files hold the data. VNets (virtual networks), NSGs (network security groups), load balancers, and Private Endpoints wire it together privately. Monitor and Log Analytics tell you what the whole thing is doing. Backup and disaster recovery make it survivable. Cost management is the discipline that proves the estate earns its keep, using the same tags, the same action groups, and the same utilization telemetry, aimed at money instead of uptime. The AZ-104 job fits in one sentence: keep the estate secure, observable, recoverable, and worth what it costs.

Cloud bills ambush teams who keep treating Azure like a lab somebody else pays for. Start with visibility: Cost Analysis split by resource group, by service, and by tag. Budgets and anomaly alerts catch the ugly surprises while they are still small. Reservations and savings plans reward predictable compute and databases, but only once you know your baseline. Buy before you know it and you will reserve the wrong size for three years.

Waste hunting is an ordinary admin habit, like walking the office at night and switching off the lights. Unattached disks. Orphan public IP addresses. App Service plans nobody remembers creating. Oversized VMs left on Always On. Automation, meaning Advisor's cost recommendations plus small scripts like the ones above, beats one panicked spreadsheet a year. Pair it with governance tags so finance can charge costs back to the right team without an archaeological dig.

Try this

Pull a cost summary for the current month and see which resource groups are spending the most. Then go hunting in a lab subscription for an idle public IP address or an unattached disk, something you can safely delete.

terminal
az consumption usage list --start-date $(date -u +%Y-%m-01) --end-date $(date -u +%Y-%m-%d) --top 5 -o table 2>/dev/null || true
az disk list --query "[?diskState=='Unattached'].{name:name,rg:resourceGroup,sku:sku.name}" -o table
az network public-ip list --query "[?ipConfiguration==null].{name:name,rg:resourceGroup}" -o table
output
$ az disk list --query "[?diskState=='Unattached'].{name:name,rg:resourceGroup,sku:sku.name}" -o table
Name RG Sku
-------------- ----------- ------------
old-osdisk-01 rg-retired Premium_LRS
# Sample output
$ az network public-ip list --query "[?ipConfiguration==null].{name:name,rg:resourceGroup}" -o table
Name RG
--------- ----------
pip-orphan rg-lab-old

Takeaway

Four things to keep straight. Cost Management shows you the money. Tags say whose money it is. Reservations and savings plans discount the steady part. Right-sizing and deleting orphans cut the waste the same day you find it.

Next: put a budget on every production subscription with an alert at 80%, and use Azure Policy to require a cost-center tag on every new resource group.

Quick check
01A team wants a budget on their pay-as-you-go subscription that automatically stops all spending once the month reaches $1,500. What do you tell them?
Incorrect — The spending limit exists only on credit-based subscriptions such as a free trial or Visual Studio benefits, never on pay-as-you-go, and it tracks the credit rather than an amount you pick.
Incorrect — Forecasted alerts are worth having because they warn you early, but they still only notify. They never block provisioning or consumption.
Correct — A budget never caps spending, so a genuine backstop means wiring the action group to automation that deallocates tagged VMs.
Incorrect — Budgets never stop consumption at any point. The lag only delays the notification, and a runaway VM can burn hundreds of dollars before the first email arrives.
02You are reviewing a team's monthly run rate. A three-year reservation you bought shows up as one large charge on its purchase day in one cost view, and spread evenly across the whole term in another. Which view spreads the reservation cost evenly?
Correct — AmortizedCost spreads a reservation purchase evenly across its term, which is what you want when judging whether a monthly run rate is healthy.
Incorrect — ActualCost shows charges as they were billed, so a multi-year reservation lands as a single spike on the purchase day.
Incorrect — Forecasting predicts future spend for budget alerts. It is not one of the two cost views that decide how a reservation charge is split.
Incorrect — The spending limit is a hard credit cap on credit-based subscriptions, not a way of viewing reservation costs.
03A rendering pipeline runs large batch jobs that can be interrupted and restarted safely, and it checkpoints its progress to disk. You want the lowest compute price you can get. Which option is MOST cost-effective?
Incorrect — Reservations suit a steady round-the-clock baseline. Committing to bursty, interruptible work wastes the commitment.
Incorrect — Pay-as-you-go carries no discount at all, and auto-shutdown is aimed at idle business-hours dev/test machines, not at cheap batch compute.
Incorrect — A savings plan (up to about 65% off) fits steady spend that floats across families. It is neither built for interruptible work nor the cheapest option here.
Correct — Interruptible work maps straight onto Spot VMs for up to about 90% off, and the Deallocate policy keeps the disks so the checkpointed job can resume.

Related