Cost management
Visibility, reservations, right-sizing, waste.
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:
# actual cost so far this month, grouped by resource group —# the same query Cost Analysis runs, called directlySUB=$(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.
# 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.
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.
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.
# Advisor's cost recommendationsaz 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 usingaz 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.
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.
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 || trueaz disk list --query "[?diskState=='Unattached'].{name:name,rg:resourceGroup,sku:sku.name}" -o tableaz network public-ip list --query "[?ipConfiguration==null].{name:name,rg:resourceGroup}" -o table
$ az disk list --query "[?diskState=='Unattached'].{name:name,rg:resourceGroup,sku:sku.name}" -o tableName RG Sku-------------- ----------- ------------old-osdisk-01 rg-retired Premium_LRS# Sample output$ az network public-ip list --query "[?ipConfiguration==null].{name:name,rg:resourceGroup}" -o tableName 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.