Skip to content

Azure Cost Optimization

Key Points

  • Mental model: ingress is free, egress is expensive. Same-region in-VNet traffic is free. Cross-region is metered. Internet egress is billable per GB.
  • Idle resources are the silent budget killer: deallocated VMs still pay for disks; unused public IPs / gateways / Bastions tick 24/7.
  • Reservations (1y/3y) and Savings Plans give 30–70% off compute. Spot VMs for evictable batch save 60–90%.
  • Tag everything (env, costCenter, owner, project) and enforce via Azure Policy. Untagged resources are unattributable.
  • Top three cost surprises: Log Analytics ingestion at full retention, Cosmos RU/s autoscale max, internet egress on chatty microservices.

Concepts (deep dive)

Azure cost optimization isn't one tool — it's a practice that compounds three habits:

  1. Don't pay for what nobody uses — kill idle resources, right-size oversized SKUs, tier cold data into cool/archive, decommission orphans.
  2. Commit where usage is predictable — Reservations and Savings Plans trade flexibility for 30–70% off the baseline you're running anyway.
  3. Architect with cost in mind — egress charges, cross-zone traffic, Log Analytics ingestion, and Cosmos RU/s aren't bugs to fix later; they're shape decisions you make on day one.

The rest of this file is the concrete levers under each habit. The order — mental model → Cost Management tooling → reservations/savings/spot → tagging/policy → service-specific traps — mirrors how a senior engineer audits an unfamiliar subscription.

The senior cost mental model

Free / cheap                          Expensive
─────────────────────────────────     ──────────────────────────
Ingress to Azure                      Egress to internet ($/GB)
Same-region same-VNet traffic         Cross-region traffic
Same-zone traffic                     Cross-zone (within region) — small fee
Reserved compute (-30 to -70%)        On-demand compute
Spot VMs (-60 to -90%)                Idle VMs paying full
Storage Cool / Archive tiers          Hot tier for cold data
LRS                                   GZRS, RA-GZRS
Log Analytics Basic Logs              Analytics Logs at full retention

💡 The biggest cost mistake at senior level isn't picking the wrong SKU — it's not killing what nobody uses: dev environments running 24/7, ungoverned subscriptions, orphaned disks, public IPs detached from anything.

Azure Cost Management

Built-in (free) cost tooling:

  • Cost analysis: pivot by service, resource group, tag, location.
  • Budgets: monthly limits with email alerts at 50/80/100% — don't auto-stop services without explicit policy.
  • Anomaly detection: ML-based; flags unusual cost spikes per resource group.
  • Exports: daily CSV to Storage; ingest into Power BI for chargeback dashboards.
  • Cost recommendations (via Advisor): right-size VMs, idle SQL DBs, unused public IPs, etc.
Cost Mgmt → Budget → 
   Threshold 80% → email + webhook → 
   Threshold 100% → Action group → page on-call

Reservations

Commit to compute capacity for 1 or 3 years; pay upfront or monthly.

Resource Discount range
VM (specific size + region) 35–72%
App Service Plan (Premium v3) ~40%
Azure SQL DB (vCore) 30–55%
Cosmos DB (RU/s) 20–63%
Storage (Blob committed capacity) 13–38%

⚠️ Reservations lock to specific size/region/family. Wrong fit = sunk cost. Use Reservation Recommendations in Cost Mgmt — based on 30-day usage.

Savings Plans (compute)

More flexible than reservations: - Commit $/hr for 1 or 3 years; applies to any VM size, any region (within compute family). - Slightly less discount than equivalent reservation, but survives architecture changes.

💡 Modern guidance: reservations for stable databases (SQL/Cosmos), Savings Plans for variable VM/App Service compute.

Spot VMs

az vm create ... --priority Spot --eviction-policy Deallocate --max-price -1
  • 60–90% discount vs on-demand.
  • Azure can evict with 30s notice when capacity is needed.
  • --max-price -1 means "pay up to on-demand price"; otherwise eviction also triggers when spot price exceeds your max.

Use for: AI training, batch processing, Kubernetes preemptible node pools (AKS Spot pool), CI build agents. ❌ Don't use for: stateful primaries, customer-facing tier 1 services.

Dev/Test pricing

If your subscription is a Visual Studio / MSDN subscription, you get: - ~55% off Windows/SQL Server licensing on VMs. - No SQL Server licensing on Azure SQL DB Dev/Test tier. - Cheap test environments at ~half the cost.

Limitation: not for production workloads (license-side compliance).

Hybrid Benefit (BYOL)

If you own Windows Server / SQL Server licenses with Software Assurance: - Bring them to Azure VMs / Azure SQL. - Save the OS / SQL license cost (often 30–40% of VM hourly rate).

Standard A2 v2 VM:
  $X/hr base + $Y Windows = full price
  $X/hr base + $0 with Hybrid Benefit

Eligible: Windows Server, SQL Server, Linux subscriptions (RHEL, SUSE).

