CAFfe Zeit

A lot of topics around the Microsoft Cloud Adoption Framework
de en

Cost Management - Azure RI vs. Saving Plans

2023-10-05 Niels Ophey

There is always a discussion about whether Azure Reserved Instances (RI) or Azure Saving Plan (SP) are the better option for running an application based on IaaS in Azure. There is not one answer to this question. In the following, the two options Reserved Instance and Saving Plan are compared and evaluated based on scenarios.

Brief definition of terms:

Azure Reserved Instance

A Reserved Instance is a commitment to a specific VM in a specific region for a specific period of time. For example, a D8s_v5 VM in Western Europe for three years. This reservation can then be paid monthly for 3 years and offers a discount of up to 72%. In this specific example, the price advantage is ~62% off. It is important to note that the reservation must be paid for, regardless of whether it is used or not. On MS Learn you can find detailed documentation on the topic under the following link: Save costs with reserved Azure VM instances

Azure Saving Plan

In contrast to a Reserved Instance, a Saving plan does not reserve a specific VM in a specific region, but only a sum of EUR in compute. This can then be used flexibly for different VMs or other compute services. The reserved sum will then be billed monthly, regardless of whether it has been fully used or not. If you consume more than you have committed, you will be charged at the pay-as-you-go price. The documentation for the Saving Plans can be found at the following place: What is Azure savings plans for compute?

Scenarios

To get a possible good comparability, we assume the following scenarios and evaluate the costs of the respective solution. Let’s imagine we have a time recording system. This system is mainly 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 in the other 12 hours
  • Scenario B: The workload runs 12 hours a day, but must remain available in the other 12 hours, but with less performance.
  • Scenario C: The workload runs only 21 working days 12 hours and could be replaced by a less powerful VM in the remaining time.
Assumption Value Unit
Calculatory hours per month 730 Hours
30 days 6:00 am - 6:00 pm 360 Hours
21 working days 6:00 am - 12:00 pm 252 Hours

The data from the Azure Price Calculator was used as the basis for the VM prices (region “westeurope”):

VM Type Pay-as-you-go (PAYG) RI SP
D8s_v5 0,438 EUR/h 0,166 EUR/h 0,240 EUR/h
D4s_v5 0,213 EUR/h 0,083 EUR/h 0,0120 EUR/h
B4s_v2 0,183 EUR/h 0,069 EUR/h 0,097 EUR/h

All prices are only the compute price, i.e. without operating system costs. Pricecalculator dated 10/05/2023.

Scenario A

If the time recording system could really be completely switched off in the times when it is not used, the following prices result in one month for the VM:

Configuration Price
VM D8s_v5 as RI 121,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 makes it clear that it would only be worthwhile to take the pay-as-you-go price if we get into the range of 21 days 12h per day. The RI discount of 62% on the pay-as-you-go price of the VM pays off from ~350 hours of operation of the VM.

Scenario B

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

If everything is mapped at the PAYG price and a D8s_v5 is used for 12 hours and a D4s_v5 for 12 hours:

VM size Price
D8s_v5 157,68 EUR/month
D4s_v5 81,03 EUR/month
Total Workload 238,71 EUR/month
Difference to RI + 97%

If all prices are covered by a Saving Plan:

VM size Price
D8s_v5 86,40 EUR/month
D4s_v5 44,40 EUR/month
Total Workload 130,80 EUR/month
Difference to RI + 8%

If all prices are covered by a Saving Plan and a B-Series can be used as a downsizing VM:

VM size Price
D8s_v5 86,40 EUR/month
B4s_v2 35,89 EUR/month
Total Workload 122,29 EUR/month
Difference to RI + 0,92%

This brings us almost to the price of an RI.

Scenario C

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

If everything is mapped at the PAYG price and a D8s_v5 is used for 21 working days for 12 hours. In the remaining hours, it is then changed to a D4s_v5:

VM size Price
D8s_v5 110,37 EUR/month
D4s_v5 104,68 EUR/month
Total Workload 215,05 EUR/month
Difference to RI + 77%

If all prices are covered by a Saving Plan:

VM size Price
D8s_v5 60,48 EUR/month
D4s_v5 57,36 EUR/month
Total Workload 117,84 EUR/month
Difference to RI - 2,8%

Then extended by a B-Series VM instead of the D4s_v5 in the Saving Plan:

VM Size Price
D8s_v5 60,48 EUR/month
B4s_v2 46,37 EUR/month
Total Workload 106,85 EUR/month
Difference to RI - 11,8%

The last two configurations generate a saving compared to the pure RI for one VM size.

If you do not only consider the pure compute, but also include the Windows licenses, the picture changes significantly. Then PAYG 30 days 12 hours in use and otherwise off with the same VM size becomes cheaper than an RI of the same VM size.

Configuration Calculation
RI D8s_v5 376,68 EUR/month
PAYG 30 Tage 12 Stunden 291,56 EUR/month
PAYG 21 Tage 12 Stunden 198,58 EUR/month

Including operating system and switched off when not needed.

Configuration Scenario B Scenario C
D8s_v5 + D4s_v5 PAYG 429,46 EUR/month 386,91 EUR/month
D8s_v5 + D4s_v5 SP 321,55 EUR/month 289,69 EUR/month
D8s_v5 + B4s_v2 SP 253,84 EUR/month 202,22 EUR/month

At the peak, you achieve a saving of over 46% compared to the Reserved Instance. Here, the advantage in price for the OS with a B-Series VM plays the decisive role.

Implementation

To put this into practice, here is a short example of the implementation.

Deploying a VM

The bicep code to deploy a VM can be downloaded here or copied directly 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 according to your own ideas, especially the password for admin access. In the Azure portal, the following will be deployed:

Azure Resourcegroup

Automation Account

The automation account needs an authorization to be able to resize the VM on the subscription or resource group scope. For this purpose, the automation account “aa-sizimgdemo” has been provided with a system assigned identity and the corresponding Azure role assignment (MS Learn):

Azure Automation Account

Create a runbook and connect it to a schedule

The runbook based on Powershell to change the VM size in this scenario would then 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

# Ensures you do not inherit an AzContext in your runbook
Disable-AzContextAutosave -Scope Process

# Connect to Azure with system-assigned managed identity
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 also an Extension for VisualStudio code to make the runbook as well as the schedule and the complete configuration directly in VSCode.

In this example the schedule was set to every 12 hours:

{
    "id": "/subscriptions/<YOURSUBSCRIPTION>/resourceGroups/rg-sizingdemo/providers/Microsoft.Automation/automationAccounts/aa-sizingdemo/schedules/Resizing",
    "name": "Resizing",
    "type": "Microsoft.Automation/AutomationAccounts/Schedules",
    "startTime": "2023-10-06T16:00:00.000Z",
    "startTimeOffsetMinutes": 120,
    "expiryTime": "9999-12-31T23:59:59.999Z",
    "expiryTimeOffsetMinutes": 0,
    "isEnabled": true,
    "nextRun": "2023-10-06T16:00:00.000Z",
    "nextRunOffsetMinutes": 120,
    "interval": 12,
    "frequency": "Hour",
    "timeZone": "Europe/Berlin",
    "advancedSchedule": null,
    "creationTime": "2023-10-06T08:45:43.643Z",
    "lastModifiedTime": "2023-10-06T08:45:43.643Z",
    "description": ""
}

NOTE: This is only an exemplary implementation. For productive use, a solution is recommended that automatically adjusts the size of the VMs, e.g. using tags, so that a script does not have to be set up for each VM. Examples of this can be found in the Runbook Gallery directly in the Azure portal or on Github. See also aka.ms/AzureAutomationGitHub