Optimizing cluster costs by prioritizing Spot, choosing efficient instance families, and avoiding overprovisioning; and handling EC2 Spot interruptions and health events automatically through Karpenter's queue processor with pod drain and re-scheduling.

In episode 8 you saw how pod-side scheduling constraints determine NodePool selection. Episode 9 covers the cost side: how to make Karpenter work as cheaply as possible without sacrificing availability. We'll break down two big themes — cost-aware scaling, which defines the pricing strategy, and interruption handling, which keeps applications alive when AWS reclaims capacity.
After this episode, you'll understand how to prioritize Spot, choose efficient instance families, avoid overprovisioning, and how Karpenter responds to EC2 interruption notifications automatically.
Spot is the key to Karpenter's biggest savings. Spot instance prices on AWS are far cheaper than On-Demand, and Karpenter is designed to leverage them via the values list on the karpenter.sh/capacity-type requirement. Stateless, fault-tolerant workloads — like backend APIs, worker queues, and batch jobs — are a great fit for Spot.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: spot-first
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: defaultTip
The order of values in values sets preference, not a hard rule. The leftmost value like spot is tried first, and the values to its right become backup when the first capacity isn't available.
Not all instance types are equally efficient for one workload. Newer generation instances usually offer better performance per unit price than their predecessors. Karpenter supports the karpenter.k8s.aws/instance-generation requirement to filter instance generations.
spec:
template:
spec:
requirements:
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["4"]
- key: node.kubernetes.io/instance-type
operator: In
values:
- m7g.large
- m7g.xlarge
- c7g.large
- r7g.largeThe Gt operator above asks for generations greater than four, so Karpenter won't pick old-generation instances that waste money for the same need.
Overprovisioning happens when available capacity far exceeds the need. The most common cause is an instance type that's too large. This is where binpacking from episode 6 comes into play: let Karpenter pick the smallest sufficient size, and limit the instance type list so it doesn't stray far from the real need.
| Strategy | Effect on Cost |
|---|---|
| Spot first | Big discount for tolerant workloads |
| Filter for latest generation | Better performance per unit price |
| Limit instance size | Avoids overprovisioning |
| Active consolidation | Idle nodes automatically trimmed |
For workloads that must not be interrupted, create a separate NodePool that only contains on-demand. This strategy keeps cost and availability boundaries clear without complicating configuration.
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand"]Spot capacity can be reclaimed by AWS at any time. Before termination, AWS sends a two-minute notice. In addition, AWS also sends health events like instance rebalance recommendations and failed status checks. If this happens to a node without handling, the pods inside simply disappear.
Karpenter handles this via a component called the queue processor. The sequence is as follows:
The queue name configuration lives in the karpenter-global-settings ConfigMap in the karpenter namespace.
apiVersion: v1
kind: ConfigMap
metadata:
name: karpenter-global-settings
namespace: karpenter
data:
aws.interruptionQueueName: karpenter-queue-productionImportant
The SQS queue must be created and connected to EventBridge before Karpenter is installed. Without this queue, Karpenter can't receive interruption notifications and Spot nodes will be lost without a drain.
When an interruption notification is received, Karpenter doesn't delete the node immediately. The process is gradual: the karpenter.sh/interruption annotation is attached to the node to mark the cause, pods start draining, a new node is created if needed, and only after all pods have moved is the instance terminated.
kubectl get nodes -l karpenter.sh/managed=true -o jsonpath='{.items[?(@.metadata.annotations.karpenter\.sh/interruption)].metadata.name}'Warning
The two-minute window given by AWS is a hard limit. Make sure your workload doesn't depend on long-running drain phases, like dumping large persistence, so the process finishes before the instance is terminated.
Besides Spot interruptions, the queue processor also responds to health events. If EC2 detects a problematic instance via status checks, Karpenter treats that node like a node about to be terminated: drained and replaced. This keeps the cluster healthy even when the underlying instance starts failing.
Putting a database or single-replica queue on Spot is an invitation to trouble. An interruption at any moment will cut the service. Limit Spot to workloads that can disappear and be born again.
Installing Karpenter without creating the SQS queue leaves interruption protection inactive. Make sure the EventBridge, SQS, and ConfigMap chain is correct before production.
Cost and availability aren't mutually exclusive. With proper Spot priority, efficient instance selection, and automatic interruption handling, Karpenter can cut costs while keeping applications available.
Key takeaways:
In episode 10, you'll learn to separate workloads across several NodePools with weight and taints, plus best practices for running Karpenter across multiple clusters. See you there!