Tagging strategy

Mandatory tags (enforce via Azure Policy):

costCenter = "8472"
env        = "prod" | "staging" | "dev"
owner      = "[email protected]"
project    = "checkout"

Azure Policy (deny untagged):

{
  "if": { "field": "tags['costCenter']", "exists": "false" },
  "then": { "effect": "deny" }
}

Inheriting tags from RG to resources is not automatic — use a Modify-effect policy or a CI/CD step.

Right-sizing recommendations

Azure Advisor surfaces: - VMs with low CPU / network / IOPS for 14 days. - App Service Plans over-provisioned for the actual app traffic. - SQL DBs at <20% DTU/vCore utilization.

Workflow: Advisor → review → schedule resize during off-hours → monitor 7 days → finalize.

The forgotten resources tax

Resource Common waste
Managed disks Detached after VM deletion; full price forever
Public IP Standard Reserved but not assigned; ~$3/mo each, dozens accumulate
Application Gateway / Firewall / Bastion $150–300/mo idle; abandoned demos
Snapshots Old VM snapshots, never cleaned
Log Analytics workspace data Default 31-day retention but you set 730; pays forever
Cosmos containers Min 400 RU/s = ~$24/mo each, even with no traffic
Storage account snapshots/versions Versioning ON without lifecycle = unbounded growth
Dev environments Running 24/7 when only used 9–5 weekdays

💡 Run a monthly orphan scan: detached disks, idle public IPs, empty resource groups, deallocated VMs older than 90 days.

Auto-shutdown for dev/test

resource autoShut 'Microsoft.DevTestLab/schedules@2018-09-15' = {
  name: 'shutdown-computevm-${vm.name}'
  properties: {
    status: 'Enabled'
    taskType: 'ComputeVmShutdownTask'
    dailyRecurrence: { time: '1900' }
    timeZoneId: 'Eastern Standard Time'
    targetResourceId: vm.id
  }
}

Saves ~50% on a 9–5 dev VM. Plus auto-start at 8 AM.

For App Service, use Always On = false + autoscale min 0 / 1 instance.

Cost surprises

Log Analytics ingestion

Default pricing tier: Pay-As-You-Go @ ~$2.76/GB ingested. A chatty diagnostic settings export can ingest TB/day.

Tactics: - Basic Logs ($0.65/GB) for high-volume audit/firewall logs (limited query capability). - Sampling in Application Insights (default 100% — drop to 5–25%). - Filter on the agent: don't ship every Performance Counter. - Set table-level retention — keep 30 days for SecurityEvent, 7 for Heartbeat. - Use Data Collection Rules (DCR) to drop at ingestion. - Commitment tiers (>100 GB/day) save ~25%.

App Insights

  • Default 100% sampling — for high traffic apps, drop to 5–10%.
  • telemetryConfiguration.DisableTelemetry = true for noisy dependencies (Redis pings, health checks).

Cosmos DB RU/s

  • Autoscale max can quietly hit ceiling and 10x cost.
  • Min for a container = 400 RU/s = ~$24/mo even if empty.
  • Serverless (≤5K RU/s, per-op) for sporadic workloads.
  • Shared-throughput databases for many small containers.

Egress to internet

  • $0.05–$0.087/GB depending on volume tier.
  • Same-region same-VNet: free.
  • Cross-region: $0.02–$0.05/GB.
  • NAT Gateway data processing: $0.045/GB beyond included.

💡 Microservices doing 10s of GB/day cross-region for chat = $$$$. Co-locate in one region or use Front Door caching.

Bandwidth-heavy gotchas

  • Container registry pulls across regions (use geo-replication or regional ACRs).
  • AKS pulling from public Docker Hub instead of ACR.
  • Backup egress to Recovery Services Vault in different region.

FinOps practices

  • Chargeback model: every team sees their own cost; budget pressure where it can act.
  • Cost ownership: each resource has a tagged owner; orphaned cost = team lead's lap.
  • Weekly review: anomaly digest in Slack/Teams.
  • Pre-deploy cost estimate: Bicep what-if + Azure Pricing Calculator in PR template.
  • Showback for shared services: split central platform cost (firewall, log analytics) across consumers by usage.

Reserved Instances + Savings Plan combo

Use both: - Reservations on stable resources (production SQL, Cosmos primaries). - Savings Plan for compute breadth (App Service, dev VMs, AKS). - Spot for batch / training / CI.

A mature org typically has 60–80% of compute spend covered by some discount mechanism.


How it works under the hood

  • Azure billing meters every API call / resource hour. Costs aggregate hourly into the billing system, available with ~8–24h delay in Cost Management.
  • Reservations apply automatically when matching usage occurs in the scope (subscription / shared / management group).
  • Anomaly detection uses Azure ML to model expected daily spend per RG; flags >2σ deviations.
  • Cost exports run nightly into a Storage container; Power BI / Synapse can pull for dashboards.

