Skip to content
Back to Blog
1 min read

Azure Cost Optimization Tips for 2023

I wrote “Azure Cost Optimization Tips for 2023” to share practical, production-minded guidance on this topic.

Quick Wins

1. Stop Unused Resources

# Find and deallocate stopped VMs that are still incurring charges
az vm list --query "[?powerState!='VM running'].{Name:name, RG:resourceGroup, State:powerState}" -o table

# Deallocate to stop charges
az vm deallocate --resource-group $RG --name $VM_NAME

# Delete unused disks
az disk list --query "[?diskState=='Unattached'].{Name:name, RG:resourceGroup, Size:diskSizeGb}" -o table

# Delete unattached public IPs
az network public-ip list --query "[?ipConfiguration==null].{Name:name, RG:resourceGroup}" -o table

2. Right-Size VMs

from azure.mgmt.monitor import MonitorManagementClient
from azure.mgmt.compute import ComputeManagementClient

def get_rightsizing_recommendations(subscription_id: str):
    """Analyze VMs and suggest right-sizing."""

    compute_client = ComputeManagementClient(credential, subscription_id)
    monitor_client = MonitorManagementClient(credential, subscription_id)

    recommendations = []

    for vm in compute_client.virtual_machines.list_all():
        # Get CPU metrics
        cpu_metrics = monitor_client.metrics.list(
            vm.id,
            metricnames="Percentage CPU",
            aggregation="Average",
            timespan="P7D"
        )

        avg_cpu = calculate_average(cpu_metrics)
        current_size = vm.hardware_profile.vm_size

        if avg_cpu < 20:
            recommendations.append({
                "vm": vm.name,
                "current_size": current_size,
                "avg_cpu": f"{avg_cpu:.1f}%",
                "recommendation": "Downsize or use B-series",
                "potential_savings": "40-60%"
            })

    return recommendations

3. Use Spot VMs for Fault-Tolerant Workloads

resource spotVmss 'Microsoft.Compute/virtualMachineScaleSets@2022-03-01' = {
  name: 'spot-vmss'
  location: location
  sku: {
    name: 'Standard_D4s_v3'
    tier: 'Standard'
    capacity: 10
  }
  properties: {
    virtualMachineProfile: {
      priority: 'Spot'
      evictionPolicy: 'Deallocate'
      billingProfile: {
        maxPrice: -1  // Pay up to on-demand price
      }
    }
    // ... other config
  }
}

Storage Optimization

Use Appropriate Tiers

# Storage tier recommendations based on access patterns
def recommend_storage_tier(
    access_frequency: str,
    retention_days: int
) -> str:
    """Recommend storage tier based on access patterns."""

    if access_frequency == "frequent":
        return "Hot"
    elif access_frequency == "monthly":
        if retention_days > 30:
            return "Cool"
        return "Hot"
    elif access_frequency == "quarterly":
        return "Cool"
    elif access_frequency == "yearly" or retention_days > 180:
        return "Archive"
    else:
        return "Cool"

# Lifecycle management policy
lifecycle_policy = {
    "rules": [
        {
            "name": "moveToCoool",
            "enabled": True,
            "type": "Lifecycle",
            "definition": {
                "filters": {"blobTypes": ["blockBlob"]},
                "actions": {
                    "baseBlob": {
                        "tierToCool": {"daysAfterModificationGreaterThan": 30},
                        "tierToArchive": {"daysAfterModificationGreaterThan": 90},
                        "delete": {"daysAfterModificationGreaterThan": 365}
                    }
                }
            }
        }
    ]
}

Optimize Cosmos DB

// Use serverless for variable workloads
var cosmosClientOptions = new CosmosClientOptions
{
    // Enable content response on write = false to reduce RU consumption
    EnableContentResponseOnWrite = false,

    // Use Direct mode for better performance
    ConnectionMode = ConnectionMode.Direct
};

// Efficient queries reduce RU consumption
var query = new QueryDefinition(
    "SELECT c.id, c.name FROM c WHERE c.partitionKey = @pk")
    .WithParameter("@pk", partitionKey);

// Use point reads when possible (1 RU vs query cost)
var response = await container.ReadItemAsync<Item>(id, new PartitionKey(pk));

Reserved Instances

