Build1 publisher2 min readPublished
Karpenter moves the instance decision from config-authoring time to the moment pods go pending
A dev.to walkthrough of the provisioning loop shows Karpenter batching pending pods for up to ten seconds, packing them in memory against their own scheduling constraints, then letting EC2 Fleet price the candidates.
The Engineer · Build desk

What happened
- Cluster Autoscaler can only simulate adding a node from a group that already exists, so it cannot invent a node shape for the pods that are pending.
- In the post's example an m5.xlarge with 4 vCPU and 16 GiB hosts exactly one pod requesting 2 vCPU and 14 GiB, stranding 2 vCPU, and the autoscaler counts that simulation as a success.
- Because the simulation needs homogeneous groups, three instance families times two capacity types times three availability zones becomes eighteen node groups, each with its own launch template and AMI upgrade cycle.
- Karpenter collects unschedulable pods in a window that starts at one second and extends to ten as more arrive, then solves the packing across many instance types in memory.
- It hands EC2 Fleet a ranked list under a lowest-price allocation strategy, so an exhausted c6g.4xlarge Spot pool in us-east-1a falls through to the next candidate instead of failing the request.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- cost The cluster owner pays for whole nodes while half the vCPU on each one cannot be scheduled, and that padding recurs every hour the group runs.
- constraint Once EC2 chooses the type from a ranked list, the only levers on node shape are the pod's own requests and constraints; there is no group to point at when you need a specific instance.
- decision A team adopting Karpenter has to re-read its pod specs, because copying a NodePool imports someone else's reading of theirs.
- exposure Pre-binding commits pods to a node that has not registered, so Karpenter's in-memory model of the instance has to match what EC2 actually delivers.
Two of those pods would need 28 GiB, and the node has 16 [3]. So memory is what caps that node at one pod, and the 2 stranded vCPU is a side effect of a memory request: half the node's compute, paid for and unschedulable [4][21]. The group's instance type was picked before anyone knew the pods would ask for 14 GiB.
Cluster Autoscaler behaves correctly throughout. Its simulation asks whether adding one node from group X would let the pod schedule, and the answer is yes every time [1]. The post names the gap: instance decisions get made "at config-authoring time, when you know the least", while the demand signal arrives at scheduling time [9][10].
Karpenter's packing step reads the pod fields the kube-scheduler reads, which is to say resource requests, node selectors, node affinity, taints and tolerations, topology spread constraints and pod affinity rules, and from those it computes which instance types could host the batch [14]. Those fields are the input to the instance decision. A NodePool lifted from another cluster is a set of bounds on a search that your own pod specs drive; the post's stated aim is that readers can reason about a NodePool config rather than copy one [18].
The batching window is what makes that packing worth doing. A Deployment scaled from one replica to fifty arrives as a single batch and produces a handful of large nodes instead of fifty separate provisioning decisions [13]. That example holds when the fifty pods share requests and constraints; because the packing step evaluates topology spread and affinity per pod [14], a batch whose pods must land in different zones will not collapse into a handful of nodes. The window also means the first pod of a growing burst waits for the last, up to ten seconds, before anything launches [12].
Then Karpenter binds the pending pods to a node it has not created yet [17]. The commitment precedes the instance, so whatever Fleet returns has to hold what Karpenter already promised it would hold. The post credits pre-binding with cutting time-to-ready without quantifying it, and the available text stops mid-sentence inside step 5, before the production behaviours the introduction promised [20].
The forecasting framing is the post's own: it calls the padded node group a forecasting problem and says Kubernetes was never designed to solve forecasting on its own [19]. Nothing in Karpenter's loop reads a forecast. It reads requests and constraints at the moment the pods are pending [14][12], and the eighteen-group matrix it replaces, per the post, "goes stale the moment your workload mix changes" [8].
What to watch
- Whether the rest of the post delivers the production behaviours its introduction promises, and which ones they are.
- A published time-to-ready measurement for pre-binding against a normal scheduler round trip.
- Whether Fleet's fallthrough holds under a multi-zone Spot crunch, not just the single-zone c6g.4xlarge case described.