The discussion comes up time and again: are Azure Reserved Instances (RI) or Azure Savings Plans (SP) the better option for running an IaaS-based application in Azure? There is no single answer to this question. In the following, we compare both options and evaluate them using different scenarios.

Brief definition of terms:

Azure Reserved Instance

A Reserved Instance is a commitment for a specific VM in a specific region for a specific duration. For example, a D8s_v5 VM in West Europe for three years. This reservation can be paid monthly over 3 years and offers a discount of up to 72%. In this specific example, the price advantage is approximately 62%. Important: the reservation must be paid regardless of whether it is used or not. You can find detailed documentation on this topic on MS Learn: Save costs with Azure Reserved VM Instances

Azure Savings Plan

Unlike a Reserved Instance, a Savings Plan does not reserve a specific VM in a specific region, but rather a fixed amount in EUR of compute. This can then be used flexibly for different VMs or other compute services. The committed amount is billed monthly regardless of whether it is fully utilized or not. If you consume more than committed, the excess is billed at the pay-as-you-go rate. Documentation for Savings Plans can be found here: What are Azure Savings Plans for Compute?

Scenarios

To achieve reasonable comparability, we consider the following scenarios and evaluate the costs. Imagine we have a time-tracking system. This system is primarily needed between 6:00 AM and 6:00 PM.

  • Scenario A: The workload runs 12 hours a day and could theoretically be completely shut down during the other 12 hours
  • Scenario B: The workload runs 12 hours a day but must remain available during the other 12 hours, albeit with less performance.
  • Scenario C: The workload only runs on 21 working days for 12 hours and could be replaced by a less powerful VM during the remaining time.
AssumptionValueUnit
Calculated hours per month730hours
30 days 6:00 AM - 6:00 PM360hours
21 working days 6:00 AM - 6:00 PM252hours

VM prices are based on data from the Azure pricing calculator (region “westeurope”):

VM TypePay-as-you-go (PAYG)RISP
D8s_v50.438 EUR/h0.166 EUR/h0.240 EUR/h
D4s_v50.213 EUR/h0.083 EUR/h0.0120 EUR/h
B4s_v20.183 EUR/h0.069 EUR/h0.097 EUR/h

All prices are compute-only, excluding operating system costs. Pricing calculator as of 10/05/2023.

Scenario A

If the time-tracking system could truly be completely shut down when not in use, the following monthly VM prices apply:

ConfigurationPrice
VM D8s_v5 as RI121.18 EUR/month
VM D8s_v5 30 days 12 hours/day (PAYG)157.68 EUR/month
VM D8s_v5 21 days 12 hours/day (PAYG)110.38 EUR/month

This shows that pay-as-you-go pricing only makes sense when we get into the range of 21 days at 12 hours per day. The 62% RI discount on the pay-as-you-go price pays off from approximately 350 operating hours.

Scenario B

The workload runs 12 hours a day but must remain available during the other 12 hours with less performance.

If everything is at PAYG pricing with 12 hours on D8s_v5 and 12 hours on D4s_v5:

VM SizePrice
D8s_v5157.68 EUR/month
D4s_v581.03 EUR/month
Total Workload238.71 EUR/month
Difference to RI+ 97%

If all prices are covered by a Savings Plan:

VM SizePrice
D8s_v586.40 EUR/month
D4s_v544.40 EUR/month
Total Workload130.80 EUR/month
Difference to RI+ 8%

If all prices are covered by a Savings Plan and a B-series VM is used for downsizing:

VM SizePrice
D8s_v586.40 EUR/month
B4s_v235.89 EUR/month
Total Workload122.29 EUR/month
Difference to RI+ 0.92%

We are already nearly at the RI price.

Scenario C

The workload only runs on 21 working days for 12 hours and could be replaced by a less powerful VM during the remaining time.

If everything is at PAYG pricing with D8s_v5 for 21 working days at 12 hours, and D4s_v5 for the remaining hours:

VM SizePrice
D8s_v5110.37 EUR/month
D4s_v5104.68 EUR/month
Total Workload215.05 EUR/month
Difference to RI+ 77%

If all prices are covered by a Savings Plan:

VM SizePrice
D8s_v560.48 EUR/month
D4s_v557.36 EUR/month
Total Workload117.84 EUR/month
Difference to RI- 2.8%

Extended with a B-series VM instead of D4s_v5 under the Savings Plan:

VM SizePrice
D8s_v560.48 EUR/month
B4s_v246.37 EUR/month
Total Workload106.85 EUR/month
Difference to RI- 11.8%

The last two configurations result in savings compared to a pure RI for a single VM size.

When considering not just pure compute costs but also including Windows licenses, the picture changes significantly. Even PAYG with 30 days at 12 hours of use and shut down otherwise becomes cheaper than an RI of the same VM size.

ConfigurationProjection
RI D8s_v5376.68 EUR/month
PAYG 30 days 12 hours291.56 EUR/month
PAYG 21 days 12 hours198.58 EUR/month

Including operating system and shut down when not needed.

ConfigurationScenario BScenario C
D8s_v5 + D4s_v5 PAYG429.46 EUR/month386.91 EUR/month
D8s_v5 + D4s_v5 SP321.55 EUR/month289.69 EUR/month
D8s_v5 + B4s_v2 SP253.84 EUR/month202.22 EUR/month

