FinOps best practices for compute

This article outlines a collection of proven FinOps practices for compute services. It provides guidance on optimizing costs, improving efficiency, and gaining insights into your compute resources in Azure. The practices are categorized based on the type of compute service, such as virtual machines (VM), Azure Kubernetes Service (AKS), and Azure Functions.


Azure Kubernetes Service

Azure Kubernetes Service (AKS) simplifies deploying and managing containerized applications. It offers serverless Kubernetes, an integrated CI/CD experience, and enterprise-grade security and governance.

Related resources:

Query: AKS cluster details

This ARG query retrieves detailed information about AKS clusters in your Azure environment.

Category

Resource management

Query

resources
| where type == "microsoft.containerservice/managedclusters"
| extend AgentPoolProfiles = properties.agentPoolProfiles
| mvexpand AgentPoolProfiles
| project
    id,
    ProfileName = tostring(AgentPoolProfiles.name),
    Sku = tostring(sku.name),
    Tier = tostring(sku.tier),
    mode = AgentPoolProfiles.mode,
    AutoScaleEnabled = AgentPoolProfiles.enableAutoScaling,
    SpotVM = AgentPoolProfiles.scaleSetPriority,
    VMSize = tostring(AgentPoolProfiles.vmSize),
    nodeCount = tostring(AgentPoolProfiles.['count']),
    minCount = tostring(AgentPoolProfiles.minCount),
    maxCount = tostring(AgentPoolProfiles.maxCount),
    location,
    resourceGroup,
    subscriptionId,
    AKSname = name

Use Spot VMs for AKS clusters

Recommendation: Use Spot VMs for AKS agent pools to reduce compute costs for fault-tolerant, interruptible workloads.

About Spot VMs in AKS

Spot VMs take advantage of unused Azure capacity at a significantly reduced cost. When Azure needs the capacity back, the Azure infrastructure evicts Spot VMs. Spot VMs are useful for workloads that can handle interruptions, like batch processing jobs, dev/test environments, and large compute workloads.

AKS clusters that use autoscaling but don't leverage Spot VMs may be paying more than necessary. By enabling Spot VMs for interruptible workloads, you can significantly reduce compute costs. This recommendation only applies to clusters running workloads that can tolerate interruptions. Not all workloads are suitable for Spot VMs.

Note

FinOps hubs can automatically identify AKS clusters without Spot VMs as an opt-in recommendation. Learn more.

Query: AKS clusters without Spot VMs

Use the following ARG query to identify AKS clusters with autoscaling enabled that aren't using Spot VMs.

Category

Optimization

Query

resources
| where type == 'microsoft.containerservice/managedclusters'
| mvexpand AgentPoolProfiles = properties.agentPoolProfiles
| where AgentPoolProfiles.enableAutoScaling == true
    and isnull(AgentPoolProfiles.scaleSetPriority)
| project
    ResourceId = id,
    AKSName = name,
    ProfileName = tostring(AgentPoolProfiles.name),
    VMSize = tostring(AgentPoolProfiles.vmSize),
    NodeCount = tostring(AgentPoolProfiles.['count']),
    MinCount = tostring(AgentPoolProfiles.minCount),
    MaxCount = tostring(AgentPoolProfiles.maxCount),
    Region = location,
    ResourceGroupName = resourceGroup,
    SubscriptionId = subscriptionId

Virtual machines

Azure virtual machines (VMs) are one of several types of on-demand, scalable computing resources that Azure offers. Typically, you choose a VM when you need more control over the computing environment than the other choices offer.

An Azure VM gives you the flexibility of virtualization without having to buy and maintain the physical hardware that runs it. However, you still need to maintain the VM by performing tasks, such as configuring, patching, and installing the software that runs on it.

Related resources:

Deallocate virtual machines

Recommendation: Deallocate VMs to avoid unused compute charges. Avoid stopping VMs without deallocating them.

About inactive VMs

VMs have two inactive states: Stopped and Deallocated.

Stopped VMs were shut down from within the operating system (for example, using the Shut down command). Stopped VMs are powered off, but Azure still reserves compute resources, like CPU and memory. Since compute resources are reserved and aren't available to for use with other VMs, these VMs continue to incur compute charges.

Deallocated VMs are stopped via cloud management APIs in the Azure portal, CLI, PowerShell, or other client tool. When a VM is deallocated, Azure releases the corresponding compute resources. Since compute resources are released, these VMs don't incur compute charges; however, it's important to note that both stopped and deallocated VMs continue to incur charges unrelated to compute, like storage charges from disks.

Note

FinOps hubs can automatically identify stopped VMs that aren't deallocated. Learn more.

Identify stopped VMs

Use the following Azure Resource Graph (ARG) query to identify stopped VMs that aren't deallocated. It retrieves details about their power state, location, resource group, and subscription ID.

