Karpenter Blueprint: Overprovision capacity in advance to increase responsiveness (EKS Auto Mode)¶
Note¶
If you're running Karpenter OSS (not EKS Auto Mode), use capacity-buffers instead. CapacityBuffers pre-provision real, Ready nodes ahead of demand using virtual placeholder pods that only exist in Karpenter's scheduling simulation, no dummy Deployment, no preemption overhead, and no manual instance-shape guesswork. This blueprint's dummy-workload pattern predates that feature and remains here only because CapacityBuffers are not yet supported on EKS Auto Mode (Auto Mode doesn't expose Karpenter feature gates).
Separately, Karpenter also supports static capacity (spec.replicas on a NodePool), which maintains a fixed node count regardless of pod demand but is excluded from cost-based consolidation entirely. See static-nodepool if that fits your use case better than overprovisioning.
Purpose¶
This blueprint keeps your EKS Auto Mode cluster overprovisioned so workloads are scheduled almost instantly. It deploys a dummy workload with a low PriorityClass to reserve capacity. When real workloads arrive, the dummy pods are preempted and your pods start on already-provisioned nodes.
With a flexible NodePool (a recommended practice for efficient compute), Karpenter optimizes for cost — meaning a single dummy pod would trigger a small, cheap instance that doesn't represent useful overprovision. This blueprint uses nodeAffinity to guarantee minimum instance requirements on the dummy workload, and podAntiAffinity to ensure each replica reserves a separate node. Together, the replica count controls how many right-sized nodes are kept overprovisioned. Adjust the nodeAffinity constraints to match your actual workload's instance requirements so the overprovisioned nodes are genuinely useful when preemption happens.
This pattern is useful in scenarios where provisioning latency directly impacts workload performance, for example:
- Data pipelines that launch a burst of pods simultaneously
- AI/ML inference endpoints that autoscale on traffic spikes, where cold-starting a large instance adds minutes of latency
- CI/CD runners that spin up in bursts when multiple PRs merge and waiting for nodes serializes builds
- Event-driven workloads like webhook processors or queue consumers that scale from near-zero and risk dropped events during the scale-up window
- Scheduled batch jobs (e.g., nightly ETL, end-of-day reconciliation) where the launch time is known and you want zero provisioning delay at kickoff
Requirements¶
- An EKS cluster with Auto Mode enabled, and an EKS Access Entry granting
AmazonEKSAutoNodePolicyto the node IAM role used by Auto Mode. - This blueprint uses the default
NodeClass(eks.amazonaws.com/v1) and requires no customNodePoolorNodeClassmanifests.
If you're using the Terraform template under
cluster/automode/in this repo, the cluster, node IAM role, and Access Entry are all created for you — you can skip the manual Access Entry step below.
If you are not using the cluster/automode/ Terraform template, configure the Access Entry manually:
aws eks create-access-entry \
--cluster-name $CLUSTER_NAME \
--principal-arn <node-role-arn> \
--type EC2
aws eks associate-access-policy \
--cluster-name $CLUSTER_NAME \
--principal-arn <node-role-arn> \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSAutoNodePolicy \
--access-scope type=cluster
Deploy¶
Let's start by deploying the "dummy" workload:
kubectl apply -f dummy-workload.yaml
After waiting for approximately two minutes, notice how Karpenter will provision the machine(s) needed to run the "dummy" workload:
> kubectl get nodeclaims
NAME TYPE ZONE NODE READY AGE
default-66hfd m6g.xlarge us-east-1b ip-10-0-25-181.ec2.internal True 7m20s
default-lgmtl m6g.xlarge us-east-1b ip-10-0-29-137.ec2.internal True 7m20s
And the "dummy" pods are now running to reserve this capacity:
> kubectl get pods 7s
NAME READY STATUS RESTARTS AGE
dummy-workload-b48bcd44-f24ng 1/1 Running 0 7m8s
dummy-workload-b48bcd44-fghws 1/1 Running 0 7m8s
Results¶
When you deploy the actual workload, the dummy pods are evicted. So, let's deploy the following workload to test it:
kubectl apply -f workload.yaml
Notice how your new pods are running within seconds, and some "dummy" pods are "Pending":
> kubectl get pods
NAME READY STATUS RESTARTS AGE
dummy-workload-b48bcd44-p7htz 0/1 Pending 0 13s
dummy-workload-b48bcd44-z76nf 0/1 Pending 0 13s
workload-6db87b48b4-28sxx 1/1 Running 0 74s
workload-6db87b48b4-9vmr8 1/1 Running 0 13s
workload-6db87b48b4-cs2fs 1/1 Running 0 74s
workload-6db87b48b4-dbk79 1/1 Running 0 13s
workload-6db87b48b4-gnhb6 1/1 Running 0 52s
workload-6db87b48b4-h9bfc 1/1 Running 0 33s
workload-6db87b48b4-hmkrx 1/1 Running 0 74s
workload-6db87b48b4-kpx5m 1/1 Running 0 33s
workload-6db87b48b4-n77fr 1/1 Running 0 74s
workload-6db87b48b4-nvf55 1/1 Running 0 74s
workload-6db87b48b4-tdpfh 1/1 Running 0 74s
workload-6db87b48b4-xx9kx 1/1 Running 0 74s
workload-6db87b48b4-z2g7n 1/1 Running 0 52s
workload-6db87b48b4-zcnbk 1/1 Running 0 74s
After waiting for approximately two minutes, you'll get the overprovision capacity and see all pods running:
> kubectl get nodeclaims
NAME TYPE ZONE NODE READY AGE
default-66hfd m6g.xlarge us-east-1b ip-10-0-25-181.ec2.internal True 11m
default-lgmtl m6g.xlarge us-east-1b ip-10-0-29-137.ec2.internal True 11m
default-nzg92 m6g.xlarge us-east-1c ip-10-0-44-194.ec2.internal True 57s
default-xrvmt m6g.xlarge us-east-1c ip-10-0-34-140.ec2.internal True 57s
The new machine is there because some "dummy" pods were pending and they exist to reserve capacity. If you think you won't need those "dummy" pods while your workload is running, you can reduce the "dummy" deployment replicas to 0, and Karpenter consolidation will kick in to remove unnecessary machines.
> kubectl scale deployment dummy-workload --replicas 0
deployment.apps/dummy-workload scaled
> kubectl get nodeclaims
NAME TYPE ZONE NODE READY AGE
default-nzg92 m6g.xlarge us-east-1c ip-10-0-44-194.ec2.internal True 2m16s
default-xrvmt m6g.xlarge us-east-1c ip-10-0-34-140.ec2.internal True 2m16s
Cleanup¶
To remove all objects created, run the following commands:
kubectl delete -f .