Cost Management - Azure RI vs. Saving Plans
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:
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):
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