resources
| where type =~ 'microsoft.compute/virtualmachines'
| extend PowerState = tostring(properties.extended.instanceView.powerState.displayStatus)
| where PowerState !in =('VM deallocated', 'VM running')
| project
    ResourceId = id,
    PowerState,
    Region = location,
    ResourceGroupName = resourceGroup,
    SubscriptionId = subscriptionId

Use commitment discounts

Recommendation: Use commitment discounts to save up to 72% compared to list costs.

About commitment discounts

Commitment discounts are financial incentives offered to organizations who commit to using Azure services for a specified period or term, typically one or three years. By agreeing to a fixed amount of usage or spend (cost) for the term, organizations can benefit from significant discounts (up to 72%) compared to list prices. Discounts are applied to eligible resources, helping organizations save on their cloud costs while providing flexibility and predictability in their budgeting.

To learn more about commitment discounts, refer to the Rate optimization capability.

Measure virtual machine commitment discount coverage

Use the following FinOps hub query to measure overall VM commitment discount coverage.

Costs
| where ResourceType =~ 'Virtual machine'
| where x_SkuMeterCategory startswith 'Virtual Machines'
//
// Join with prices to filter out ineligible SKUs
| extend tmp_MeterKey = strcat(substring(ChargePeriodStart, 0, 7), x_SkuMeterId)
| project tmp_MeterKey, EffectiveCost, PricingCategory, CommitmentDiscountCategory, ResourceName, x_ResourceGroupName, SubAccountName, BillingCurrency
| join kind=leftouter (
    Prices
    | where x_SkuMeterCategory startswith 'Virtual Machines'
    | summarize sp = countif(x_SkuPriceType == 'SavingsPlan'), ri = countif(x_SkuPriceType == 'ReservedInstance')
        by tmp_MeterKey = strcat(substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId)
    | project tmp_MeterKey, x_CommitmentDiscountSpendEligibility = iff(sp == 0, 'Not Eligible', 'Eligible'), x_CommitmentDiscountUsageEligibility = iff(ri == 0, 'Not Eligible', 'Eligible')
) on tmp_MeterKey
| extend x_CommitmentDiscountUsageEligibility = iff(isempty(x_CommitmentDiscountUsageEligibility), '(missing prices)', x_CommitmentDiscountUsageEligibility)
| extend x_CommitmentDiscountSpendEligibility = iff(isempty(x_CommitmentDiscountSpendEligibility), '(missing prices)', x_CommitmentDiscountSpendEligibility)
//
// Sum costs
| summarize
    TotalCost = sum(EffectiveCost),
    OnDemandCost = sumif(EffectiveCost, PricingCategory == 'Standard'),
    SpotCost = sumif(EffectiveCost, PricingCategory == 'Dynamic'),
    CommittedCost = sumif(EffectiveCost, PricingCategory == 'Committed'),
    CommittedSpendCost = sumif(EffectiveCost, CommitmentDiscountCategory == 'Spend'),
    CommittedUsageCost = sumif(EffectiveCost, CommitmentDiscountCategory == 'Usage')
    by x_CommitmentDiscountUsageEligibility, x_CommitmentDiscountSpendEligibility, BillingCurrency
| extend OnDemandPercent = round(OnDemandCost / TotalCost * 100, 2)
| extend CoveragePercent = round(CommittedCost / TotalCost * 100, 2)
| extend CoverageUsagePercent = round(CommittedUsageCost / TotalCost * 100, 2)
| extend CoverageSpendPercent = round(CommittedSpendCost / TotalCost * 100, 2)
| order by CoveragePercent desc

Use the following query to measure the cost breakdown per VM, including coverage of commitment discounts.

Costs
| where ResourceType =~ 'Virtual machine'
| where x_SkuMeterCategory startswith 'Virtual Machines'
//
// Join with prices to filter out ineligible SKUs
| extend tmp_MeterKey = strcat(substring(ChargePeriodStart, 0, 7), x_SkuMeterId)
| project tmp_MeterKey, EffectiveCost, PricingCategory, CommitmentDiscountCategory, ResourceName, x_ResourceGroupName, SubAccountName, BillingCurrency
| join kind=leftouter (
    Prices
    | where x_SkuMeterCategory startswith 'Virtual Machines'
    | summarize sp = countif(x_SkuPriceType == 'SavingsPlan'), ri = countif(x_SkuPriceType == 'ReservedInstance')
        by tmp_MeterKey = strcat(substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId)
    | project tmp_MeterKey, x_CommitmentDiscountSpendEligibility = iff(sp == 0, 'Not Eligible', 'Eligible'), x_CommitmentDiscountUsageEligibility = iff(ri == 0, 'Not Eligible', 'Eligible')
) on tmp_MeterKey
| extend x_CommitmentDiscountUsageEligibility = iff(isempty(x_CommitmentDiscountUsageEligibility), '(missing prices)', x_CommitmentDiscountUsageEligibility)
| extend x_CommitmentDiscountSpendEligibility = iff(isempty(x_CommitmentDiscountSpendEligibility), '(missing prices)', x_CommitmentDiscountSpendEligibility)
//
// Sum costs by resource
| summarize
    TotalCost = sum(EffectiveCost),
    OnDemandCost = sumif(EffectiveCost, PricingCategory == 'Standard'),
    SpotCost = sumif(EffectiveCost, PricingCategory == 'Dynamic'),
    CommittedCost = sumif(EffectiveCost, PricingCategory == 'Committed'),
    CommittedSpendCost = sumif(EffectiveCost, CommitmentDiscountCategory == 'Spend'),
    CommittedUsageCost = sumif(EffectiveCost, CommitmentDiscountCategory == 'Usage')
    by ResourceName, x_ResourceGroupName, SubAccountName, x_CommitmentDiscountUsageEligibility, x_CommitmentDiscountSpendEligibility, BillingCurrency
