How do you tell Kubernetes "run 20% of my app on On-Demand and 80% on Spot"? The answer is not to manage nodes directly — it's to express constraints on pods and let Karpenter figure out what nodes to build.
The Legacy Way (Hard)
With Cluster Autoscaler, you'd create two separate ASGs — one On-Demand, one Spot — and try to balance them using node labels, taints, and manual weight configurations. Every change required updating ASG definitions. Debugging required cross-referencing ASG state with pod scheduling events. It worked, but it was fragile.
The Karpenter Way: Topology Spread Constraints
Kubernetes has a native scheduling primitive called Topology Spread Constraints. It tells the scheduler: "Distribute my pods across some topology domain, and keep the distribution balanced within a defined skew."
Karpenter treats karpenter.sh/capacity-type (the label it applies to nodes indicating spot or on-demand) as a valid topology domain. This means you can spread pods across capacity types the same way you'd spread them across Availability Zones.
# In your Deployment spec
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: karpenter.sh/capacity-type
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: my-service
What this does, step by step:
- You deploy
my-servicewithreplicas: 10. - The Kubernetes scheduler sees 10 pending pods and the topology spread constraint.
- It tries to place pods so that no capacity type has more than 1 extra pod compared to another (the
maxSkew: 1rule). - Karpenter sees pods pending that require both
spotandon-demandnodes, and launches both. - Result: ~5 pods on Spot, ~5 pods on On-Demand — automatically, with no manual node management.
whenUnsatisfiable: ScheduleAnyway is important here: if Spot capacity is unavailable, the scheduler won't block the pods — it will schedule them on On-Demand and accept the imbalance. This prevents a Spot shortage from causing a deployment outage.
Pinning Critical Pods to On-Demand
For pods that must never run on Spot — databases, payment processors, admission webhooks — use a node selector or node affinity rule to hard-pin them to On-Demand nodes:
# In your critical Pod spec
spec:
nodeSelector:
karpenter.sh/capacity-type: on-demand
Karpenter will only schedule these pods on On-Demand nodes and will only launch On-Demand nodes to satisfy them. This is a hard constraint, not a preference.