At peak, you can achieve savings of over 46% compared to the Reserved Instance. The key factor here is the price advantage for the OS with a B-series VM.

Implementation

Here is a brief example of how to put this into practice.

Deploying a VM

The Bicep code to deploy a VM can be copied here:

@description('Location to deploy the vNet and the VM')
param location string = 'westeurope'

@description('Name of the VM')
param vmName string = 'vm-sizingdemo01'

@description('Admin username for the VM')
param adminUsername string = 'demouser'

@description('Admin password for the VM')
@secure() 
param adminPassword string = 'Pass!word123'

@description('Name of the vNet')
param vnetName string = 'vnet-sizingdemo'

@description('Name of the subnet')
param subnetName string = 'snet-sizingdemo'

@description('Name of the automation account')
param automationAccountName string = 'aa-sizingdemo'


resource vnet 'Microsoft.Network/virtualNetworks@2020-11-01' = {
  name: vnetName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.0.0.0/16'
      ]
    }
    subnets: [
      {
        name: subnetName
        properties: {
          addressPrefix: '10.0.0.0/24'
        }
      }
    ]
  }
}

resource publicIp 'Microsoft.Network/publicIPAddresses@2020-11-01' = {
  name: '${vmName}-pip'
  location: location
  properties: {
    publicIPAllocationMethod: 'Dynamic'
  }
}

resource nic 'Microsoft.Network/networkInterfaces@2020-11-01' = {
  name: '${vmName}-nic'
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig'
        properties: {
          subnet: {
            id: vnet.properties.subnets[0].id
          }
          publicIPAddress: {
            id: publicIp.id
          }
        }
      }
    ]
  }
}

resource vm 'Microsoft.Compute/virtualMachines@2020-12-01' = {
  name: vmName
  location: location
  dependsOn: [
    nic
  ]
  properties: {
    hardwareProfile: {
      vmSize: 'Standard_D8s_v5'
    }
    storageProfile: {
      imageReference: {
        publisher: 'MicrosoftWindowsServer'
        offer: 'WindowsServer'
        sku: '2019-Datacenter'
        version: 'latest'
      }
      osDisk: {
        name: '${vmName}-osdisk'
        caching: 'ReadWrite'
        createOption: 'FromImage'
        diskSizeGB: 128
      }
    }
    osProfile: {
      computerName: vmName
      adminUsername: adminUsername
      adminPassword: adminPassword
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: nic.id
        }
      ]
    }
  }
}

resource automationAccount 'Microsoft.Automation/automationAccounts@2022-08-08' = {
  name: automationAccountName
  location: location
  properties: {
    sku: {
      name: 'Basic'
    }
  }
}

The parameters should be adjusted to your own preferences, especially the password for administrative access. In the Azure portal, the following is then deployed:

Azure Resource Group

Automation Account

The Automation Account then needs permissions to resize the VM on the subscription or resource group. In this example, the Automation Account “aa-sizingdemo” has been set up with a System Assigned Identity and the corresponding Azure Role Assignment (cf. MS Learn):

Azure Automation Account

Create a Runbook and Connect It to a Schedule

The PowerShell-based runbook for resizing the VM in the described scenario would look like this:

# Set variables
$resourceGroup = "rg-sizingdemo"
$vm = "vm-sizingdemo01"
# Set desired VM-Sizes
$normalsize = "Standard_D8s_v5"
$smallersize = "Standard_B4s_v2"

# login to Azure
Disable-AzContextAutosave -Scope Process
Connect-AzAccount -Identity

# check if the desired VM size is available
$availableSizes = Get-AzVMSize  -ResourceGroupName $resourceGroup -VMName $vm | Select-Object -ExpandProperty Name

if($availableSizes -notcontains $normalsize) {
    Write-Host "The desired normal VM size is not available."
    exit 1
}

if($availableSizes -notcontains $smallersize) {
    Write-Host "The desired smaller VM size is not available."
    exit 1
}

# Check current Size
$updatevm = Get-AzVM -ResourceGroupName $resourceGroup -Name $vm
$actualsize = $updatevm.HardwareProfile.VmSize
Write-Host "Current Size is $actualsize"

$targetsize = ($actualsize -eq $normalsize) ? $smallersize : $normalsize

# Deallocate the VM
Stop-AzVM -ResourceGroupName $resourceGroup -Name $vm -StayProvisioned -Force 
Write-Host "The VM is deallocated."

Write-Host "Target Size is $targetsize"
$updatevm.HardwareProfile.VmSize = $targetsize

# Resize the VM
Update-AzVM -ResourceGroupName $resourceGroup -VM $updatevm
Write-Host "New Size is $targetsize"

# Start the VM
Start-AzVM -ResourceGroupName $resourceGroup -Name $vm
Write-Host "The VM is started."

NOTE: There is now an Extension for Visual Studio Code to manage the runbook, schedule, and entire configuration directly in VS Code.

NOTE: This is only an exemplary implementation. For production use, a solution that automates resizing based on tags is recommended. Examples can be found in the Runbook Gallery or at aka.ms/AzureAutomationGitHub.