| extend OnDemandPercent = round(OnDemandCost / TotalCost * 100, 2)
| extend CoveragePercent = round(CommittedCost / TotalCost * 100, 2)
| extend CoverageUsagePercent = round(CommittedUsageCost / TotalCost * 100, 2)
| extend CoverageSpendPercent = round(CommittedSpendCost / TotalCost * 100, 2)
| order by CoveragePercent desc

To learn more about FinOps hubs, refer to FinOps hubs.

Query: Virtual machine scale set details

This query analyzes Virtual Machine Scale Sets in your Azure environment based on their SKU, spot VM priority, and priority mix policy. It provides insights for cost optimization and resource management strategies.

Category

Resource management

Query

resources
| where type =~ 'microsoft.compute/virtualmachinescalesets'
| extend SpotVMs = tostring(properties.virtualMachineProfile.priority)
| extend SpotPriorityMix = tostring(properties.priorityMixPolicy)
| extend SKU = tostring(sku.name)
| extend resourceGroup = strcat('/subscriptions/', subscriptionId, '/resourceGroups/', resourceGroup)
| project id, SKU, SpotVMs, SpotPriorityMix, subscriptionId, resourceGroup, location

Migrate to managed disks

Recommendation: Migrate VMs using unmanaged disks to managed disks to improve reliability, simplify management, and prepare for the retirement of unmanaged disks.

About unmanaged disks

Unmanaged disks store VHD files as page blobs in Azure Storage accounts, requiring you to manage storage account capacity, performance, and security yourself. Managed disks simplify disk management by handling storage account management for you, providing better reliability with availability sets, more granular access control, and support for newer features like disk encryption and bursting. Microsoft has announced the retirement of unmanaged disks, so migrating to managed disks is both a cost optimization and a compliance step.

Note

FinOps hubs can automatically identify VMs using unmanaged disks. Learn more.

Identify VMs with unmanaged disks

Use the following ARG query to identify VMs that are still using unmanaged disks.

resources
| where type =~ 'microsoft.compute/virtualmachines'
| where isnull(properties.storageProfile.osDisk.managedDisk)
| project
    ResourceId = tolower(id),
    ResourceName = name,
    OsDiskVhd = tostring(properties.storageProfile.osDisk.vhd.uri),
    Region = location,
    ResourceGroupName = resourceGroup,
    SubscriptionId = subscriptionId

Query: Virtual machine processor type analysis

This query identifies the processor type (ARM, AMD, or Intel) used by VMs in your Azure environment. It helps in understanding the distribution of VMs across different processor architectures, which is useful for optimizing workload performance and cost efficiency.

Category

Resource management

Query

resources
| where type == 'microsoft.compute/virtualmachines'
| extend vmSize = properties.hardwareProfile.vmSize
| extend processorType = case(
    // ARM Processors
    vmSize has "Epsv5"
        or vmSize has "Epdsv5"
        or vmSize has "Dpsv5"
        or vmSize has "Dpdsv", "ARM",
    // AMD Processors
    vmSize has "Standard_D2a"
        or vmSize has "Standard_D4a"
        or vmSize has "Standard_D8a"
        or vmSize has "Standard_D16a"
        or vmSize has "Standard_D32a"
        or vmSize has "Standard_D48a"
        or vmSize has "Standard_D64a"
        or vmSize has "Standard_D96a"
        or vmSize has "Standard_D2as"
        or vmSize has "Standard_D4as"
        or vmSize has "Standard_D8as"
        or vmSize has "Standard_D16as"
        or vmSize has "Standard_D32as"
        or vmSize has "Standard_D48as"
        or vmSize has "Standard_D64as"
        or vmSize has "Standard_D96as", "AMD",
    "Intel"
)
| project vmName = name, processorType, vmSize, resourceGroup

