Virtual Machines vs AKS in Azure
Virtual machines and AKS represent two fundamentally different approaches to running workloads in Azure. VMs give each workload its own dedicated machine; AKS lets multiple workloads share node capacity. The case for AKS is primarily about density and standardisation — and it only becomes compelling when the operational investment in Kubernetes is justified by the workload scale.
The core trade-off
Running a workload on a VM is straightforward: provision a VM, install the application, and it runs. The VM’s CPU and memory are dedicated to that workload. If the workload uses 10% of the VM’s capacity, 90% is idle and still billed.
Running on AKS means multiple workloads share node capacity. A pod that uses 0.5 vCPU and 512 MiB runs on a node alongside other pods. The node’s unused capacity is available to any pod scheduled there. When the cluster has 10 services, each using an average of 15% of node resources, a single node can potentially run all 10 services at reasonable utilisation — versus 10 individual VMs running at 15% utilisation each.
The break-even point — where AKS overhead is justified by density savings — is typically around 3–5 containerised services. Below that, the operational complexity of running Kubernetes outweighs the density benefits. Above it, AKS typically reduces compute costs while improving deployment consistency.
Comparison across key dimensions
| Dimension | Azure VMs | AKS |
|---|---|---|
| Workload density | 1 VM per workload (typically) | Multiple workloads per node via pod bin-packing |
| Deployment model | SSH + script, Azure VM extensions, or image-based | Container image + Kubernetes manifests (Deployment, Service) |
| Scaling | Manual resize, VMSS autoscale (minutes to add new VM) | HPA + Cluster Autoscaler (pods in seconds, nodes in 1–3 min) |
| Deployment rollout | In-place update or blue/green with load balancer changes | Rolling update, blue/green, canary — built into Kubernetes |
| Self-healing | VM Scale Set health probes can replace failed instances | Kubernetes restarts failed pods automatically, reschedules on node failure |
| Operational overhead | OS patches, agents, per-VM configuration | Kubernetes expertise, cluster upgrades, networking |
| Networking | VNet, NSG, directly assigned IP | Pod networking (Azure CNI), Services, Ingress, Network Policies |
| Observability | Azure Monitor, Log Analytics agent per VM | Container Insights, Prometheus, Grafana — pod-level metrics |
| Application portability | VM-specific — tied to Azure VHD format | Container image — portable to any Kubernetes environment |
Density and cost efficiency at scale
Consider 10 microservices, each requiring 0.5 vCPU and 512 MiB at average load. On VMs, the smallest reasonable VM for each is a Standard_B2s (2 vCPU, 4 GiB) at approximately $35/month — total: $350/month for 10 VMs, utilising roughly 25% of their combined CPU capacity.
On AKS, these 10 pods need a combined 5 vCPUs and 5 GiB RAM. A node pool of two Standard_D4s_v5 VMs (4 vCPU, 16 GiB each) at ~$140/month each = $280/month provides that capacity with headroom — a saving of $70/month at half the utilisation. At higher service count, the density advantage grows.
The AKS node cost also benefits from Spot node pools, which can reduce node VM costs by 60–90% for batch workloads and non-critical services. VM Scale Sets support Spot VMs too, but AKS makes managing mixed on-demand and Spot node pools more straightforward.
The density calculation assumes workloads are containerised and stateless. Workloads with large in-memory state, persistent local storage requirements, or OS-level dependencies cannot always be containerised without significant rework, which changes the cost-benefit calculation.
Deployment consistency and rollouts
VM deployments are often heterogeneous — different VMs may have different versions of runtimes installed, different patch levels, or slightly different configurations that have accumulated over time. “Configuration drift” is a well-documented problem in VM-based infrastructure that leads to “works on this VM but not that one” issues.
Container images enforce reproducibility. Every pod running the same image runs exactly the same binaries, dependencies, and configuration. AKS Deployments support rolling updates natively — new pods are started before old pods are terminated, ensuring continuous availability. Rollbacks are a single command.
# Deploy a new image version with zero downtime
kubectl set image deployment/my-app my-app=myregistry.azurecr.io/my-app:v2.1
# Check rollout status
kubectl rollout status deployment/my-app
# Roll back if the new version is broken
kubectl rollout undo deployment/my-appWhen VMs are the right choice
- Running 1–3 services where AKS operational overhead exceeds density savings
- Applications that cannot be containerised — legacy apps with deep OS dependencies, Windows Services, COM-based applications
- Workloads requiring specific OS versions, kernel modules, or hardware-level isolation
- Lift-and-shift migrations where containerisation would delay time-to-cloud significantly
- Teams without Kubernetes expertise where the learning curve is not justified by current workload scale
When AKS is the right choice
- Running 5+ containerised services where density savings justify the operational investment
- Teams deploying frequently — AKS rolling updates and GitOps workflows are faster and safer than VM-based deployment scripts
- Workloads with variable load that benefit from fast horizontal scaling — pods scale in seconds, new VMs take minutes
- Polyglot microservice environments where different services use different runtimes — each runs in its own container with its own dependencies
- Teams that want application portability across Azure, on-premises, or other clouds
Migration path from VMs to AKS
Moving from VM-hosted workloads to AKS is a multi-step process. The containerisation step is usually the most effort-intensive for legacy applications, but modern applications built with frameworks like .NET, Node.js, or Python containerise quickly.
- Containerise: Write a Dockerfile for each application. Test locally with Docker or Docker Compose. Ensure the application reads configuration from environment variables (not hardcoded or file-based).
- Build and push: Set up a CI pipeline to build the container image and push to Azure Container Registry (ACR). Tag images with the commit SHA or version number.
- Deploy to AKS: Write Kubernetes Deployment and Service manifests. Deploy to a staging AKS cluster and validate.
- Configure Ingress: Install an Ingress controller (NGINX, Azure Application Gateway Ingress Controller) and configure routing rules to replace the VM’s load balancer or direct DNS.
- Cut over traffic: Update DNS to point to the AKS Ingress. Monitor application behaviour. Keep the VM running for a short overlap period as a fallback.
- Decommission VM: Once the AKS deployment is stable, delete the VM, its managed disk, and associated resources.
Common mistakes
- Migrating to AKS before containerising properly. A container image that writes to a local filesystem, stores session state in memory, or assumes a specific hostname will fail unpredictably on Kubernetes. Externalise all state before deploying to AKS.
- Running one service on AKS and expecting cost savings. A single-service AKS cluster is almost always more expensive than the same workload on a VM. AKS cost efficiency comes from density across multiple services.
- Not setting resource requests and limits on pods. Without resource requests, the Kubernetes scheduler cannot make informed placement decisions. Without limits, a noisy pod can consume all node CPU and cause other pods to be throttled or evicted.
- Treating AKS like a VM by scheduling one pod per node. Deploying pods with very high resource requests that prevent bin-packing defeats the density advantage of AKS. Right-size pod requests to match actual utilisation, not theoretical maximums.
Summary
- VMs dedicate resources per workload; AKS enables multiple workloads to share node capacity through pod bin-packing, reducing cost as service count grows.
- AKS delivers density savings at scale (typically 5+ containerised services) but requires Kubernetes expertise and cluster management overhead that a single VM does not.
- AKS rolling updates and self-healing provide deployment consistency and reliability improvements over VM-based deployments at scale.
- The migration path requires containerising applications first — this is the prerequisite and often the primary effort in moving from VM-based to Kubernetes-based hosting.
Frequently asked questions
Can I run the same application on both a VM and AKS?
If the application is containerised, yes. A Docker image runs on a VM (with Docker installed) or as a Kubernetes Deployment on AKS. The containerisation step is the prerequisite. If the application is not containerised and has deep OS dependencies, it typically cannot run on AKS without significant rework.
Is AKS more expensive than a VM for a single service?
For a single service, AKS is usually more expensive because you need at minimum one node VM (typically 2 vCPU, 8 GiB) to run the cluster, and that node cost is similar to or higher than a VM sized for the same workload. AKS delivers cost efficiency through density — multiple services sharing node capacity. For one service, a VM is often more cost-effective.
What is the migration path from VMs to AKS?
The migration path is: containerise the application (write a Dockerfile), test the container image locally and in staging, provision an AKS cluster, deploy the containerised application as a Kubernetes Deployment with a Service, configure an Ingress for HTTP traffic, set up CI/CD to build and push the container image, then decommission the VM. The containerisation step is often the largest effort for legacy applications.