# Azure RI to Spot Migration for Non-Production

> Reservation- or savings-plan-covered VMs with a positive non-production signal are candidates for Spot redeployment. The discount fraction is 1 minus the Spot-to-RI-1yr rate ratio, sanity-banded between 0.05 and 0.92; Windows/SQL Server, M-series, DR/standby, and stateful workloads are vetoed, and missing live rates cause abstention.

Source: https://zop.dev/integrations/azure/recommendations/azure-ri-to-spot-migration-for-non-production
Updated: 2026-08-19

---

## Reserved non-prod VMs that Spot can actually take

- ReservationCoverage = Reservation/SavingsPlan (OnDemand VMs are RC-210's); already-Spot skip; interruptible-lifecycle (Metadata["lifecycle"]=spot/low_priority) false-RI-premise guard (fail-closed)
- Positive non-prod signal (isNonProdResource, audit 2026-06-26 pattern A): gating on !isProdResource fired Spot on neutral-named prod VMs; abstains otherwise
- M-series skip (isMSeriesVM, since Spot is unavailable); Databricks / VMSS-member skip. isVMSSMember only recognizes Uniform-orchestration members (nested .../virtualMachineScaleSets/.../virtualMachines/N UID); a Flexible-orchestration member is a flat Microsoft.Compute/virtualMachines UID linked via properties.virtualMachineScaleSet.id, which the discoverer does not currently stamp into Metadata, so such a member is not excluded today (audit 2026-07-01, low severity; a per-instance Spot migration on a Flex member is a policy-conflict risk with the scale set's own scaling/HA policy, not a factually wrong claim). Extend the exclusion once the discoverer stamps VMSS-Flex membership.
- stateful data-service veto (matchesStatefulWorkload)
- critical-role name veto (#1906 B2): shares matchesCriticalRole (dr, standby, segment-boundary), and a DR/standby box is never migrated onto Spot.
- OS/license veto (#1906 B4): shares isSpotIncompatibleOS, so Windows / SQL-Server (Metadata["os_family"]/platform) are skipped; absent OS signal abstains.

## The Spot to RI one-year rate ratio

cost-driven discount. The fraction is derived solely from live tier rates: frac = 1 − Spot/RI1yrNoUpfront, sanity-banded [0.05, 0.92] (riToSpotFractionFromTiers, rate-inversion guarded); SavingsUSD = cost × frac. The denominator is RI1yrNoUpfront (Aryan B3: the VM's current cost is already RI-discounted, so OnDemand would over-state by the RI-discount fraction). The previous DeriveCommitmentToSpotFraction(..., azureRIToSpotSavingsFraction) flat 0.60 fallback is removed; when the aggregator has not priced this SKU's RI1yr/Spot tiers (ok=false) or pricing is absent (cost ≤ 0) the rule abstains (returns nil), never falling back to a fabricated fraction.

## Redeploying as Spot and freeing the reservation

1. Confirm the workload tolerates interruption
2. Re-deploy the VM as Spot priority
3. Release the reservation back to the pool for production use

## CPU and network as background evidence

CPUUtilization + VMNetworkIn (30d) are evidence only; the firing signal is structural (RI/SP coverage plus a positive non-prod signal)