Use Azure Hybrid Benefit for Windows VMs

Recommendation: Enable Azure Hybrid Benefit for Windows VMs to reduce licensing costs by using existing on-premises Windows Server licenses.

About Azure Hybrid Benefit for Windows

Azure Hybrid Benefit lets you use your on-premises Windows Server licenses with Software Assurance or Windows Server subscription to run Windows VMs in Azure at a reduced cost. Instead of paying for a full Windows Server license with each VM, you can bring your existing licenses and only pay for the base compute cost. This recommendation only applies if your organization has qualifying on-premises Windows Server licenses.

Note

FinOps hubs can automatically identify Windows VMs without Azure Hybrid Benefit as an opt-in recommendation. Learn more.

Query: Windows VMs without Azure Hybrid Benefit

Use the following ARG query to identify Windows VMs and scale sets that aren't leveraging Azure Hybrid Benefit. The query excludes dev/test subscriptions, which already have discounted licensing.

Category

Optimization

Query

resourcecontainers
| where type =~ 'Microsoft.Resources/subscriptions'
| where tostring(properties.subscriptionPolicies.quotaId) !has 'MSDNDevTest_2014-09-01'
| project SubscriptionName = name, subscriptionId
| join (
    resources
    | where type =~ 'microsoft.compute/virtualmachines'
        or type =~ 'microsoft.compute/virtualMachineScaleSets'
    | where tostring(properties.storageProfile.osDisk.osType) == 'Windows'
        or tostring(properties.virtualMachineProfile.storageProfile.osDisk.osType) == 'Windows'
    | where tostring(properties.['licenseType']) !has 'Windows'
        and tostring(properties.virtualMachineProfile.['licenseType']) != 'Windows_Server'
    | project
        ResourceId = id,
        ResourceName = name,
        VMSize = tostring(properties.hardwareProfile.vmSize),
        LicenseType = tostring(properties.['licenseType']),
        Region = location,
        ResourceGroupName = resourceGroup,
        subscriptionId
) on subscriptionId
| project
    ResourceId,
    ResourceName,
    VMSize,
    LicenseType,
    Region,
    ResourceGroupName,
    SubscriptionName,
    SubscriptionId = subscriptionId

Use Azure Hybrid Benefit for SQL VMs

Recommendation: Enable Azure Hybrid Benefit for SQL Server VMs to reduce licensing costs by using existing on-premises SQL Server licenses.

About Azure Hybrid Benefit for SQL VMs

Azure Hybrid Benefit for SQL Server lets you use your on-premises SQL Server licenses with Software Assurance to run SQL Server VMs in Azure at a reduced cost. This benefit applies to Standard and Enterprise editions (Developer and Express editions are already free and don't need Azure Hybrid Benefit). This recommendation only applies if your organization has qualifying on-premises SQL Server licenses with Software Assurance.

Note

FinOps hubs can automatically identify SQL VMs without Azure Hybrid Benefit as an opt-in recommendation. Learn more.

Query: SQL VMs without Azure Hybrid Benefit

Use the following ARG query to identify SQL Server VMs that aren't leveraging Azure Hybrid Benefit. The query excludes dev/test subscriptions and Developer/Express editions.

Category

Optimization

Query

resourcecontainers
| where type =~ 'Microsoft.Resources/subscriptions'
| where tostring(properties.subscriptionPolicies.quotaId) !has 'MSDNDevTest_2014-09-01'
| project SubscriptionName = name, subscriptionId
| join (
    resources
    | where type =~ 'Microsoft.SqlVirtualMachine/SqlVirtualMachines'
        and tostring(properties.['sqlServerLicenseType']) != 'AHUB'
    | project
        ResourceId = id,
        ResourceName = name,
        LicenseType = tostring(properties.['sqlServerLicenseType']),
        SQLVersion = tostring(properties.['sqlImageOffer']),
        SQLSKU = tostring(properties.['sqlImageSku']),
        Region = location,
        ResourceGroupName = resourceGroup,
        subscriptionId
) on subscriptionId
| join (
    resources
    | where type =~ 'Microsoft.Compute/virtualMachines'
    | project
        ResourceName = tolower(name),
        VMSize = tostring(properties.hardwareProfile.vmSize),
        subscriptionId
) on ResourceName
| where SQLSKU != 'Developer' and SQLSKU != 'Express'
| project
    ResourceId,
    ResourceName,
    VMSize,
    LicenseType,
    SQLVersion,
    SQLSKU,
    Region,
    ResourceGroupName,
    SubscriptionName,
    SubscriptionId = subscriptionId

Give feedback

Let us know how we're doing with a quick review. We use these reviews to improve and expand FinOps tools and resources.

If you're looking for something specific, vote for an existing or create a new idea. Share ideas with others to get more votes. We focus on ideas with the most votes.


Related resources:

Related products:

Related solutions: