Understanding where costs are being generated within your Azure Kubernetes Service (AKS) cluster is critical for optimizing cloud spending. This guide shows you how to implement granular cost allocation for AKS workloads using OpenCost, Azure Cost Analysis, and native AKS cost features to track expenses by namespace, deployment, and pod.
Overview
Azure Kubernetes Service clusters often run multiple applications, teams, and environments within a single infrastructure. Without proper cost allocation, it's impossible to know which workloads are consuming the most resources or to perform accurate showback/chargeback to different cost centers.
This guide covers three complementary approaches:
- AKS Cost Analysis (Azure Portal) - Built-in cost breakdown by namespace and cluster resources
- OpenCost - Open-source tool for detailed Kubernetes cost allocation with custom metrics
- Azure Monitor Container Insights - Infrastructure metrics correlated with cost data
By the end of this guide, you'll be able to track AKS costs at the namespace, deployment, pod, and label level.
Prerequisites
Before you begin, ensure you have:
- An active Azure subscription with an existing AKS cluster
- Owner or Contributor role on the AKS cluster resource
- Cost Management Reader role at the subscription or resource group level
- Azure CLI installed and authenticated (
az login) kubectlinstalled and configured to connect to your AKS cluster- Helm 3.x installed for deploying OpenCost
- Basic understanding of Kubernetes namespaces, deployments, and labels
- Container Insights enabled on your AKS cluster (recommended)
Verify Your Access
# Check your current role assignments
az role assignment list --assignee $(az account show --query user.name -o tsv) \
--scope /subscriptions/$(az account show --query id -o tsv) \
--query "[].{Role:roleDefinitionName, Scope:scope}" -o table
# Verify kubectl access to your cluster
kubectl auth can-i get pods --all-namespaces
Method 1: Enable AKS Cost Analysis in Azure Portal
Azure provides native cost breakdown capabilities for AKS clusters directly in the Azure Portal.
Step 1: Navigate to Cost Analysis
- Sign in to the Azure Portal
- Navigate to Cost Management + Billing
- Select Cost analysis from the left navigation
- Change the scope to your AKS cluster's resource group or subscription
Step 2: Create AKS-Specific Cost Views
- In Cost analysis, click + Add filter
- Select Resource type and choose Microsoft.ContainerService/managedClusters
- Click Group by and select Resource
- This shows total cost per AKS cluster
Step 3: View Costs by Namespace (Azure Portal)
Important: Namespace-level cost breakdown requires Container Insights to be enabled.
- Navigate to your AKS cluster in the Azure Portal
- Select Insights from the left navigation
- Click the Containers tab
- Select the Metrics view
- Use the namespace filter to see resource utilization by namespace
- Cross-reference utilization with Cost Analysis data
Step 4: Enable Split Billing by Namespace Tags
To get automated namespace cost attribution:
- Navigate to your AKS cluster
- Select Configuration > Tags
- Add namespace-specific tags to cluster resources
- Use Azure Policy to automatically tag resources based on Kubernetes labels
Example Azure Policy for Auto-Tagging:
{
"mode": "Indexed",
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.ContainerService/managedClusters"
}
]
},
"then": {
"effect": "append",
"details": [
{
"field": "tags['Environment']",
"value": "[resourceGroup().tags['Environment']]"
}
]
}
}
}
Method 2: Deploy OpenCost for Granular Cost Allocation
OpenCost is a CNCF sandbox project that provides real-time cost allocation for Kubernetes workloads.
Step 1: Install OpenCost Using Helm
# Add the OpenCost Helm repository
helm repo add opencost https://opencost.github.io/opencost-helm-chart
helm repo update
# Create a namespace for OpenCost
kubectl create namespace opencost
# Install OpenCost with Azure provider
helm install opencost opencost/opencost \
--namespace opencost \
--set opencost.exporter.cloudProviderApiKey="AIzaSyD29bGCUHHbGQXaW5j9XXXXXXXXX" \
--set opencost.prometheus.internal.enabled=true \
--set opencost.ui.enabled=true
Note: For Azure, the cloudProviderApiKey is optional but recommended for accurate pricing.
Step 2: Configure Azure Pricing Integration
To get accurate Azure pricing data, configure OpenCost with Azure credentials:
# Create Azure Service Principal for OpenCost
az ad sp create-for-rbac --name opencost-reader \
--role "Cost Management Reader" \
--scopes /subscriptions/$(az account show --query id -o tsv)
# Save the output (appId, password, tenant)
Create a Kubernetes secret with the credentials:
kubectl create secret generic azure-opencost-config \
--namespace opencost \
--from-literal=azure-subscription-id="YOUR_SUBSCRIPTION_ID" \
--from-literal=azure-client-id="YOUR_APP_ID" \
--from-literal=azure-client-secret="YOUR_PASSWORD" \
--from-literal=azure-tenant-id="YOUR_TENANT_ID"
Update the OpenCost Helm values:
# opencost-values.yaml
opencost:
exporter:
cloudProviderApiKey: ""
azureSubscriptionID: "YOUR_SUBSCRIPTION_ID"
azureClientID: "YOUR_APP_ID"
azureClientSecret: "YOUR_PASSWORD"
azureTenantID: "YOUR_TENANT_ID"
ui:
enabled: true
prometheus:
internal:
enabled: true
servicemonitor:
enabled: true
Upgrade the Helm release:
helm upgrade opencost opencost/opencost \
--namespace opencost \
--values opencost-values.yaml
Step 3: Access the OpenCost Dashboard
# Port-forward to access the OpenCost UI
kubectl port-forward -n opencost service/opencost 9090:9090
# Open in browser
open http://localhost:9090
The OpenCost UI provides:
- Cost breakdown by namespace, deployment, and pod
- Historical cost trends
- Cost efficiency metrics
- Resource allocation vs usage
Step 4: Query OpenCost API for Cost Data
OpenCost exposes a REST API for programmatic access:
# Get cost allocation for all namespaces (last 7 days)
curl "http://localhost:9090/allocation/compute?window=7d&aggregate=namespace"
# Get cost by deployment in production namespace
curl "http://localhost:9090/allocation/compute?window=30d&aggregate=deployment&filterNamespaces=production"
# Get cost by label
curl "http://localhost:9090/allocation/compute?window=7d&aggregate=label:team"
Example Response:
{
"code": 200,
"data": {
"production": {
"name": "production",
"properties": {
"namespace": "production"
},
"window": {
"start": "2025-01-10T00:00:00Z",
"end": "2025-01-17T00:00:00Z"
},
"cpuCost": 45.23,
"gpuCost": 0.00,
"ramCost": 23.18,
"pvCost": 12.50,
"networkCost": 8.92,
"totalCost": 89.83
}
}
}
Method 3: Use Azure CLI to Export Cost Data with AKS Tags
You can programmatically retrieve cost data filtered by AKS resources using Azure CLI.
Step 1: Create Cost Export with Azure CLI
# Define variables
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
RESOURCE_GROUP="your-rg-name"
AKS_CLUSTER="your-aks-cluster"
STORAGE_ACCOUNT="costexportstorage"
CONTAINER_NAME="aks-costs"
# Create storage account for cost exports
az storage account create \
--name $STORAGE_ACCOUNT \
--resource-group $RESOURCE_GROUP \
--location eastus \
--sku Standard_LRS
# Create container
az storage container create \
--name $CONTAINER_NAME \
--account-name $STORAGE_ACCOUNT
# Create cost export for AKS resources
az costmanagement export create \
--name "aks-monthly-export" \
--type "ActualCost" \
--scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP" \
--storage-account-id "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Storage/storageAccounts/$STORAGE_ACCOUNT" \
--storage-container "$CONTAINER_NAME" \
--timeframe "MonthToDate" \
--recurrence "Daily" \
--recurrence-period-from "2025-01-01T00:00:00Z" \
--recurrence-period-to "2025-12-31T00:00:00Z" \
--schedule-status "Active"
Step 2: Filter Cost Data by AKS Tags
Once exports are running, query the cost data:
# Download the latest export
az storage blob download \
--account-name $STORAGE_ACCOUNT \
--container-name $CONTAINER_NAME \
--name "aks-monthly-export/$(date +%Y%m%d)/cost-data.csv" \
--file "aks-costs.csv"
# Parse and filter using tools like jq, csvkit, or pandas
Method 4: Integrate with Azure Monitor and Log Analytics
Combine resource metrics with cost data for comprehensive analysis.
Step 1: Enable Container Insights
# Enable Container Insights on existing cluster
az aks enable-addons \
--resource-group $RESOURCE_GROUP \
--name $AKS_CLUSTER \
--addons monitoring
Step 2: Create Custom Log Analytics Queries
// Query: CPU and Memory usage by namespace
let startTime = ago(7d);
let endTime = now();
Perf
| where TimeGenerated between (startTime .. endTime)
| where ObjectName == "K8SContainer"
| where CounterName == "cpuUsageNanoCores" or CounterName == "memoryRssBytes"
| extend Namespace = tostring(split(InstanceName, "/")[0])
| summarize
AvgCPU = avg(CounterValue),
MaxCPU = max(CounterValue),
AvgMemory = avg(CounterValue),
MaxMemory = max(CounterValue)
by Namespace, CounterName
| order by Namespace asc
Step 3: Join Cost Data with Usage Metrics
// Query: Correlate namespace usage with estimated costs
let costPerCPUCore = 0.05; // Example: $0.05 per CPU hour
let costPerGB = 0.01; // Example: $0.01 per GB hour
let startTime = ago(30d);
let endTime = now();
Perf
| where TimeGenerated between (startTime .. endTime)
| where ObjectName == "K8SContainer"
| extend Namespace = tostring(split(InstanceName, "/")[0])
| summarize
TotalCPUHours = sum(CounterValue) / 1000000000 / 3600, // Convert nanocores to hours
TotalGBHours = sum(CounterValue) / 1073741824 / 3600 // Convert bytes to GB hours
by Namespace, CounterName
| extend EstimatedCost = iff(CounterName == "cpuUsageNanoCores", TotalCPUHours * costPerCPUCore, TotalGBHours * costPerGB)
| summarize TotalEstimatedCost = sum(EstimatedCost) by Namespace
| order by TotalEstimatedCost desc
Best Practices
1. Implement Consistent Tagging Strategy
Tag all Kubernetes resources with cost allocation labels:
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
environment: production
team: platform
cost-center: "1234"
business-unit: engineering
Apply labels to deployments:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
labels:
app: web-app
team: platform
cost-center: "1234"
spec:
template:
metadata:
labels:
app: web-app
team: platform
cost-center: "1234"
2. Set Resource Requests and Limits
Accurate cost allocation requires defined resource requests:
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
containers:
- name: app
image: myapp:latest
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
3. Use Namespace Resource Quotas
Prevent cost overruns with resource quotas:
apiVersion: v1
kind: ResourceQuota
metadata:
name: production-quota
namespace: production
spec:
hard:
requests.cpu: "100"
requests.memory: "200Gi"
limits.cpu: "200"
limits.memory: "400Gi"
persistentvolumeclaims: "50"
4. Implement Cost Allocation Labels Policy
Use Azure Policy to enforce tagging:
# Example: Require cost-center tag on all resources
az policy assignment create \
--name "require-cost-center-tag" \
--policy "require-tag" \
--params '{"tagName":{"value":"cost-center"}}' \
--scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP"
5. Schedule Regular Cost Reviews
Automate cost reporting with scheduled queries:
# Create Log Analytics scheduled query
az monitor log-analytics query \
--workspace "your-workspace-id" \
--analytics-query "Perf | where ObjectName == 'K8SContainer' | summarize avg(CounterValue) by Namespace" \
--timespan "P7D"
6. Right-Size Node Pools
Use cost data to optimize node pool sizes:
# Review node pool utilization
kubectl top nodes
# Scale node pool based on cost analysis
az aks nodepool scale \
--resource-group $RESOURCE_GROUP \
--cluster-name $AKS_CLUSTER \
--name nodepool1 \
--node-count 5
7. Enable Cluster Autoscaler
Automatically adjust cluster size based on workload demands:
az aks update \
--resource-group $RESOURCE_GROUP \
--name $AKS_CLUSTER \
--enable-cluster-autoscaler \
--min-count 3 \
--max-count 10
Troubleshooting
Issue: OpenCost Shows Incorrect Pricing
Symptoms: OpenCost displays costs that don't match Azure invoices
Solution:
- Verify Azure credentials are correctly configured
- Check that the Service Principal has "Cost Management Reader" role
- Update to latest OpenCost version:
helm upgrade opencost opencost/opencost -n opencost - Verify region pricing matches your cluster location
# Test Azure credentials
kubectl logs -n opencost deployment/opencost -c opencost | grep -i "azure"
# Expected output should show successful Azure API connection
Issue: Cost Data Not Available for Namespaces
Symptoms: Namespace-level costs show as zero or unavailable
Solution:
- Ensure Container Insights is enabled
- Verify resources have defined requests and limits
- Check that OpenCost has sufficient RBAC permissions
# Verify Container Insights
az aks show \
--resource-group $RESOURCE_GROUP \
--name $AKS_CLUSTER \
--query "addonProfiles.omsagent.enabled" -o tsv
# Should return "True"
# Check OpenCost RBAC
kubectl auth can-i get pods --namespace opencost --as system:serviceaccount:opencost:opencost
Issue: Historical Cost Data Missing
Symptoms: Only current day costs are visible
Solution:
- OpenCost requires Prometheus to store historical metrics
- Verify Prometheus retention settings
- Increase PVC size for Prometheus if needed
# Check Prometheus storage
kubectl get pvc -n opencost
# Increase Prometheus retention
helm upgrade opencost opencost/opencost \
--namespace opencost \
--set opencost.prometheus.internal.retention=30d \
--set opencost.prometheus.internal.storageSize=50Gi
Issue: Cost Export Failing to Storage Account
Symptoms: Automated exports not appearing in storage
Solution:
- Verify storage account permissions
- Check export configuration status
- Review activity logs for errors
# Check export status
az costmanagement export show \
--name "aks-monthly-export" \
--scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP"
# View recent export runs
az monitor activity-log list \
--resource-group $RESOURCE_GROUP \
--max-events 50 \
--query "[?contains(operationName.value, 'Microsoft.CostManagement')]"
Issue: High Memory Usage by OpenCost
Symptoms: OpenCost pods consuming excessive memory
Solution:
- Reduce Prometheus scrape frequency
- Limit metric retention period
- Increase resource limits
# Update resource limits
helm upgrade opencost opencost/opencost \
--namespace opencost \
--set opencost.exporter.resources.limits.memory=2Gi \
--set opencost.exporter.resources.requests.memory=1Gi
Next Steps
After implementing AKS cost allocation, consider these advanced configurations:
-
Set Up Cost Budgets and Alerts: Create proactive notifications when namespace costs exceed thresholds
-
Implement Chargeback Process: Distribute costs to business units
-
Optimize AKS Costs: Use cost data to identify optimization opportunities
- Review pod resource utilization vs requests
- Identify idle resources
- Right-size node pools
- Leverage spot instances for non-critical workloads
-
Automate Cost Reporting: Build dashboards and automated reports
- Connect OpenCost to Grafana
- Export cost data to PowerBI
- Create Slack/Teams notifications for cost anomalies
-
Integrate with FinOps Workflows: Establish cloud financial operations practices
- Regular cost review meetings
- Cost optimization sprints
- Showback/chargeback processes
- Budget planning and forecasting
Related Resources
Frequently Asked Questions
Find answers to common questions
Need Professional Help?
Our team of experts can help you implement and configure these solutions for your organization.