Reduce Azure VMSS costs by 25-50% through autoscale tuning, SKU right-sizing, Spot Priority Mix, CI/CD agent optimization, and Azure Hybrid Benefit stacking.
Here is a number worth checking: what percentage of your VMSS instances are actually doing useful work right now? If the answer makes you uncomfortable, you are not alone. Azure Virtual Machine Scale Sets promise elastic compute that grows and shrinks with demand. The reality is less elegant: misconfigured autoscale rules, oversized SKUs, and CI/CD agents idling through the night can quietly inflate monthly spend by 30-60% -- turning your "elastic" infrastructure into an expensive fixed-cost line item. The most effective cost reductions come from aligning autoscale thresholds with actual demand, right-sizing instance SKUs using Advisor telemetry, adopting Spot instances for interruptible workloads, and eliminating orphaned or idle scale sets. Spotto continuously monitors VMSS utilization patterns across Azure subscriptions and flags scale sets where autoscale parameters, SKU selections, or scheduling configurations leave money on the table.
For Managed Service Providers managing dozens of scale sets across client tenants, these optimizations compound into substantial monthly savings without sacrificing elasticity or pipeline reliability.
Key Takeaways at a Glance:
Virtual Machine Scale Sets solve the elasticity problem, but they create a configuration problem. A scale set that handles peak traffic correctly may still waste 40% of its budget during off-peak hours if autoscale thresholds are too conservative, minimum instance counts are too high, or the underlying SKU is oversized for the workload.
The most common cost traps:
Financial impact: Organizations that systematically tune VMSS configurations typically reduce scale set compute costs by 25-50%.
There is an operational pattern worth calling out: VMSS configuration debt. A scale set is deployed with reasonable defaults, then the workload changes but the VMSS configuration never catches up. The most telling symptom is a scale set where autoscale was configured at launch, immediately overridden with a manual capacity change during an incident, and never reverted. The override sets min=max, autoscale rules still exist but never fire, and months later the scale set runs 8 instances for a workload that peaks at 3.
| Feature | Flexible | Uniform |
|---|---|---|
| Recommended for new deployments | Yes | Legacy |
| Mixed Spot + On-Demand | Yes (Spot Priority Mix) | No |
| VM size heterogeneity | Multiple sizes | Single size |
| Default in CLI/PS (Nov 2023+) | Yes | No |
| Availability Zones | Supported | Supported |
| Instance-level control | Full | Limited |
Note: Flexible orchestration is recommended for all new deployments. Since November 2023, Azure CLI and PowerShell default to Flexible.
VMSS Cost Optimization Priority Flowchart
==========================================
[Start] Assess VMSS Fleet
|
+-- Step 1: Are there scale sets with min = max instance count?
| |
| +-- YES --> Fix autoscale: set distinct min/max based on demand
| +-- NO --> Proceed
|
+-- Step 2: Are SKUs right-sized? (Check Advisor telemetry)
| |
| +-- CPU avg < 20% --> Downsize SKU or adopt B-series burstable
| +-- CPU avg 20-60% --> Validate sizing is appropriate
| +-- CPU avg > 60% --> Sizing is likely correct
|
+-- Step 3: Can workloads tolerate interruption?
| |
| +-- YES --> Adopt Spot instances with Priority Mix (Flexible only)
| +-- NO --> Use On-Demand with RI/Savings Plan
|
+-- Step 4: Are there CI/CD VMSS agent pools?
| |
| +-- YES --> Enable auto-teardown, tune standby count
| +-- NO --> Proceed
|
+-- Step 5: Is this a Windows Server workload?
| |
| +-- YES --> Apply Azure Hybrid Benefit
| +-- NO --> Proceed
|
+-- Step 6: Is demand predictable?
| |
| +-- YES --> Add schedule-based autoscale profiles
| +-- NO --> Tune metric-based autoscale thresholds
|
+-- Step 7: Are there orphaned/empty scale sets?
|
+-- YES --> Remove or deallocate (check associated LB, PIP)
+-- NO --> Optimization completeAzure Advisor samples CPU, memory, and network metrics every 30 seconds over 7-90 days. It then recommends SKU changes when utilization consistently falls below thresholds.
Implementation steps:
Typical savings: 20-40% by moving from oversized D-series to appropriately sized SKUs.
CLI commands for checking SKU and Advisor recommendations:
# Check current VMSS SKU
az vmss show --name <vmss-name> --resource-group <rg-name> \
--query "sku" -o table
# List Advisor cost recommendations for VMSS
az advisor recommendation list \
--filter "Category eq 'Cost'" \
--query "[?resourceMetadata.resourceId contains 'virtualMachineScaleSets']" \
-o table
# Get detailed CPU metrics for right-sizing analysis
az monitor metrics list \
--resource <vmss-resource-id> \
--metric "Percentage CPU" \
--interval PT1H \
--start-time $(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--aggregation Average Maximum \
-o tableB-series burstable instances are approximately 13-15% cheaper than equivalent D-series instances. They accumulate CPU credits during idle periods and burst above baseline when needed, making them ideal for workloads with low average CPU but periodic spikes.
| SKU | vCPUs | RAM (GB) | Approx. Price/mo | Baseline CPU |
|---|---|---|---|---|
| Standard_B2s | 2 | 4 | ~$30 | 40% |
| Standard_D2s_v5 | 2 | 8 | ~$70 | 100% |
| Standard_B4ms | 4 | 16 | ~$120 | 90% |
| Standard_D4s_v5 | 4 | 16 | ~$140 | 100% |
Key constraints for B-series:
Azure Spot VMs offer up to 90% discount compared to pay-as-you-go pricing. The trade-off is that Azure can evict Spot instances with 30 seconds notice when capacity is needed. Spot Priority Mix, available only on Flexible orchestration VMSS, lets you define a baseline of standard (on-demand) instances plus Spot instances for additional capacity.
Spot Priority Mix example -- 10 total instances:
Spot Priority Mix Configuration
================================
Total desired capacity: 10 instances
[Standard] [Standard] [Standard] [Standard] [ Spot ] [ Spot ] [ Spot ] [ Spot ] [ Spot ] [ Spot ]
Instance 1 Instance 2 Instance 3 Instance 4 Instance 5 Instance 6 Instance 7 Instance 8 Instance 9 Instance 10
|<-------- 4 Standard (baseline) -------->|<------------- 6 Spot (burst capacity) ------------->|
Configuration:
baseRegularPriorityCount: 4 # Always on-demand
regularPriorityPercentageAboveBase: 0 # All additional = Spot
Result:
- 4 instances guaranteed (no eviction)
- 6 instances at ~90% discount (evictable)
- Blended savings: ~54% vs all on-demandEviction policies:
IMDS polling command for eviction detection:
# Poll IMDS for Spot eviction notice (run inside the VM)
# Returns empty if no eviction scheduled, or eviction details
curl -s -H "Metadata:true" \
"http://169.254.169.254/metadata/scheduledevents?api-version=2020-07-01" \
| jq '.Events[] | select(.EventType == "Preempt")'
# Recommended: poll every 5 seconds in a background service
# and gracefully drain workloads when eviction is detectedAzure Monitor autoscale uses metric-based rules to scale VMSS instance counts up and down. Properly configured autoscale is the single most impactful cost optimization for most scale sets.
CLI commands for creating autoscale with CPU-based rules:
# Create autoscale setting with CPU-based scale-out and scale-in
az monitor autoscale create \
--resource-group <rg-name> \
--resource <vmss-resource-id> \
--resource-type Microsoft.Compute/virtualMachineScaleSets \
--name "autoscale-cpu" \
--min-count 2 \
--max-count 10 \
--count 2
# Add scale-out rule: add 2 instances when avg CPU > 70% for 10 min
az monitor autoscale rule create \
--resource-group <rg-name> \
--autoscale-name "autoscale-cpu" \
--condition "Percentage CPU > 70 avg 10m" \
--scale out 2
# Add scale-in rule: remove 1 instance when avg CPU < 30% for 10 min
az monitor autoscale rule create \
--resource-group <rg-name> \
--autoscale-name "autoscale-cpu" \
--condition "Percentage CPU < 30 avg 10m" \
--scale in 1
# Add cooldown period to prevent flapping
az monitor autoscale rule create \
--resource-group <rg-name> \
--autoscale-name "autoscale-cpu" \
--condition "Percentage CPU > 70 avg 10m" \
--scale out 2 \
--cooldown 5Key autoscale behaviour:
If min=max, autoscale cannot function. The scale set runs at a fixed capacity regardless of demand. This is the most common VMSS cost waste pattern.
Check and fix CLI commands:
# Check current autoscale settings for a VMSS
az monitor autoscale show \
--resource-group <rg-name> \
--name <autoscale-setting-name> \
--query "{MinCount:profiles[0].capacity.minimum, \
MaxCount:profiles[0].capacity.maximum, \
DefaultCount:profiles[0].capacity.default}" -o table
# Fix: set distinct min and max
az monitor autoscale update \
--resource-group <rg-name> \
--name <autoscale-setting-name> \
--min-count 2 \
--max-count 10 \
--count 2
# Find all VMSS with min=max across the subscription
az monitor autoscale list --query "[?profiles[0].capacity.minimum == \
profiles[0].capacity.maximum].{Name:name, Min:profiles[0].capacity.minimum, \
Max:profiles[0].capacity.maximum, Resource:targetResourceUri}" -o tableAutoscale tuning decision tree:
Autoscale Tuning Decision Tree
===============================
[Start] Review current autoscale metrics
|
+-- Is the scale-out threshold too low (e.g., 50% CPU)?
| |
| +-- YES --> Raise to 70-80% to avoid premature scaling
| +-- NO --> Is the threshold too high (e.g., 95%)?
| |
| +-- YES --> Lower to 70-80% to avoid latency spikes
| +-- NO --> Threshold is appropriate
|
+-- Is the scale-in threshold too high (e.g., 50% CPU)?
| |
| +-- YES --> Lower to 20-30% to scale in more aggressively
| +-- NO --> Is the threshold too low (e.g., 5%)?
| |
| +-- YES --> Raise to 20-30% to avoid running idle instances
| +-- NO --> Threshold is appropriate
|
+-- Is the cooldown period too short (< 5 min)?
| |
| +-- YES --> Increase to 5-10 min to prevent flapping
| +-- NO --> Is it too long (> 15 min)?
| |
| +-- YES --> Reduce to 5-10 min for better responsiveness
| +-- NO --> Cooldown is appropriate
|
+-- Is the metric time grain too short (< 5 min)?
|
+-- YES --> Increase to 5-10 min to smooth out spikes
+-- NO --> Time grain is appropriateTest and development VMSS often inherit production autoscale profiles. A test scale set with a minimum count of 3 and a maximum of 10, running 24/7, costs the same as production during off-hours. For short-lived test environments, set the capacity manually to 1-2 instances or implement aggressive schedule-based scaling that drops to zero outside test windows.
# Disable autoscale and set fixed capacity for test VMSS
az monitor autoscale update \
--resource-group <rg-name> \
--name <autoscale-setting-name> \
--enabled false
# Set manual capacity to 1 instance
az vmss scale \
--resource-group <rg-name> \
--name <vmss-name> \
--new-capacity 1Azure Monitor autoscale supports three profile types that enable time-aware scaling:
Weekly capacity schedule example:
Weekly VMSS Capacity Schedule
==============================
Mon Tue Wed Thu Fri Sat Sun
0:00 2 2 2 2 2 1 1
6:00 2 2 2 2 2 1 1
8:00 6 6 6 6 6 1 1 <-- Business hours start
12:00 8 8 8 8 6 1 1 <-- Peak
18:00 4 4 4 4 2 1 1 <-- Wind down
22:00 2 2 2 2 2 1 1 <-- Night
Savings vs fixed 8 instances:
Fixed: 8 x 24 x 7 = 1,344 instance-hours/week
Scheduled: ~420 instance-hours/week
Reduction: ~69%CLI commands for schedule-based autoscale:
# Add a recurrence profile for business hours (weekdays 8am-6pm)
az monitor autoscale profile create \
--resource-group <rg-name> \
--autoscale-name "autoscale-cpu" \
--name "business-hours" \
--min-count 4 \
--max-count 10 \
--count 6 \
--recurrence week Mon Tue Wed Thu Fri \
--start "08:00" \
--end "18:00" \
--timezone "Eastern Standard Time"
# Add a weekend/night profile with minimal capacity
az monitor autoscale profile create \
--resource-group <rg-name> \
--autoscale-name "autoscale-cpu" \
--name "off-hours" \
--min-count 1 \
--max-count 3 \
--count 2 \
--recurrence week Mon Tue Wed Thu Fri Sat Sun \
--start "18:00" \
--end "08:00" \
--timezone "Eastern Standard Time"Azure DevOps VMSS agent pools can automatically provision and tear down VMs based on pipeline demand. When configured correctly, this eliminates the cost of idle build agents.
Typical savings: 50-70% compared to always-on agent pools. The key trade-off is VM startup time: Azure DevOps samples demand every 5 minutes, and new VM provisioning takes 15-20 minutes. Factor this latency into pipeline SLAs.
Key configuration details:
The standby agent count and idle delay settings directly control the cost-latency trade-off for VMSS agent pools.
| Setting | Aggressive (Cost) | Balanced | Conservative (Perf) |
|---|---|---|---|
| Standby agent count | 0 | 1-2 | 3-5 |
| Idle delay (minutes) | 0 (immediate teardown) | 15-30 | 60-120 |
| Max provisioned agents | Match peak demand | Peak + 20% | Peak + 50% |
| Pipeline wait time | 15-20 min | 0-5 min | ~0 min |
| Relative cost | Lowest | Medium | Highest |
Azure Hybrid Benefit (AHB) eliminates the Windows Server OS licensing surcharge on VMSS instances by allowing you to use existing Windows Server licenses with Software Assurance. Reserved Instances (RIs) provide additional compute discounts: 1-year RIs save approximately 20-30%, and 3-year RIs save approximately 40-72%.
Discount stacking: AHB + RI savings:
| Configuration | Windows OS Cost | Compute Cost | Total Savings |
|---|---|---|---|
| Pay-as-you-go (baseline) | Full price | Full price | 0% |
| AHB only | Eliminated | Full price | ~40% |
| 1-year RI only | Full price | ~20-30% off | ~20-30% |
| AHB + 1-year RI | Eliminated | ~20-30% off | ~55-60% |
| AHB + 3-year RI | Eliminated | ~40-72% off | Up to 80% |
CLI commands for enabling Azure Hybrid Benefit:
# Enable Azure Hybrid Benefit on an existing VMSS
az vmss update \
--resource-group <rg-name> \
--name <vmss-name> \
--license-type Windows_Server
# Verify AHB is enabled
az vmss show \
--resource-group <rg-name> \
--name <vmss-name> \
--query "virtualMachineProfile.licenseType" -o tsv
# Check all VMSS in a subscription for missing AHB
az vmss list --query "[?virtualMachineProfile.osProfile.windowsConfiguration != null \
&& virtualMachineProfile.licenseType != 'Windows_Server'].{Name:name, \
ResourceGroup:resourceGroup, LicenseType:virtualMachineProfile.licenseType}" -o tableNote: AHB requires active Windows Server licenses with Software Assurance. Verify license eligibility with your Microsoft licensing agreement before enabling. AHB can be toggled on and off without VM downtime.
Azure Advisor flags VMSS instances with average CPU utilization below 3% and network utilization below 2% over the monitoring period. Deallocating these instances stops approximately 70% of their costs (compute charges stop; disk and IP charges continue).
# Deallocate a VMSS (stops all instances, retains configuration)
az vmss deallocate \
--resource-group <rg-name> \
--name <vmss-name>
# Deallocate specific instances within a VMSS
az vmss deallocate \
--resource-group <rg-name> \
--name <vmss-name> \
--instance-ids 0 1 2Scale sets with zero running instances still incur charges for associated resources. These orphaned resources quietly add up:
# Find VMSS with zero instances
az vmss list --query "[?sku.capacity == `0`].{Name:name, \
ResourceGroup:resourceGroup, SKU:sku.name, Capacity:sku.capacity}" -o table
# Before deleting, check for associated resources
az vmss show --name <vmss-name> --resource-group <rg-name> \
--query "{LoadBalancer:virtualMachineProfile.networkProfile.networkInterfaceConfigurations[0].\
ipConfigurations[0].loadBalancerBackendAddressPools, \
PublicIP:virtualMachineProfile.networkProfile.networkInterfaceConfigurations[0].\
ipConfigurations[0].publicIPAddressConfiguration}" -o json
# Delete an empty VMSS and its associated resources
az vmss delete --resource-group <rg-name> --name <vmss-name>1. The "Set It and Forget It" Override
An autoscale profile is configured at deployment, then a manual capacity override is applied during an incident. The override sets min=max, autoscale rules still exist but never fire, and months later the VMSS runs 8 instances for a workload that peaks at 3. This is the most common and most expensive VMSS anti-pattern.
2. The Phantom Agent Pool
A VMSS agent pool is created for a CI/CD pipeline that was decommissioned months ago. The scale set still maintains standby agents, consuming compute 24/7 for pipelines that no longer run. Without regular agent pool audits, phantom pools accumulate silently.
3. The Premium Dev/Test
Development and test VMSS deployed with production-grade SKUs (D8s_v5, E-series) and production autoscale profiles. These environments run 8-16 vCPU instances when 2-4 vCPU burstable instances would suffice, often with no schedule-based scaling to reduce capacity outside working hours.
4. The License Oversight
Windows Server VMSS running without Azure Hybrid Benefit enabled. The Windows OS licensing surcharge adds approximately 40% to the compute cost. Organizations with existing Windows Server licenses through Software Assurance are paying this surcharge unnecessarily -- often across dozens of scale sets.
5. The Autoscale Safety Net That Never Fires
Autoscale is configured with an extremely conservative scale-out threshold (e.g., 95% CPU) and aggressive scale-in prevention (e.g., never scale below 6 instances). The intention is safety, but the result is a scale set that never actually scales -- running at minimum capacity permanently while paying for the illusion of elasticity.
| Recommendation | Typical Savings | Effort | Risk |
|---|---|---|---|
| Fix min=max autoscale overrides | 20-40% | Low | Low |
| Right-size SKU (Advisor telemetry) | 20-40% | Medium | Medium |
| Adopt B-series burstable SKUs | 13-15% | Medium | Medium |
| Spot instances with Priority Mix | Up to 90% | Medium | High (eviction) |
| Schedule-based autoscale | 30-60% | Low | Low |
| VMSS agent pool auto-teardown | 50-70% | Low | Low |
| Azure Hybrid Benefit | ~40% | Low | None |
| AHB + 3-year RI stacking | Up to 80% | Medium | Low (commitment) |
| Deallocate idle instances | ~70% per instance | Low | Low |
| Remove empty scale sets | 100% (associated resources) | Low | Low |
| Combined optimizations | 25-50% | Medium | Low-Medium |
Check the autoscale run history in the Azure portal under Monitor > Autoscale > Run history. If you see no scale events over the past 30 days, either your thresholds are set too conservatively (never triggered), your min=max (autoscale cannot function), or your workload is genuinely flat. The most common cause is a manual override that set min=max during an incident and was never reverted.
Flexible orchestration supports Spot Priority Mix, which lets you define a baseline of on-demand instances topped up with Spot instances. Uniform orchestration does not support mixed priority. If you need Spot cost savings with a guaranteed baseline, Flexible is the only option. Flexible also supports mixed VM sizes, which enables further right-sizing opportunities.
Yes, with careful design. Use Spot Priority Mix to maintain a baseline of on-demand instances that handle minimum load, and fill additional capacity with Spot. Design applications to handle graceful degradation when Spot instances are evicted. Stateless web frontends, batch processors, and worker queues are good candidates. Stateful databases and single-instance services are not.
They stack. AHB removes the Windows OS licensing cost, and RI reduces the compute cost. Applied together, you can achieve up to 80% savings compared to pay-as-you-go Windows Server pricing. AHB can be enabled/disabled at any time without affecting RI coverage. The RI applies to the compute portion regardless of the licensing model.
Reserved Instances are scoped to a VM size family and region. If you right-size within the same family (e.g., D4s_v5 to D2s_v5), the RI can apply with instance size flexibility. If you change families (e.g., D-series to B-series), the existing RI will not cover the new SKU. Always check RI coverage before changing SKU families, and consider exchanging the RI if needed.
Set cooldown periods of 5-10 minutes between scale actions. Ensure there is a meaningful gap between your scale-out threshold (e.g., 70% CPU) and scale-in threshold (e.g., 30% CPU). Azure's autoscale engine has built-in flapping prevention that calculates what the metric would be after removing instances, but proper threshold separation and cooldown periods are still essential.
Savings Plans offer more flexibility -- they apply across VM families, regions, and even across services. However, RIs typically offer slightly deeper discounts for the same commitment term. If your VMSS workloads are stable in SKU and region, RIs are more cost-effective. If you expect to change SKUs or regions frequently, Savings Plans provide better coverage flexibility.
Enable auto-teardown so agents are deallocated after jobs complete. Set standby agent count to 0-2 depending on your latency tolerance. Use schedule-based scaling to reduce capacity to zero outside business hours. For pipelines that run only during business hours, this combination can reduce agent costs by 50-70%.
Monitor CPU utilization (should stay within your autoscale thresholds), memory utilization (to catch under-provisioning after SKU changes), request latency (to detect capacity-related performance degradation), autoscale event frequency (to catch flapping), and failed provisioning requests (which indicate insufficient capacity or replica issues). Set alerts on each metric to catch regressions within 48-72 hours of changes.
Spotto continuously monitors your VMSS fleet to detect autoscale misconfigurations (min=max overrides, conservative thresholds), identify oversized SKUs using utilization telemetry, flag missing Azure Hybrid Benefit on Windows Server scale sets, surface idle agent pools, and track orphaned scale sets with associated resource costs. Rather than running one-off audits, Spotto provides ongoing visibility and actionable recommendations -- especially valuable for MSPs managing VMSS across multiple client tenants.
Before
$85,000/mo
30 tenants, avg 3 VMSS each, oversized SKUs, no AHB, min=max on 40% of scale sets
After
$55,000/mo
Right-sized SKUs, AHB enabled, autoscale fixed, schedule-based scaling added
Actions taken: Fixed min=max overrides on 36 scale sets. Enabled AHB on all Windows Server VMSS. Right-sized 22 scale sets from D4s to D2s based on Advisor telemetry. Added schedule-based scaling for dev/test environments. Removed 8 orphaned empty scale sets.
Before
$42,000/mo
12 VMSS agent pools, always-on with 4-8 standby agents each, D4s_v5 SKUs
After
$19,000/mo
Auto-teardown enabled, 1-2 standby, B4ms SKUs, 3 phantom pools removed
Actions taken: Enabled auto-teardown on all agent pools. Reduced standby agent count from 4-8 to 1-2. Migrated agents from D4s_v5 to B4ms (CI/CD workloads are bursty). Removed 3 agent pools for decommissioned pipelines. Added off-hours scaling to zero on weekends.
Before
$28,000/mo
4 VMSS for web/API tiers, fixed 8 instances each, D4s_v5, pay-as-you-go
After
$16,800/mo
Autoscale 2-10, schedule-based profiles, 3-year RI on baseline
Actions taken: Configured metric-based autoscale (2 min, 10 max). Added recurrence profiles for business hours vs. off-hours. Purchased 3-year RIs for baseline capacity (2 instances per VMSS). Reduced off-hours minimum to 2 instances. Traffic analysis showed 60% of capacity was unused during nights and weekends.
Before
$18,000/mo
2 VMSS for batch processing, D8s_v5, 6 instances each, all on-demand
After
$4,500/mo
Flexible VMSS with Spot Priority Mix (2 standard + 10 Spot), queue-based autoscale
Actions taken: Migrated from Uniform to Flexible orchestration. Configured Spot Priority Mix with 2 standard baseline + Spot fill for burst. Implemented queue-based autoscale (scale on queue depth). Added retry logic for Spot evictions. Batch jobs designed to checkpoint and resume.
Before
$22,000/mo
8 VMSS mirroring production, D4s_v5, always-on, no schedule scaling
After
$7,700/mo
B2s SKUs, schedule to zero nights/weekends, Spot for load testing
Actions taken: Downgraded all dev/test VMSS from D4s_v5 to B2s burstable. Implemented schedule-based scaling: 2 instances during business hours, 0 nights and weekends. Used Spot instances for load testing environments. Consolidated 3 redundant test VMSS into 1 with proper tagging.
Every optimization should have a clear rollback path:
az monitor autoscale update. Changes take effect immediately. Keep a record of previous settings before modifying.az vmss update --set sku.name=<previous-sku>. Requires a rolling upgrade to apply to existing instances. Allow 30-60 minutes for completion.az vmss update --license-type None. This reverts to pay-as-you-go Windows licensing. No VM restart required.az monitor autoscale profile delete. The default profile becomes active immediately.# Weekly VMSS Fleet Health Check - PowerShell Automation Script
# Schedule: Weekly via Azure Automation or GitHub Actions
param(
[string[]]$SubscriptionIds,
[int]$CpuThresholdPercent = 20,
[int]$LookbackDays = 7
)
$report = @()
foreach ($subId in $SubscriptionIds) {
Set-AzContext -SubscriptionId $subId | Out-Null
$vmssList = Get-AzVmss
foreach ($vmss in $vmssList) {
$rgName = $vmss.ResourceGroupName
$vmssName = $vmss.Name
# Check 1: Autoscale configuration
$autoscale = Get-AzAutoscaleSetting -ResourceGroupName $rgName \
-ErrorAction SilentlyContinue |
Where-Object { $_.TargetResourceUri -match $vmssName }
$minMaxIssue = $false
if ($autoscale) {
$profile = $autoscale.Profiles | Select-Object -First 1
if ($profile.Capacity.Minimum -eq $profile.Capacity.Maximum) {
$minMaxIssue = $true
}
}
# Check 2: Average CPU utilization
$endTime = Get-Date
$startTime = $endTime.AddDays(-$LookbackDays)
$metrics = Get-AzMetric -ResourceId $vmss.Id \
-MetricName "Percentage CPU" \
-StartTime $startTime -EndTime $endTime \
-TimeGrain 01:00:00 -AggregationType Average \
-ErrorAction SilentlyContinue
$avgCpu = ($metrics.Data | Where-Object { $_.Average } |
Measure-Object -Property Average -Average).Average
# Check 3: Azure Hybrid Benefit
$ahbEnabled = $vmss.VirtualMachineProfile.LicenseType -eq "Windows_Server"
$isWindows = $vmss.VirtualMachineProfile.OsProfile.WindowsConfiguration -ne $null
# Check 4: Instance count
$capacity = $vmss.Sku.Capacity
$isEmpty = $capacity -eq 0
# Build report entry
$report += [PSCustomObject]@{
Subscription = $subId
ResourceGroup = $rgName
VMSSName = $vmssName
SKU = $vmss.Sku.Name
Capacity = $capacity
AvgCpuPercent = [math]::Round($avgCpu, 1)
MinEqualsMax = $minMaxIssue
IsWindows = $isWindows
AHBEnabled = $ahbEnabled
AHBMissing = ($isWindows -and -not $ahbEnabled)
IsEmpty = $isEmpty
Recommendation = ""
}
}
}
# Generate recommendations
foreach ($entry in $report) {
$recs = @()
if ($entry.MinEqualsMax) {
$recs += "FIX: Autoscale min=max override detected"
}
if ($entry.AvgCpuPercent -lt $CpuThresholdPercent -and $entry.Capacity -gt 0) {
$recs += "RIGHT-SIZE: Avg CPU $($entry.AvgCpuPercent)% - consider smaller SKU"
}
if ($entry.AHBMissing) {
$recs += "ENABLE: Azure Hybrid Benefit missing on Windows VMSS"
}
if ($entry.IsEmpty) {
$recs += "CLEANUP: Empty scale set - check for orphaned resources"
}
$entry.Recommendation = ($recs -join "; ")
}
# Output report
$report | Format-Table -AutoSize
$report | Export-Csv -Path "vmss-fleet-health-$(Get-Date -Format yyyy-MM-dd).csv" \
-NoTypeInformation
# Summary
Write-Output "\n=== VMSS Fleet Health Summary ==="
Write-Output "Total VMSS: $($report.Count)"
Write-Output "Min=Max Issues: $(($report | Where-Object MinEqualsMax).Count)"
Write-Output "Under-utilized (<$CpuThresholdPercent% CPU): $(($report | \
Where-Object { $_.AvgCpuPercent -lt $CpuThresholdPercent -and \
$_.Capacity -gt 0 }).Count)"
Write-Output "Missing AHB: $(($report | Where-Object AHBMissing).Count)"
Write-Output "Empty VMSS: $(($report | Where-Object IsEmpty).Count)"The challenges outlined in this article — misconfigured autoscale rules, idle CI/CD agents, missing Hybrid Benefit flags, oversized SKUs — are the same ones our customers work through across their Azure environments. The difference is they use Spotto to surface these opportunities automatically, rather than running the analysis by hand.
Spotto continuously analyzes your Azure VMSS fleet to detect autoscale misconfigurations, idle agent pools, missing license benefits, and oversized SKUs — giving you clear, actionable recommendations to eliminate scale set waste across every subscription.
Free TrialDisclaimer: This article is provided for informational purposes only and does not constitute professional advice. Azure pricing, features, and service behaviour may have changed since publication. Always validate recommendations against your specific environment and consult Microsoft's official documentation before making changes to production infrastructure. Spotto is not liable for any actions taken based on the content of this article.