Azure
Learn how to integrate Kubeadapt with your Microsoft Azure infrastructure for accurate cost tracking, spot VM pricing, and reserved instance visibility.
Overview
Kubeadapt provides comprehensive Azure integration capabilities:
- Cloud Billing Integration - Connect to Azure Cost Export for accurate cloud costs
- Spot VM Pricing - Real-time pricing for Azure Spot Virtual Machines
- Reserved VM Instances - Automatic tracking of RI utilization and coverage
Prerequisites
- Azure subscription with appropriate permissions
- Kubeadapt installed via Helm chart
- kubectl access to your cluster
- Azure CLI configured (for setup steps)
- Contributor or Cost Management Contributor role
Part 1: Cloud Billing Integration
Step 1: Create Storage Account for Cost Export
-
Navigate to Azure Portal → Storage Accounts
-
Click "+ Create"
-
Configure storage account:
- Resource group: kubeadapt-billing-rg
- Storage account name: myazurestorageaccount (must be globally unique)
- Region: Choose your primary region
- Performance: Standard
- Redundancy: LRS (Locally Redundant Storage)
-
Click "Review + Create", then "Create"
Save the storage account name and resource group for later.
Step 2: Configure Cost Export
-
Navigate to Azure Portal → Cost Management → Exports
-
Select scope:
- Subscription: Choose your subscription
- Or Management Group: For multi-subscription setups
-
Click "+ Add" to create new export
-
Configure export settings:
- Export name: kubeadapt-cost-export
- Export type: "Daily export of month-to-date costs"
- Metric: "Actual Cost (Usage and Purchases)"
- Export type: "Amortized cost" (recommended) or "Actual cost"
-
Set destination:
- Storage account: Select myazurestorageaccount
- Container: Create new container named cost-export
- Directory: Leave empty or use kubeadapt/
-
Set schedule:
- Start date: Today
- Frequency: Daily
-
Click "Create"
Initial export runs immediately. Subsequent exports run daily at ~8 AM UTC.
Step 3: Get Storage Account Credentials
Get storage account access key:
1az storage account keys list \
2 --account-name myazurestorageaccount \
3 --resource-group kubeadapt-billing-rg \
4 --query '[0].value' \
5 --output tsvSave this key securely.
Get storage account connection string:
1az storage account show-connection-string \
2 --name myazurestorageaccount \
3 --resource-group kubeadapt-billing-rg \
4 --query 'connectionString' \
5 --output tsvStep 4: Create Service Principal
Create service principal with Cost Management access:
1az ad sp create-for-rbac --name kubeadapt-cost-reader \
2 --role "Cost Management Reader" \
3 --scopes /subscriptions/YOUR_SUBSCRIPTION_IDSave the output:
1{
2 "appId": "...",
3 "displayName": "kubeadapt-cost-reader",
4 "password": "...",
5 "tenant": "..."
6}Grant additional permissions:
1# Storage Blob Data Reader - read cost exports
2az role assignment create \
3 --assignee <appId> \
4 --role "Storage Blob Data Reader" \
5 --scope /subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/kubeadapt-billing-rg/providers/Microsoft.Storage/storageAccounts/myazurestorageaccount
6
7# Reader role - read resource metadata
8az role assignment create \
9 --assignee <appId> \
10 --role "Reader" \
11 --scope /subscriptions/YOUR_SUBSCRIPTION_IDStep 5: Configure Kubeadapt with Azure Integration
Create a cloud-integration.json file:
1{
2 "azure": {
3 "storage": [
4 {
5 "subscriptionID": "<YOUR_SUBSCRIPTION_ID>",
6 "account": "myazurestorageaccount",
7 "container": "cost-export",
8 "path": "",
9 "cloud": "public",
10 "authorizer": {
11 "authorizerType": "AzureAccessKey",
12 "accessKey": "<STORAGE_ACCESS_KEY>",
13 "account": "myazurestorageaccount"
14 }
15 }
16 ],
17 "azureClientID": "<SERVICE_PRINCIPAL_APP_ID>",
18 "azureClientSecret": "<SERVICE_PRINCIPAL_PASSWORD>",
19 "azureTenantID": "<TENANT_ID>",
20 "azureOfferDurableID": "MS-AZR-0003P"
21 }
22}Azure Cloud Options:
- public - Azure Public Cloud (most common)
- gov - Azure Government
- china - Azure China
- germany - Azure Germany
Offer Durable ID: Common values
- MS-AZR-0003P - Pay-As-You-Go
- MS-AZR-0017P - Enterprise Agreement (EA)
- MS-AZR-0148P - Enterprise Dev/Test
- MS-AZR-0063P - MSDN Platforms
Find your offer ID at: Azure Portal → Subscriptions → Your Subscription → Properties
Create Kubernetes secret:
1kubectl create secret generic cloud-integration \
2 --from-file=cloud-integration.json \
3 --namespace kubeadaptUpdate your Helm values file to mount the service principal secret:
1# values.yaml
2opencost:
3 opencost:
4 exporter:
5 cloudIntegrationSecret: "cloud-integration"
6 cloudCost:
7 enabled: true
8
9 # Mount Azure service principal secret
10 extraVolumes:
11 - name: service-key-secret
12 secret:
13 secretName: azure-service-key
14
15 extraVolumeMounts:
16 - mountPath: /var/secrets
17 name: service-key-secretApply the configuration:
1helm upgrade kubeadapt kubeadapt/kubeadapt \
2 --namespace kubeadapt \
3 -f values.yamlAlternative: Using Managed Identity (AKS)
For AKS clusters, use Azure Managed Identity instead of service principal credentials:
-
Enable AAD Pod Identity or Workload Identity on your cluster
-
Create managed identity:
1az identity create \
2 --name kubeadapt-identity \
3 --resource-group kubeadapt-billing-rg- Assign required roles:
1IDENTITY_ID=$(az identity show --name kubeadapt-identity --resource-group kubeadapt-billing-rg --query principalId -o tsv)
2
3# Cost Management Reader
4az role assignment create \
5 --assignee $IDENTITY_ID \
6 --role "Cost Management Reader" \
7 --scope /subscriptions/YOUR_SUBSCRIPTION_ID
8
9# Storage Blob Data Reader
10az role assignment create \
11 --assignee $IDENTITY_ID \
12 --role "Storage Blob Data Reader" \
13 --scope /subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/kubeadapt-billing-rg/providers/Microsoft.Storage/storageAccounts/myazurestorageaccount
14
15# Reader
16az role assignment create \
17 --assignee $IDENTITY_ID \
18 --role "Reader" \
19 --scope /subscriptions/YOUR_SUBSCRIPTION_ID-
Bind identity to pod (method varies by AAD Pod Identity version)
-
Use simplified cloud-integration.json:
1{
2 "azure": {
3 "storage": [
4 {
5 "subscriptionID": "<YOUR_SUBSCRIPTION_ID>",
6 "account": "myazurestorageaccount",
7 "container": "cost-export",
8 "path": "",
9 "cloud": "public"
10 }
11 ],
12 "azureOfferDurableID": "MS-AZR-0003P"
13 }
14}Managed Identity authentication happens automatically, no client credentials or access keys needed.
Part 2: Spot VM Pricing
Azure Spot VM prices are retrieved from Azure's Rate Card API. The service principal must have appropriate permissions.
How It Works
- Automatic Detection: Kubeadapt detects Spot VMs by checking instance metadata
- Rate Card API: Current spot prices fetched from Azure Consumption API
- Historical Data: Cost exports include actual spot usage costs
Required Permissions
Ensure service principal has these roles (already included in setup):
1az role assignment create \
2 --assignee <SERVICE_PRINCIPAL_APP_ID> \
3 --role "Billing Reader" \
4 --scope /subscriptions/YOUR_SUBSCRIPTION_IDVerification
Check that spot VMs are detected:
1kubectl logs -n kubeadapt deployment/kubeadapt-cost-analyzer | grep -i spotIn the Kubeadapt dashboard:
- Navigate to Node View
- Check Pricing Type column
- Spot VMs should show "Spot" label
Customer-Specific Pricing (Enterprise Agreements)
If your organization has an Enterprise Agreement or Partner Agreement with Azure, you can configure Kubeadapt to use the Consumption Price Sheet API for account-specific discounts.
Prerequisites
- Find your billing account ID in the Azure portal
- Find your subscription's offer ID (Azure Portal → Subscriptions → Your Subscription → Properties)
Configuration
Update your Helm values file to add billing account and offer ID:
1# values.yaml
2opencost:
3 opencost:
4 exporter:
5 extraEnv:
6 AZURE_BILLING_ACCOUNT: "<YOUR_BILLING_ACCOUNT_ID>"
7 AZURE_OFFER_ID: "MS-AZR-0003P" # Your subscription offer IDApply the configuration:
1helm upgrade kubeadapt kubeadapt/kubeadapt \
2 --namespace kubeadapt \
3 -f values.yamlPart 3: Reserved VM Instances
Reserved Instance data is automatically retrieved from your Azure cost export. RIs are applied at billing time and reflected in the cost data.
What Gets Tracked
With Azure cost export configured, Kubeadapt automatically shows:
- RI Utilization - How much of your purchased RI capacity is being used
- RI Coverage - Percentage of VM usage covered by RIs
- Effective Discount - Actual discount percentage from reservations
- Net Savings - Savings compared to Pay-As-You-Go pricing
RI Types Supported
- VM Reserved Instances - 1 or 3-year commitments for specific VM sizes
- Azure Hybrid Benefit - Windows Server license discounts
- Reserved Capacity - Storage and database reservations
Viewing RI Data
RI information appears automatically in:
- Dashboard - Cost breakdown shows PAYG vs Reserved vs Spot
- Node View - Nodes show effective pricing (RI-discounted or PAYG)
- Cost Reports - Historical trends show RI coverage over time
- Namespace View - Per-namespace costs reflect RI savings
Troubleshooting RI Tracking
Issue: RIs not appearing
- Verify cost export has been running for at least 24 hours
- Check export includes "PricingModel" column
- Ensure service principal has "Cost Management Reader" role
Issue: RI amounts seem incorrect
- RIs are applied at the EA enrollment or subscription level
- Shared scope RIs may apply across multiple subscriptions
- Check reservation properties in Azure Portal → Reservations
Multi-Subscription Setup (Enterprise Agreement / Management Groups)
For organizations with multiple Azure subscriptions under an Enterprise Agreement or Management Groups, you need to configure Kubeadapt to access cost data across all subscriptions.
Architecture Overview
Unlike AWS (which uses masterPayerARN for cross-account access), Azure uses Service Principal with multi-subscription permissions and either:
- Centralized Cost Export - Single storage account with Management Group scope
- Per-Subscription Exports - Multiple exports, one per subscription
1Azure Tenant (Enterprise Agreement)
2├── Management Group (Optional)
3│ ├── Subscription 1: aaaa-1111 (AKS Cluster 1)
4│ ├── Subscription 2: bbbb-2222 (AKS Cluster 2)
5│ ├── Subscription 3: cccc-3333 (AKS Cluster 3)
6│ ├── Subscription 4: dddd-4444 (AKS Cluster 4)
7│ └── Subscription 5: eeee-5555 (AKS Cluster 5)
8│
9└── Shared Storage Account: myazurestorageaccount
10 └── Cost Export Container (all subscriptions' data)Approach 1: Centralized Cost Export (Recommended)
This approach uses a single storage account with Management Group-scoped cost export that includes all subscriptions.
Step 1: Create Centralized Storage Account
In your primary subscription (or billing subscription):
1az storage account create \
2 --name myazurestorageaccount \
3 --resource-group kubeadapt-billing-rg \
4 --location eastus \
5 --sku Standard_LRSStep 2: Create Management Group-Scoped Cost Export
- Navigate to Azure Portal → Cost Management (at Management Group level)
- Click Exports → + Add
- Configure:
- Export name: kubeadapt-cost-export
- Scope: Management Group (includes all subscriptions)
- Export type: Daily export of month-to-date costs
- Metric: Actual Cost (Usage and Purchases)
- Storage account: myazurestorageaccount
- Container: cost-export
Step 3: Create Service Principal with Multi-Subscription Access
Create service principal that can access all subscriptions:
1# Create service principal
2az ad sp create-for-rbac --name kubeadapt-multi-sub \
3 --role "Cost Management Reader" \
4 --scopes /providers/Microsoft.Management/managementGroups/YOUR_MANAGEMENT_GROUP_IDGrant storage access:
1# Get service principal object ID
2SP_OBJECT_ID=$(az ad sp list --display-name kubeadapt-multi-sub --query '[0].id' -o tsv)
3
4# Grant Storage Blob Data Reader on billing storage account
5az role assignment create \
6 --assignee $SP_OBJECT_ID \
7 --role "Storage Blob Data Reader" \
8 --scope /subscriptions/PRIMARY_SUBSCRIPTION_ID/resourceGroups/kubeadapt-billing-rg/providers/Microsoft.Storage/storageAccounts/myazurestorageaccountStep 4: Configure cloud-integration.json
For all 5 AKS clusters, use the same configuration:
1{
2 "azure": {
3 "storage": [
4 {
5 "subscriptionID": "aaaa-1111-1111-1111-111111111111",
6 "account": "myazurestorageaccount",
7 "container": "cost-export",
8 "path": "",
9 "cloud": "public",
10 "authorizer": {
11 "authorizerType": "AzureAccessKey",
12 "accessKey": "<STORAGE_ACCESS_KEY>",
13 "account": "myazurestorageaccount"
14 }
15 }
16 ],
17 "azureClientID": "<SERVICE_PRINCIPAL_APP_ID>",
18 "azureClientSecret": "<SERVICE_PRINCIPAL_PASSWORD>",
19 "azureTenantID": "<TENANT_ID>",
20 "azureOfferDurableID": "MS-AZR-0017P"
21 }
22}Key Point: All clusters use:
- Same
storageconfiguration (centralized billing storage) - Same
azureClientID,azureClientSecret,azureTenantID(shared service principal) - Same cost export data source
Step 5: Deploy to Each Cluster
For each of the 5 AKS clusters:
1# Create secret
2kubectl create secret generic cloud-integration \
3 --from-file=cloud-integration.json \
4 --namespace kubeadapt
5
6# Deploy or upgrade Kubeadapt
7helm upgrade kubeadapt kubeadapt/kubeadapt \
8 --namespace kubeadapt \
9 -f values.yamlHow It Works (Centralized Approach)
- Management Group Export: Cost data from all 5 subscriptions flows into single storage account
- Kubeadapt in Subscription aaaa-1111:
- Reads from centralized storage account
- Queries cost data
- Filters by
SubscriptionId = aaaa-1111(automatically)
- Kubeadapt in Subscription bbbb-2222:
- Reads from same centralized storage
- Queries cost data
- Filters by
SubscriptionId = bbbb-2222
- No Double-Counting: Each Kubeadapt-Opencost only reports costs for its own subscription
Approach 2: Per-Subscription Export
If Management Group-scoped export is not available, configure each subscription separately.
Architecture
1Subscription 1: aaaa-1111 (AKS Cluster 1)
2 └── Storage: sub1-billing → Kubeadapt reads sub1 costs
3
4Subscription 2: bbbb-2222 (AKS Cluster 2)
5 └── Storage: sub2-billing → Kubeadapt reads sub2 costs
6
7Subscription 3: cccc-3333 (AKS Cluster 3)
8 └── Storage: sub3-billing → Kubeadapt reads sub3 costs
9
10...Configuration for Subscription 1
1{
2 "azure": {
3 "storage": [
4 {
5 "subscriptionID": "aaaa-1111-1111-1111-111111111111",
6 "account": "sub1billing",
7 "container": "cost-export",
8 "path": "",
9 "cloud": "public",
10 "authorizer": {
11 "authorizerType": "AzureAccessKey",
12 "accessKey": "<SUB1_STORAGE_ACCESS_KEY>",
13 "account": "sub1billing"
14 }
15 }
16 ]
17 }
18}Each subscription gets its own:
- Storage account
- Cost export
- cloud-integration.json configuration
Alternative: Using Azure Managed Identity
For better security, use Workload Identity (Azure's equivalent of AWS IRSA).
Enable Workload Identity on AKS
1az aks update \
2 --name my-cluster \
3 --resource-group my-rg \
4 --enable-workload-identity \
5 --enable-oidc-issuerCreate Managed Identity
1az identity create \
2 --name kubeadapt-identity \
3 --resource-group kubeadapt-billing-rgGrant Permissions
1IDENTITY_PRINCIPAL_ID=$(az identity show --name kubeadapt-identity --resource-group kubeadapt-billing-rg --query principalId -o tsv)
2
3# Cost Management Reader
4az role assignment create \
5 --assignee $IDENTITY_PRINCIPAL_ID \
6 --role "Cost Management Reader" \
7 --scope /subscriptions/SUBSCRIPTION_ID
8
9# Storage Blob Data Reader
10az role assignment create \
11 --assignee $IDENTITY_PRINCIPAL_ID \
12 --role "Storage Blob Data Reader" \
13 --scope /subscriptions/SUBSCRIPTION_ID/resourceGroups/kubeadapt-billing-rg/providers/Microsoft.Storage/storageAccounts/myazurestorageaccountConfigure Helm Values
1opencost:
2 serviceAccount:
3 annotations:
4 azure.workload.identity/client-id: "<MANAGED_IDENTITY_CLIENT_ID>"Simplified cloud-integration.json
1{
2 "azure": {
3 "storage": [
4 {
5 "subscriptionID": "aaaa-1111-1111-1111-111111111111",
6 "account": "myazurestorageaccount",
7 "container": "cost-export",
8 "path": "",
9 "cloud": "public"
10 }
11 ]
12 }
13}No credentials needed - Workload Identity handles authentication automatically.
Key Differences from AWS
| Aspect | AWS | Azure |
|---|---|---|
| Cross-Account Auth | masterPayerARN + AssumeRole | Service Principal with multi-sub permissions |
| Billing Data | CUR in Management Account | Cost Export in Storage Account |
| Identity Method | IRSA (IAM Roles) | Workload Identity (Managed Identity) |
| Configuration | One field: masterPayerARN | Same Service Principal across all clusters |
Approach 1 vs Approach 2 Comparison
| Feature | Centralized (Approach 1) | Per-Subscription (Approach 2) |
|---|---|---|
| Setup Complexity | Low (one export) | High (N exports) |
| Cost | Lower (one storage) | Higher (N storage accounts) |
| Management | Easier (single source) | Complex (multiple sources) |
| Security | Single principal | Per-subscription principals |
| Recommended | ✅ Yes | Only if Mgmt Group unavailable |
Validation
Test your configuration:
1# Check if cloud costs are being retrieved
2kubectl logs -n kubeadapt deployment/kubeadapt-cost-analyzer | grep -i "azure"
3
4# Verify secret is mounted correctly
5kubectl describe pod -n kubeadapt -l app=cost-analyzer | grep cloud-integration
6
7# Check API connectivity
8kubectl exec -n kubeadapt deployment/kubeadapt-cost-analyzer -- \
9 curl -s http://localhost:9003/healthzRecommendation: Use Management Group export for centralized visibility.
Troubleshooting
Common Issues
Issue: "Storage account access denied"
- Verify service principal has "Storage Blob Data Reader" role
- Check storage account firewall settings allow access
- Ensure container name matches export configuration
Issue: "Cost export not found"
- Wait 24 hours after creating export for initial data
- Check export status in Azure Portal → Cost Management → Exports
- Verify container and directory paths match configuration
Issue: "Authentication failed"
- Verify client ID, secret, and tenant ID are correct
- Check service principal hasn't expired
- Ensure subscription ID matches the one with cost exports
Issue: "Rate Card API errors"
- Verify service principal has "Billing Reader" role
- Some subscription types don't support Rate Card API (use cost export data)
- Check Azure service health for API availability
Issue: "Spot pricing missing"
- Spot usage appears in cost export with "Spot" pricing model
- Verify nodes are using spot eviction policy
- Check that VMSS or VM scale sets are configured for Spot
Issue: "Cross-subscription access denied"
- Grant service principal reader role on all subscriptions:
bash
1az role assignment create \ 2 --assignee <SERVICE_PRINCIPAL_APP_ID> \ 3 --role "Reader" \ 4 --scope /subscriptions/<SUBSCRIPTION_ID>
Validation
Test your configuration:
1# Check if cloud costs are being retrieved
2kubectl logs -n kubeadapt deployment/kubeadapt-cost-analyzer | grep -i "azure"
3
4# Verify secret is mounted correctly
5kubectl describe pod -n kubeadapt -l app=cost-analyzer | grep cloud-integration
6
7# Check API connectivity
8kubectl exec -n kubeadapt deployment/kubeadapt-cost-analyzer -- \
9 curl -s http://localhost:9003/healthzTesting Storage Access
Verify cost export files exist:
1az storage blob list \
2 --account-name myazurestorageaccount \
3 --container-name cost-export \
4 --auth-mode login \
5 --output tableDebugging Service Principal
Check service principal permissions:
1# List all role assignments
2az role assignment list \
3 --assignee <SERVICE_PRINCIPAL_APP_ID> \
4 --output table
5
6# Verify specific role
7az role assignment list \
8 --assignee <SERVICE_PRINCIPAL_APP_ID> \
9 --role "Cost Management Reader" \
10 --scope /subscriptions/<SUBSCRIPTION_ID>Support
For additional help:
- Review Cost Attribution Concepts
- Contact authors@kubeadapt.io
Next Steps
- AWS Integration - Configure Amazon Web Services
- GCP Integration - Configure Google Cloud Platform
- Dashboard Overview - Explore cost monitoring features
- Available Savings - Review optimization recommendations