Code: correct vs wrong

❌ Wrong: ungoverned tagging

resource sa 'Microsoft.Storage/storageAccounts@...' = {
  name: 'mystorage'
  // No tags
}

✅ Correct: enforced tags

resource sa '...' = {
  name: 'mystorage'
  tags: {
    env: 'prod'
    costCenter: '8472'
    owner: '[email protected]'
    project: 'checkout'
  }
}

❌ Wrong: full-fidelity App Insights at scale

builder.Services.AddApplicationInsightsTelemetry(); // 100% sampling default

✅ Correct: adaptive sampling

builder.Services.AddApplicationInsightsTelemetry(o =>
{
    o.EnableAdaptiveSampling = true;
});
// Or fixed sampling:
// o.SamplingPercentage = 10;

❌ Wrong: max RU/s set defensively

Container: 100,000 RU/s autoscale max  (just in case)

Hits cost ceiling on a runaway query.

✅ Correct: tight max + alerting

Autoscale max: 4,000 RU/s
Alert: > 80% RU consumption sustained 10 min → page

Design patterns for this topic

Pattern 1 — "Tag-driven chargeback"

  • Intent: every resource attributable; unowned resources eliminated.

Pattern 2 — "Reservation + Savings Plan + Spot stack"

  • Intent: layer discount mechanisms; reach 60–80% covered compute.

Pattern 3 — "Lifecycle policies for storage"

  • Intent: auto-tier hot → cool → archive → delete; never grow forever.

Pattern 4 — "Auto-shutdown for non-prod"

  • Intent: turn off dev outside hours; save ~50%.

Pattern 5 — "Pre-deploy cost gate"

  • Intent: PR template includes pricing calc; surprises caught before merge.

Pros & cons / trade-offs

Aspect Pros Cons
Reservations Big discount Locked to size/region
Savings Plans Flexible Smaller discount
Spot VMs Massive discount Evictable; not for stateful
Hybrid Benefit Use existing licenses Must own SA
Dev/Test pricing Cheap non-prod Compliance limits
Cost Management Built-in, free Not real-time
Azure Advisor Right-size hints Conservative; verify
FinOps culture Sustained savings Org change effort

When to use / when to avoid

  • Use reservations on stable production primaries (SQL, Cosmos, App Service Plan core).
  • Use Savings Plans for variable / migrating compute.
  • Use Spot for CI agents, AI training, batch jobs.
  • Use Cool/Cold/Archive tiers + lifecycle for any data >30 days.
  • Avoid reservations on workloads you'll re-architect within a year.
  • Avoid GRS/GZRS for ephemeral data.
  • Avoid Hot tier as default for everything — measure access patterns.

Interview Q&A

Q1. Why is egress expensive but ingress free? Cloud economics: bringing data in is "good for the platform." Egress to internet is metered per GB.

Q2. Reservation vs Savings Plan? Reservations: lock to specific size/region, biggest discount. Savings Plans: $/hr commit, flexible across compute family, slightly less discount.

Q3. Spot VM use cases? Evictable workloads: batch, AI training, CI agents, AKS preemptible pools. 60–90% off.

Q4. Hybrid Benefit? BYOL — use existing Windows Server / SQL Server licenses with Software Assurance to skip Azure license cost.

Q5. Common idle costs? Detached disks, unattached public IPs, idle Bastion/AppGW/Firewall, dev VMs running 24/7.

Q6. Cosmos cost surprises? Autoscale max ceiling, min 400 RU/s per container, hot partitions inflating RUs.

Q7. Log Analytics savings? Basic Logs tier, sampling, table-level retention, commitment tiers, drop at ingestion via DCRs.

Q8. Tagging strategy? Mandatory: env, costCenter, owner, project. Enforce via Azure Policy deny.

Q9. Auto-shutdown for dev? DevTestLab schedule resource on VM; auto-stop at 7 PM, start at 8 AM. ~50% savings.

Q10. Application Insights cost? Default 100% sampling = expensive. Drop to adaptive or 5–10% for high-traffic apps.

Q11. Cost ownership? Tag every resource with owner; chargeback to team budgets; review weekly.

Q12. Cross-region traffic? Metered. Co-locate microservices in one region; use Front Door / regional caching.


Gotchas / common mistakes

  • ⚠️ Untagged resources → unattributable cost.
  • ⚠️ Default Log Analytics retention at 730 days for high-volume tables.
  • ⚠️ Cosmos autoscale max too high → silent runaway spend.
  • ⚠️ Reservation locked to wrong size when you plan to right-size.
  • ⚠️ App Insights 100% sampling in prod high-traffic apps.
  • ⚠️ Detached managed disks still billed monthly.
  • ⚠️ Cross-region chatty microservices — egress + transit fees.
  • ⚠️ Idle Bastion / AppGW / Firewall — $150–500/mo for nothing.
  • ⚠️ No budget alerts — surprise at end-of-month invoice.

Further reading