If you’re running Kubernetes on a cloud provider and using the Cluster Autoscaler, Karpenter is worth your attention. Karpenter is a node provisioning project from AWS (now a CNCF project, also supported on Azure and GCP) that takes a fundamentally different approach to node scaling — and the result is faster scale-out, lower costs, and less operational overhead.
The Problem with Cluster Autoscaler
The Kubernetes Cluster Autoscaler has been around since 2016 and works like this:
- Pod can’t be scheduled (pending)
- Autoscaler looks at existing node groups
- Picks the cheapest node group that can fit the pod
- Scales that node group up by adding a node of the pre-configured type
This works, but has real limitations:
- Node groups must be pre-configured: You define your instance types ahead of time
- Slow scale-out: CA has to wait for the cloud provider to provision the node, register it, then reschedule the pod
- Scale-in is conservative: CA is slow to remove nodes to avoid disruptions
- Fixed instance families: Opportunities to use spot instances across many instance types are hard to configure
The result: over-provisioned clusters, suboptimal instance selection, and paying for capacity you’re not using.
How Karpenter Works Differently
Karpenter watches for unschedulable pods directly and provisions individual nodes optimized for those pods:
- Pod can’t be scheduled (pending)
- Karpenter analyzes the pod’s requirements (CPU, memory, GPU, labels, topology constraints)
- Karpenter selects the optimal instance type from a broad list
- Provisions that specific instance via the cloud provider API
- Registers it with the cluster
The key differences:
No pre-configured node groups: Karpenter considers all available instance types and picks the best fit.
Right-sized nodes: If you have 3 pending pods that together need 4 CPU and 8GB RAM, Karpenter can provision a single 4-core/8GB node rather than two smaller ones.
Spot instance optimization: Karpenter can consider dozens of instance types simultaneously, dramatically increasing spot availability and reducing interruptions.
Faster scale-out: By bypassing node groups and directly calling EC2 APIs, Karpenter provisions nodes faster.
Automatic consolidation: Karpenter actively looks for opportunities to consolidate workloads onto fewer nodes and terminate underutilized ones.
Installation on AWS EKS
# Set environment variables
export CLUSTER_NAME=my-cluster
export AWS_DEFAULT_REGION=us-east-1
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# Create IAM role for Karpenter
cat > karpenter-node-trust-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
aws iam create-role \
--role-name KarpenterNodeRole-${CLUSTER_NAME} \
--assume-role-policy-document file://karpenter-node-trust-policy.json
# Attach necessary policies
for policy in AmazonEKSWorkerNodePolicy AmazonEKS_CNI_Policy AmazonEC2ContainerRegistryReadOnly AmazonSSMManagedInstanceCore; do
aws iam attach-role-policy \
--role-name KarpenterNodeRole-${CLUSTER_NAME} \
--policy-arn arn:aws:iam::aws:policy/$policy
done
# Install Karpenter with Helm
helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter \
--version "1.1.0" \
--namespace karpenter \
--create-namespace \
--set settings.clusterName=${CLUSTER_NAME} \
--set settings.interruptionQueue=${CLUSTER_NAME} \
--set controller.resources.requests.cpu=1 \
--set controller.resources.requests.memory=1Gi \
--set controller.resources.limits.cpu=1 \
--set controller.resources.limits.memory=1Gi \
--wait
Configuring NodePool and EC2NodeClass
The core configuration objects in Karpenter:
# EC2NodeClass: defines the node configuration (AMI, networking, storage)
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2023
role: KarpenterNodeRole-my-cluster
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: my-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: my-cluster
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 100Gi
volumeType: gp3
encrypted: true
deleteOnTermination: true
---
# NodePool: defines the pool of node types and constraints
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
metadata:
labels:
karpenter.sh/node-pool: default
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"] # Compute, memory, memory-optimized
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["2"] # At least 3rd gen
taints: []
limits:
cpu: 1000 # Max 1000 CPU across all nodes
memory: 1000Gi
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
budgets:
- nodes: 10% # Don't disrupt more than 10% of nodes at once
Spot Instances at Scale
One of Karpenter’s biggest wins is spot instance optimization:
# Separate NodePool for spot-only workloads
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: spot-workers
spec:
template:
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
# Specify many instance types to maximize spot availability
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r", "t"]
- key: karpenter.k8s.aws/instance-size
operator: In
values: ["large", "xlarge", "2xlarge", "4xlarge"]
taints:
- key: spot
value: "true"
effect: NoSchedule
# Pod tolerating spot nodes
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch-processor
spec:
replicas: 10
template:
spec:
tolerations:
- key: spot
value: "true"
effect: NoSchedule
nodeSelector:
karpenter.sh/capacity-type: spot
containers:
- name: processor
image: myapp:latest
# Make sure batch jobs checkpoint/resume on interruption
By spreading spot workloads across many instance types and sizes, Karpenter reduces interruption probability significantly compared to pinning to one instance type.
Consolidation: The Hidden Cost Savings
Karpenter’s consolidation feature actively looks for opportunities to pack workloads onto fewer nodes:
# Aggressive consolidation for development clusters
spec:
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 60s # Consolidate quickly
budgets:
- nodes: 50% # Up to 50% churn is fine in dev
# Conservative consolidation for production
spec:
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 5m # Wait longer before acting
budgets:
- nodes: 5% # Limit disruption
- schedule: "0 9-17 * * MON-FRI"
nodes: 0% # No disruption during business hours
Consolidation works by evicting pods from underutilized nodes and rescheduling them on other nodes, then terminating the empty node. Over time, this actively reduces your node count to the minimum needed.
Observed Cost Savings
Common reported improvements after migrating from Cluster Autoscaler to Karpenter:
- 20-40% reduction in EC2 costs from better right-sizing and spot utilization
- 50-70% faster scale-out — Karpenter bypasses some of CA’s polling delays
- Fewer “stuck” pending pods — CA could leave pods pending if no node group fit; Karpenter is more flexible
The exact numbers depend heavily on your workload characteristics and how well-configured your node groups were before.
When Karpenter Makes Sense
Karpenter is best for:
- Variable workloads with unpredictable scaling needs
- Mixed spot/on-demand requirements
- AI/ML workloads needing specific GPU instance types
- Development clusters you want to keep cheap overnight
Karpenter adds complexity and isn’t worth it for:
- Small, fixed-size clusters
- On-premises Kubernetes (though Azure and GCP support is growing)
- Homelab clusters (no cloud provider APIs to call)
Conclusion
Karpenter represents a meaningfully better approach to node provisioning than the Cluster Autoscaler. If you’re running EKS (or AKS with the Azure provider) and have variable workloads, the combination of right-sized node provisioning, spot instance optimization, and active consolidation typically delivers 20-40% cost reduction with better scheduling performance.
The migration path from Cluster Autoscaler is well-documented and reversible. Start by running Karpenter alongside your existing node groups to get a feel for how it behaves before fully committing.