Karpenter: how to cut Kubernetes costs without sacrificing performance
Introduction
One of the biggest challenges in Kubernetes environments isn't just scaling applications, but making sure infrastructure grows and shrinks efficiently.
In a traditional Kubernetes cluster running on AWS, it's common to find an architecture built on Amazon EKS (Elastic Kubernetes Service) with Managed Node Groups or Auto Scaling Groups (ASGs).
The problem shows up when application demand varies.
Imagine a cluster with:
- APIs;
- workers;
- RabbitMQ or Kafka consumers;
- CI/CD pipelines;
- internal applications;
- processing workloads;
- observability components.
During business hours, the cluster might need 20 nodes. Overnight, maybe only six.
If the Node Groups are sized for peak load, a large part of the infrastructure will sit idle for many hours.
A traditional approach would be to use:
Pods
↓
Horizontal Pod Autoscaler
↓
Pending Pods
↓
Cluster Autoscaler
↓
Auto Scaling Group
↓
EC2The Cluster Autoscaler solves part of the problem, but works mainly by adjusting previously defined node groups.
Karpenter takes a different approach.
Instead of asking:
"Which Node Group should grow?"
it works closer to:
"What resources do these pods actually need, and what infrastructure capacity meets that demand?"
From there, Karpenter can pick different EC2 families, sizes, zones and capacity models.
That flexibility lets you combine:
- Spot Instances;
- On-Demand;
- multiple EC2 types;
- node consolidation;
- pod-driven scalability;
- workloads with different requirements.
The result can be significantly more efficient infrastructure without necessarily hurting application performance.
What Karpenter is
Karpenter is an open source Kubernetes node autoscaling project originally created by AWS.
Its goal is to dynamically provision compute infrastructure based on workload requirements.
Instead of relying exclusively on fixed instance groups, Karpenter watches pods that can't be scheduled and figures out what infrastructure can run them.
Simplifying:
Deployment
↓
Pods
↓
Kubernetes Scheduler
↓
Pending Pods
↓
Karpenter
↓
Requirement analysis
↓
EC2 Fleet / AWS APIs
↓
New EC2
↓
Node joins the cluster
↓
Pods get scheduledKarpenter considers factors like:
- CPU;
- memory;
- architecture;
- availability zone;
- affinity;
- anti-affinity;
- taints;
- tolerations;
- topology spread;
- instance type;
- Spot capacity;
- On-Demand capacity.
In current releases, configuration mainly revolves around objects like NodePool, NodeClaim, and, on the AWS provider, EC2NodeClass.
Key concepts
NodePool
The NodePool determines which types of nodes Karpenter can create.
It defines constraints such as:
architecture
operating system
EC2 family
size
zone
capacity model
taints
labels
limits
disruption policiesA cluster can have several NodePools.
For example:
NodePool: applications
→ Spot
→ c/m/r
→ amd64
NodePool: databases
→ On-Demand
→ r-family
→ amd64
NodePool: monitoring
→ Spot + On-Demand
→ m/r
NodePool: cicd
→ Spot
→ c/mSeparating workloads this way lets you control cost and availability individually.
The documentation recommends NodePools be, when possible, mutually exclusive, reducing ambiguity about which pool should serve a given workload.
EC2NodeClass
The EC2NodeClass holds AWS-specific configuration.
For example:
- AMI;
- subnets;
- Security Groups;
- IAM Role;
- EBS volumes;
- kubelet configuration;
- EC2 tags.
While NodePool answers:
"What type of capacity can I use?"
EC2NodeClass answers:
"How should this instance be created in AWS?"
The same EC2NodeClass can be used by multiple NodePools.
NodeClaim
The NodeClaim represents a concrete capacity request created by Karpenter.
We can visualize it like this:
NodePool
↓
allowed template
↓
Karpenter detects demand
↓
NodeClaim
↓
EC2
↓
Kubernetes NodeThe NodeClaim contains the requirements resulting from combining:
- NodePool;
- pod requirements;
- required resources;
- cloud provider availability.
It's usually not an object the operator creates manually.
How Karpenter works
Imagine three pending pods:
api-1 500m CPU 512Mi
api-2 500m CPU 512Mi
worker-1 2000m CPU 2GiKarpenter notices there isn't enough capacity in the cluster.
It analyzes:
total CPU
total memory
nodeSelector
affinity
topologySpreadConstraints
taints/tolerations
architecture
zones
capacity-typeThen it looks for EC2 offerings capable of running these pods.
Unlike a strategy that determines in advance only:
t3.xlargeit's possible to provide flexibility to use:
c6a.large
c6a.xlarge
c7a.large
m6a.large
m6a.xlarge
m7a.large
t3a.xlarge
...as long as those instances satisfy the defined requirements.
This flexibility particularly matters for Spot.
The more unnecessary restrictions you apply, the smaller the available capacity pool becomes. AWS recommends avoiding overly restrictive instance-type constraints for workloads that can be flexible.
Where the savings actually come from
Karpenter alone doesn't make a cluster cheap.
The cost reduction usually comes from combining several strategies:
Karpenter
+
Spot
+
flexible instances
+
correct requests
+
consolidation
+
pod autoscaling
+
removing idle capacityBefore
Imagine five nodes:
5 × m6i.xlargeEach has roughly:
4 vCPU
16 GiB RAMCapacity:
20 vCPU
80 GiB RAMBut the workloads are using roughly:
8 vCPU
32 GiB RAMThere's a lot of idle infrastructure.
After
With Karpenter, pods can be reorganized and underutilized nodes can become candidates for consolidation.
The cluster might end up looking like, for example:
2 × m6a.xlarge
1 × c6a.xlargeor another combination that satisfies the scheduling requirements.
The actual choice will depend on:
- price;
- availability;
- requests;
- architecture;
- constraints;
- capacity model.
The goal shouldn't be to manually pick the cheapest EC2.
The goal is to give enough flexibility for Karpenter to choose efficient capacity.
Full example: EC2NodeClass
A near-production example:
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: production
spec:
amiSelectorTerms:
- alias: al2023@v20250808
role: KarpenterNodeRole-production
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: production
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: production
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 80Gi
volumeType: gp3
encrypted: true
deleteOnTermination: true
tags:
Environment: production
ManagedBy: karpenterWhat this configuration does
The EC2NodeClass defines how nodes are created in AWS.
amiSelectorTerms:determines the AMI used.
In production, it's advisable to pin a tested AMI version instead of automatically using @latest, preventing a new image from entering the cluster without prior validation. This recommendation is explicitly made by the EKS best-practices documentation for Karpenter.
role:determines the IAM Role used by the instances.
subnetSelectorTerms:lets you discover subnets through tags.
securityGroupSelectorTerms:selects the Security Groups used by the nodes.
volumeType: gp3uses GP3 EBS volumes.
And:
encrypted: trueensures volume encryption.
The role name, AMI version, disk size, tags, subnets and Security Groups should be adapted to your environment.
Spot NodePool for applications
Now we can create a NodePool.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: prod-app
spec:
template:
metadata:
labels:
workload-type: application
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: production
taints:
- key: prod-app
value: "true"
effect: NoSchedule
requirements:
- key: kubernetes.io/arch
operator: In
values:
- amd64
- key: kubernetes.io/os
operator: In
values:
- linux
- key: karpenter.sh/capacity-type
operator: In
values:
- spot
- key: karpenter.k8s.aws/instance-category
operator: In
values:
- c
- m
- key: karpenter.k8s.aws/instance-generation
operator: Gte
values:
- "6"
expireAfter: 720h
terminationGracePeriod: 2m
limits:
cpu: "200"
memory: 400Gi
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 5m
budgets:
- nodes: "20%"This NodePool lets Karpenter choose instances:
c*
m*from generation six onward, as long as they meet the requirements.
Instead of defining:
values:
- m6a.xlargewe offer several possibilities.
This improves the odds of finding available Spot capacity.
Karpenter supports constraints by architecture, operating system, capacity type, category, generation and other instance characteristics.
Taint
The NodePool creates:
prod-app=true:NoScheduleAs a result, only workloads with the matching toleration can run on these nodes.
This keeps random cluster components off capacity meant for applications.
Limits
limits:
cpu: "200"
memory: 400Gisets a growth cap on the pool.
Without spec.limits, maximum growth could end up bounded mostly by the cloud provider's quotas, so setting limits is an important operational and financial safeguard.
Deployment using the NodePool
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-api
namespace: production
spec:
replicas: 4
selector:
matchLabels:
app: orders-api
template:
metadata:
labels:
app: orders-api
spec:
terminationGracePeriodSeconds: 30
tolerations:
- key: prod-app
operator: Equal
value: "true"
effect: NoSchedule
nodeSelector:
workload-type: application
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: orders-api
containers:
- name: api
image: registry.example.com/orders-api:2.8.1
ports:
- containerPort: 8080
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 15
periodSeconds: 20There's a fundamental point here:
resources:
requests:
cpu: 500m
memory: 512MiKarpenter uses requests to understand how much resource the pods need.
If requests are wildly oversized, Karpenter will conclude it needs more infrastructure than is actually necessary.
For example:
Actual CPU usage: 150m
CPU request: 2000mFor Kubernetes, that pod reserves:
2 CPUseven though it only uses a small fraction of that.
So FinOps in Kubernetes starts with correctly sizing workloads.
PodDisruptionBudget
If we use consolidation, we need to think about availability.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: orders-api
namespace: production
spec:
minAvailable: 3
selector:
matchLabels:
app: orders-apiWith four replicas:
replicas = 4
minAvailable = 3a voluntary disruption must preserve at least three available pods.
The PodDisruptionBudget (PDB) helps protect the application during operations like drain and consolidation.
But excessively restrictive PDBs can also prevent Karpenter from consolidating nodes. The documentation itself shows PDBs as a possible cause of nodes marked Unconsolidatable.
So:
too weak a PDB
→ availability risk
too restrictive a PDB
→ infrastructure that's hard to consolidate
correct PDB
→ balance between availability and efficiencyA day-to-day example
Consider a SaaS platform with:
EKS
├── Node.js APIs
├── Go APIs
├── workers
├── RabbitMQ
├── Redis
├── PostgreSQL
├── Prometheus
├── Grafana
└── CI/CD pipelinesDuring business hours:
CPU requested: ~45 vCPUAt night:
CPU requested: ~15 vCPUBefore Karpenter, the company keeps:
12 × m6a.xlargerunning 24 hours a day.
The cluster needs that capacity during peaks but keeps a big chunk of it idle for many hours.
Applying Karpenter
The architecture moves to:
Kubernetes
│
┌───────────┴───────────┐
│ │
APIs/Workers Critical infra
│ │
▼ ▼
NodePool prod-app NodePool critical
│ │
Spot On-Demand
│ │
c/m families m/r familySo:
APIs and workers
Use:
Spotbecause they're:
- replicated;
- stateless;
- interruption-tolerant;
- fast to start.
Databases or extremely sensitive components
Can stay:
On-Demandor on dedicated Managed Node Groups.
The goal isn't to run 100% of the cluster on Spot.
It's to use Spot wherever the architecture can tolerate interruptions.
Spot doesn't just mean cheap infrastructure
Spot has an important characteristic:
capacity can be interrupted.
On AWS, certain Spot interruptions give roughly two minutes of notice. Karpenter has built-in handling for these events and can start a replacement while cordoning/draining the affected node.
For that, it's important to configure interruption handling, including the SQS queue Karpenter uses.
Architecturally:
AWS interruption event
↓
EventBridge
↓
SQS
↓
Karpenter
↓
cordon / taint / drain
↓
new NodeClaim
↓
new EC2Spot should mainly be used for workloads built to disappear and come back.
Spot + On-Demand strategy
An alternative is to allow both models:
- key: karpenter.sh/capacity-type
operator: In
values:
- spot
- on-demandIn NodePools that support multiple capacity models, Karpenter can fall back to alternatives based on availability. The current documentation also covers reserved capacity for specific capacity reservations; this shouldn't be confused with traditional Reserved Instances.
Another, more controlled strategy is to have independent NodePools:
prod-app-spot
prod-app-ondemandand use:
- labels;
- weights;
- affinities;
- topology spread.
The choice depends on the organization's reliability policy.
Consolidation
One of Karpenter's most important features for FinOps is consolidation.
Imagine:
Node A → 20% utilized
Node B → 25% utilized
Node C → 20% utilizedIt might be possible to move the workloads to:
Node D → 70% utilizedand remove the three previous ones.
Karpenter can evaluate:
Node A ──┐
Node B ──┼──► more efficient node
Node C ──┘The documentation describes consolidation through removal or replacement operations, evaluating whether workloads can run on existing capacity or on a more efficient alternative.
consolidateAfter
Consolidation doesn't need to happen immediately.
For example:
consolidateAfter: 5mmeans Karpenter waits for a period of stability before considering certain consolidations.
This is useful for workloads with variable behavior.
Imagine:
10:00 → scales up
10:02 → scales down
10:04 → scales up
10:06 → scales downOverly aggressive consolidation can produce:
creates node
↓
removes node
↓
creates node
↓
removes nodeThis behavior is informally known as node churn.
A well-tuned consolidateAfter reduces unnecessary node movement.
Disruption Budgets
An important safeguard for production is controlling how many nodes Karpenter can interrupt at the same time.
Example:
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 5m
budgets:
- nodes: "10%"This way, Karpenter doesn't indiscriminately run large numbers of simultaneous voluntary interruptions.
NodePool Disruption Budgets limit voluntary actions related to mechanisms like consolidation and drift.
This safeguard matters especially when there are dozens or hundreds of nodes.
Production: separating workloads
A mature architecture might have:
EKS
│
┌───────────────┼────────────────┐
│ │ │
prod-app monitoring database
│ │ │
Spot Spot/OnDemand On-Demand
│ │ │
c/m m/r rAnd also:
ci-cd
│
Spot
│
c/mprod-app
Applications:
Node.js
Go
PHP
JavaCan use Spot when they have:
- multiple replicas;
- readinessProbe;
- PDB;
- proper shutdown;
- distribution across AZs.
CI/CD
Build agents are great Spot candidates when jobs can be retried.
Observability
Can use a hybrid strategy.
Databases
Stateful workloads require more careful analysis.
Karpenter can run stateful workloads, but that doesn't automatically mean a critical database should be placed on Spot capacity.
FinOps: requests are fundamental
Consider:
resources:
requests:
cpu: "4"
memory: 8GiBut observability shows:
CPU P95: 700m
RAM P95: 2.5GiIf there are ten pods:
Kubernetes sees:
40 vCPU
80 GiBeven though actual consumption is far below that.
Karpenter will provision capacity based on what the scheduler needs to reserve.
So the correct optimization sequence is usually:
Metrics
↓
Rightsizing
↓
Correct requests
↓
Karpenter
↓
Consolidation
↓
SpotNot:
wrong requests
↓
Karpenter
↓
"why didn't it save money?"Observability
Karpenter needs to be monitored like any critical platform component.
It exposes Prometheus-format metrics related to provisioning, NodeClaims, the cloud provider, reconciliations and consolidation.
A typical stack:
Karpenter
│
├── metrics ──► Prometheus ──► Grafana
│
└── logs ─────► Loki ────────► GrafanaImportant points to track include:
time to create a node
pending NodeClaims
cloud provider errors
provisioning failures
NotReady nodes
Pending pods
Spot vs. On-Demand ratio
consolidations
interruptions
cost per NodePool
CPU/memory utilization
requests vs. utilizationSome available metrics include data on cloud provider errors and operation durations, plus consolidation-related metrics.
Logs
Controller logs can be inspected with:
kubectl logs \
-n kube-system \
-l app.kubernetes.io/name=karpenter \
--tail=200To follow in real time:
kubectl logs \
-n kube-system \
-l app.kubernetes.io/name=karpenter \
-fIn Loki, you can centralize these logs and build dashboards or alerts for events like:
failed scheduling
insufficient capacity
NodeClaim failed
cloud provider error
unconsolidatableTroubleshooting
Pods stay Pending
First:
kubectl get pods -A --field-selector=status.phase=PendingThen:
kubectl describe pod <pod>Check the:
Eventssection.
Common errors:
Insufficient cpu
Insufficient memory
untolerated taint
node affinity conflict
topology constraintsChecking NodePools
kubectl get nodepoolDetails:
kubectl describe nodepool prod-appCheck:
requirements
limits
conditions
disruptionChecking NodeClaims
kubectl get nodeclaimsThen:
kubectl describe nodeclaim <nodeclaim>This helps identify issues between:
Pod
↓
NodePool
↓
NodeClaim
↓
EC2Checking EC2NodeClass
kubectl get ec2nodeclassAnd:
kubectl describe ec2nodeclass productionProblems can include:
subnet not found
wrong security group
invalid AMI
insufficient IAM
capacity unavailableKubernetes events
One of the most useful commands:
kubectl get events -A \
--sort-by=.metadata.creationTimestampAlso:
kubectl get nodes \
-L karpenter.sh/nodepool,karpenter.sh/capacity-type,node.kubernetes.io/instance-typeThis lets you see something like:
NODE NODEPOOL CAPACITY INSTANCE
node-01 prod-app spot c6a.xlarge
node-02 prod-app spot m6a.large
node-03 critical on-demand m6i.xlargeSecurity
IAM
Karpenter needs to create and remove AWS infrastructure.
So IAM permissions need to follow the principle of least privilege.
Avoid policies like:
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}The controller's and the instances' permissions should have clearly separated responsibilities.
ServiceAccounts
On EKS, prefer workload-specific identity mechanisms, like EKS Pod Identity or IRSA depending on the adopted architecture and version.
Avoid hardcoded AWS credentials in:
env:
AWS_ACCESS_KEY_ID: ...or:
Secret:
access-key: ...when federated identity can be used instead.
Workload isolation
Use:
taints
tolerations
labels
nodeAffinity
NetworkPolicies
namespaces
RBACto prevent workloads from running where they shouldn't.
For example:
database → database nodes
monitoring → monitoring nodes
applications → prod-app nodes
CI/CD → ci-cd nodesKarpenter doesn't replace Kubernetes isolation policies.
Availability
Savings shouldn't come at the cost of availability.
An application on Spot should account for:
Deployment
│
├── replica 1 → AZ A
├── replica 2 → AZ B
└── replica 3 → AZ A/BUse:
topologySpreadConstraints:or equivalent mechanisms to avoid unnecessary concentration of replicas.
Also consider:
PDB
readinessProbe
livenessProbe
startupProbe
terminationGracePeriodSeconds
graceful shutdownWhat to avoid
Pinning a single instance type
Avoid:
- key: node.kubernetes.io/instance-type
operator: In
values:
- m6a.xlargewithout a real need for it.
This considerably reduces flexibility.
It's better to work with requirements:
- key: karpenter.k8s.aws/instance-category
operator: In
values:
- c
- mwhen the workload allows it.
Putting everything on Spot
Don't do:
Entire Kubernetes cluster
↓
100% Spotjust to maximize savings.
Classify workloads by interruption tolerance.
For example:
| Workload | Possible strategy |
|---|---|
| Stateless API | Spot |
| Idempotent worker | Spot |
| CI/CD Agent | Spot |
| Batch processing | Spot |
| Observability | hybrid |
| Critical database | On-Demand |
| Essential components | On-Demand or hybrid strategy |
These are architectural decisions, not absolute rules.
Don't ignore requests
Avoid:
resources: {}In autoscaling clusters, this hurts:
- scheduling;
- capacity;
- bin packing;
- predictability;
- FinOps.
Don't indiscriminately use the @latest AMI in production
Example:
amiSelectorTerms:
- alias: al2023@latestis convenient in test environments.
In production, an unexpected update can introduce changes to:
kernel
container runtime
kubelet
drivers
configurationAWS's recommendation is to use known, production-tested AMIs and validate new versions in non-production environments before rolling out an update.
Development vs. production
Development
Might be acceptable:
100% Spot
aggressive consolidation
few replicas
generic NodePools
shorter expireAfterThe main goal might be cost.
Production
An appropriate strategy tends to include:
Spot + On-Demand
multiple AZs
PDB
topology spread
interruption handling
pinned AMIs
observability
NodePool limits
disruption budgets
reviewed requests
per-workload isolationProduction should prioritize balancing:
cost
+
resilience
+
performance
+
predictabilitySample production architecture
AWS
│
EKS
│
┌────────────┼────────────┐
│ │ │
API Workers Database
│ │ │
HPA KEDA Stateful
│ │ │
└──────┬─────┘ │
│ │
▼ ▼
Pending Pods Critical Pods
│ │
▼ ▼
Karpenter NodePool
│ On-Demand
┌──────────┴──────────┐
│ │
Spot On-Demand
│ │
c/m families m/r familiesIn this model:
HPA
Scales APIs by metrics.
CPU
memory
custom metricsKEDA
Can scale workers based on:
RabbitMQ
Kafka
SQS
PrometheusKarpenter
Scales infrastructure when pods no longer fit existing nodes.
So:
HPA/KEDA
↓
more Pods
↓
Pending Pods
↓
Karpenter
↓
more NodesAnd on the way down:
demand drops
↓
HPA/KEDA reduces Pods
↓
nodes become underutilized
↓
Karpenter consolidates
↓
EC2 removedThis interaction is one of the most efficient forms of Kubernetes elasticity.
A complete FinOps strategy
Karpenter should be part of a bigger strategy.
A mature architecture might use:
Prometheus
↓
usage analysis
↓
VPA recommendations / rightsizing
↓
correct requests
↓
HPA/KEDA
↓
Karpenter
↓
Spot
↓
consolidation
↓
OpenCostOpenCost can complement the environment by showing costs by:
namespace
deployment
pod
label
NodeThat way you can uncover situations like:
Namespace A
CPU requests: 30 cores
Actual CPU: 6 coresIn that case, switching On-Demand for Spot might cut cost, but the structural problem is still there:
oversized requests.Efficient FinOps starts with demand, not just infrastructure price.
When to use Karpenter
Karpenter is especially interesting when there are:
- EKS clusters with variable demand;
- many workloads;
- stateless applications;
- frequent autoscaling;
- Spot usage;
- a need for multiple EC2 types;
- workloads with different requirements;
- significant cost concerns;
- unpredictable growth;
- environments using HPA or KEDA.
The bigger the demand swings, the bigger the potential benefit tends to be.
When Karpenter might not be necessary
Not every cluster needs Karpenter.
Imagine:
3 nodes
3 applications
nearly constant loadIf the environment runs continuously on:
3 × m6i.largeand rarely changes, a Managed Node Group might be enough.
Adding:
Karpenter
NodePools
NodeClaims
EC2NodeClasses
interruption handling
disruption policies
monitoringincreases operational complexity.
In that scenario, the savings might not justify the complexity.
Karpenter tends to bring the most value to dynamic environments.
Checklist before using Karpenter in production
Before migrating, validate:
- reviewed requests and limits;
- replicated applications;
- readiness and liveness probes configured;
- graceful shutdown;
- PodDisruptionBudgets;
- distribution across AZs;
- NodePools separated by responsibility;
- CPU and memory limits;
- Spot/On-Demand strategy;
- interruption handling;
- IAM following least privilege;
- encrypted disks;
- controlled AMIs;
- Prometheus metrics;
- Grafana dashboards;
- centralized logs;
- alerts for provisioning failures;
- real node-interruption tests.
Practical summary
Karpenter changes the traditional logic of Kubernetes infrastructure autoscaling.
Instead of thinking only:
I need to grow my t3.xlarge groupwe can think:
I have these pods
with these requirements
and I need the most suitable capacity availableThat difference lets you take better advantage of:
Spot
+
multiple instance types
+
multiple families
+
consolidation
+
dynamic autoscalingThe end-to-end flow can be represented as:
Demand increases
↓
HPA / KEDA
↓
New Pods
↓
Pending Pods
↓
Karpenter
↓
NodeClaim
↓
Spot / On-Demand EC2
↓
Kubernetes Node
↓
Pods runningWhen demand drops:
fewer Pods
↓
underutilized nodes
↓
consolidation
↓
drain
↓
EC2 termination
↓
cost reductionConclusion
Karpenter shouldn't be treated as just a tool that automatically creates EC2 instances.
Its biggest benefit shows up when it's part of a Kubernetes architecture designed for elasticity.
Cost reduction comes from combining:
rightsizing
+
application autoscaling
+
dynamic provisioning
+
Spot
+
instance-type flexibility
+
consolidation
+
observabilityA cluster with oversized requests, applications that can't tolerate interruptions, and overly restrictive NodePools will keep being inefficient even with Karpenter.
On the other hand, when applications are correctly sized and architected for resilience, Karpenter lets infrastructure track real demand much more closely.
For Platform Engineering, this means offering an infrastructure layer capable of providing different capacity profiles:
applications → Spot
critical → On-Demand
batch → Spot
ci-cd → Spot
database → stable capacityFor SRE, it means combining elasticity with availability, observability and disruption controls.
For FinOps, it means shrinking the gap between provisioned capacity and actually needed capacity.
And that's the core point of Karpenter: not cutting costs simply by picking cheaper machines, but letting Kubernetes use the right amount and type of infrastructure for each moment, while keeping performance, availability and reliability as architectural requirements.
Related articles
Want to apply this to your environment?
Book a free 30-minute call. I will look at your setup and tell you where to start.
Book a callGet new articles by email
One email when a new article on infrastructure, backend and AI is published. No spam, unsubscribe anytime.