# Analyze reservation opportunities
def analyze_reservation_savings(usage_data: dict) -> dict:
    """Calculate potential savings from reserved instances."""

    vm_usage = usage_data.get("virtual_machines", [])

    recommendations = []
    total_savings = 0

    for vm in vm_usage:
        if vm["uptime_percentage"] > 70:  # Good candidate for reservation
            current_monthly = vm["monthly_cost"]

            # Reservation savings estimates
            one_year_savings = current_monthly * 0.20 * 12
            three_year_savings = current_monthly * 0.40 * 36

            recommendations.append({
                "vm_size": vm["size"],
                "quantity": vm["count"],
                "current_monthly": current_monthly,
                "1yr_reservation_monthly": current_monthly * 0.80,
                "3yr_reservation_monthly": current_monthly * 0.60,
                "1yr_total_savings": one_year_savings,
                "3yr_total_savings": three_year_savings
            })

            total_savings += three_year_savings

    return {
        "recommendations": recommendations,
        "total_potential_savings": total_savings
    }

Dev/Test Optimization

// Use Azure Dev/Test pricing for non-production
// Requires Visual Studio subscription

// Schedule resources to stop outside business hours
resource automationAccount 'Microsoft.Automation/automationAccounts@2021-06-22' = {
  name: 'automation-account'
  location: location
  properties: {
    sku: {
      name: 'Basic'
    }
  }
}

resource stopVmsSchedule 'Microsoft.Automation/automationAccounts/schedules@2021-06-22' = {
  parent: automationAccount
  name: 'stop-dev-vms'
  properties: {
    startTime: '2023-01-01T18:00:00Z'
    expiryTime: '2024-12-31T18:00:00Z'
    interval: 1
    frequency: 'Day'
    timeZone: 'Pacific Standard Time'
  }
}

resource startVmsSchedule 'Microsoft.Automation/automationAccounts/schedules@2021-06-22' = {
  parent: automationAccount
  name: 'start-dev-vms'
  properties: {
    startTime: '2023-01-01T08:00:00Z'
    expiryTime: '2024-12-31T08:00:00Z'
    interval: 1
    frequency: 'Day'
    advancedSchedule: {
      weekDays: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
    }
    timeZone: 'Pacific Standard Time'
  }
}

Network Optimization

# Network cost reduction strategies
network_optimization:
  reduce_egress:
    - Use CDN for static content
    - Deploy resources in same region
    - Use private endpoints instead of public
    - Compress data before transfer

  use_azure_cdn:
    benefits:
      - Reduced egress from origin
      - Better performance
      - Lower latency

  private_endpoints:
    benefits:
      - No public IP costs
      - No egress through internet
      - Better security

Monitoring and Alerting

// Set up cost alerts
resource costAlert 'Microsoft.CostManagement/alerts@2022-10-01' = {
  name: 'daily-cost-spike'
  properties: {
    definition: {
      type: 'ActualCost'
      threshold: 1000
      timeframe: 'TheLastDay'
    }
    notification: {
      contactEmails: ['finops@company.com']
      contactRoles: ['Owner']
    }
  }
}

// Anomaly detection
resource anomalyAlert 'Microsoft.Insights/scheduledQueryRules@2022-06-15' = {
  name: 'cost-anomaly-detection'
  location: location
  properties: {
    enabled: true
    evaluationFrequency: 'PT1H'
    windowSize: 'PT1H'
    criteria: {
      allOf: [
        {
          query: '''
            AzureDiagnostics
            | where Category == "Costs"
            | summarize HourlyCost = sum(Cost) by bin(TimeGenerated, 1h)
            | where HourlyCost > threshold_value
          '''
          timeAggregation: 'Total'
          threshold: 0
          operator: 'GreaterThan'
        }
      ]
    }
    actions: {
      actionGroups: [costAlertActionGroup.id]
    }
  }
}

Cost Optimization Checklist

weekly:
  - [ ] Review Azure Advisor recommendations
  - [ ] Check for unused resources
  - [ ] Monitor budget alerts

monthly:
  - [ ] Review cost by resource group/tag
  - [ ] Analyze VM utilization
  - [ ] Check storage tier usage
  - [ ] Review data transfer costs

quarterly:
  - [ ] Evaluate reservation opportunities
  - [ ] Review architectural efficiency
  - [ ] Assess new Azure pricing options
  - [ ] Benchmark against targets

Conclusion

Cost optimization is an ongoing practice, not a one-time project. The combination of right-sizing, reservations, storage optimization, and operational discipline can reduce Azure spend by 30-50% without impacting performance. Make it a regular part of your cloud operations.

Resources

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.