Amazon EKS — Karpenter for Cost Savings

2 minute read

Karpenter’s node lifecycle management directly reduces costs through consolidation, right-sized provisioning, and intelligent Spot usage. It’s the most impactful cost optimization tool for EKS.


How Karpenter Saves Money

1. Consolidation

Karpenter monitors node utilization and replaces underutilized nodes with smaller ones:

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 1m

Example: You have 3 nodes at 30% CPU each. Karpenter:

  1. Detects underutilization
  2. Launches 1 smaller node that can fit all pods
  3. Drains the 3 old nodes
  4. Result: 1 node instead of 3

2. Just-in-Time Provisioning

Unlike ASG-based node groups (pre-provisioned), Karpenter launches nodes only when pods are pending:

Approach Behavior Waste
ASG (min=3) 3 nodes always running High (idle during low traffic)
Karpenter 0 nodes until pods need them Minimal

3. Right-Sized Instances

Karpenter inspects pod resource requests and selects the optimal instance type:

Pending pods need:
- 3 vCPU total
- 8 GiB memory total

ASG approach: Always launches m6i.xlarge (4 vCPU, 16 GiB) — 25% CPU waste
Karpenter:    Launches m6i.large (2 vCPU, 8 GiB) + c6i.large (2 vCPU, 4 GiB) — packed tightly

4. Bin-Packing Efficiency

Karpenter packs pods tightly onto nodes, reducing the total node count:

Without Karpenter (ASG, one instance type):
┌──────────┐  ┌──────────┐  ┌──────────┐
│ ████░░░░ │  │ ██░░░░░░ │  │ ██████░░ │
│ 50% used │  │ 25% used │  │ 75% used │
└──────────┘  └──────────┘  └──────────┘
3 nodes, average 50% utilization

With Karpenter (mixed instance types):
┌──────────┐  ┌──────────┐
│ ████████ │  │ ██████░░ │
│ 95% used │  │ 80% used │
└──────────┘  └──────────┘
2 nodes, average 87% utilization

5. Spot + On-Demand Mixing

Use separate NodePools for critical (On-Demand) and non-critical (Spot) workloads:

# Spot NodePool
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: spot
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot"]
        - key: node.kubernetes.io/instance-type
          operator: In
          values: ["m6i.large", "m6i.xlarge", "m5.large", "m5.xlarge", "c6i.large", "c6i.xlarge", "r6i.large"]
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
  limits:
    cpu: "100"
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 30s
---
# On-Demand NodePool (critical only)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: on-demand
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand"]
        - key: node.kubernetes.io/instance-type
          operator: In
          values: ["m6i.xlarge", "m6i.2xlarge"]
      taints:
        - key: critical
          value: "true"
          effect: NoSchedule
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
  limits:
    cpu: "32"

Measuring Karpenter Savings

eks-node-viewer

Visualize node utilization in real-time:

go install github.com/awslabs/eks-node-viewer/cmd/eks-node-viewer@latest

eks-node-viewer --resources cpu,memory

Output:

3 nodes (875m/5790m)  15.1% cpu ████░░░░░░░░░░░░░░░░░░░░
3 nodes (1.2Gi/11Gi)  10.9% memory ███░░░░░░░░░░░░░░░░░░░░░

Prometheus Metrics

Karpenter exposes metrics for cost tracking:

# Total nodes by capacity type
count(karpenter_nodes_total) by (capacity_type)

# Node utilization
karpenter_nodes_allocatable{resource_type="cpu"}
vs
karpenter_nodes_total_pod_requests{resource_type="cpu"}

Best Practices

  1. Enable consolidation — single biggest money saver
  2. Set aggressive consolidateAfter — 30s-1m for non-production, 5m for production
  3. Diversify instance types — at least 6-10 types in NodePool
  4. Use Spot for non-critical workloads — 60-90% savings
  5. Set limits.cpu on NodePools — prevent runaway scaling
  6. Monitor with eks-node-viewer — visual confirmation of utilization

Updated: