Amazon EKS cost optimization — the AWS-specific levers that reduce an EKS bill, ranked by payback

EKS Cost Optimization: 9 AWS-Native Levers That Cut the Bill

The AWS-specific levers that actually reduce an Amazon EKS bill — extended support, compute model choice, Karpenter versus Cluster Autoscaler, spot handling, Graviton, VPC endpoints and storage defaults — ranked by payback, with a worked before-and-after example.

Most Kubernetes cost advice is cloud-agnostic, which means it stops exactly where EKS gets interesting. Right-sizing pods and packing nodes matters everywhere. What actually moves an EKS bill are the AWS-specific decisions underneath: which compute model you run, which autoscaler provisions your nodes, whether your control planes have quietly rolled onto extended support, and how much you're paying to move bytes between availability zones.

This guide covers the levers that only exist on EKS, roughly in order of payback. It assumes you already know what EKS charges for — if you don't, start with Amazon EKS pricing explained, which walks every line item with worked monthly totals. This piece is about reducing that bill, not itemising it.

The AWS-Native Levers, Ranked

LeverWhat it changesEffortTypical impact
1. Get off extended supportControl plane rate, per clusterMedium — it's an upgrade$365/mo per cluster
2. Right compute modelAuto Mode vs node groups vs FargateMedium10–30% of compute
3. Karpenter over Cluster AutoscalerNode selection and consolidationMediumLarge — often the biggest single win
4. Spot with proper handlingInstance purchase priceMediumVery large on tolerant workloads
5. GravitonPrice per vCPULow if images are multi-archMeaningful, low risk
6. VPC endpointsNAT data processingLow — minutesFree to do, immediate
7. gp2 → gp3EBS rate per GBLow~20% of EBS spend
8. Consolidate control planes$73/mo per cluster removedHighOnly at cluster sprawl

Lever 6 is the anomaly: it costs nothing, takes minutes, and most clusters have never done it. Start there while you plan the rest.

1. Check Whether You're on Extended Support

This is the most expensive thing on this page and the least discussed, because it isn't a decision anyone makes — it's a decision made for you by the calendar.

EKS charges $0.10 per cluster-hour while your Kubernetes version is in standard support, which covers roughly the first 14 months after that version becomes available. When it lapses, the cluster moves automatically to extended support at $0.60 per cluster-hour. Same cluster, same nodes, six times the control plane rate — $73/month becomes $438.

No approval step, no confirmation email. And it's per cluster, so a dev/staging/prod trio goes from $219/month to $1,314.

# Which of your clusters are on extended support right now?
for c in $(aws eks list-clusters --query 'clusters[]' --output text); do
  printf "%-28s %s\n" "$c" \
    "$(aws eks describe-cluster --name "$c" \
       --query 'cluster.[version,upgradePolicy.supportType]' --output text)"
done

Anything returning EXTENDED is costing you $365/month more than it needs to. The irony is that the clusters most likely to drift are the ones under the strictest change control — Terraform pins a version, nobody schedules the upgrade, and the meter changes on its own.

The fix is operational, not financial: track version lifecycles and upgrade inside the 14-month window. Our EKS upgrade guide covers doing that without downtime.

See which of these levers your cluster actually needs.

Free, read-only. Bin-packing, spot-eligible nodegroups and idle capacity in 10 minutes.

Scan your cluster →

2. Choose the Right Compute Model

EKS gives you three ways to run pods, and the gap between them is larger than most teams assume. This is a cost decision dressed up as an architecture decision.

Managed node groupsEKS Auto ModeFargate
You pay forEC2 instancesEC2 + management surchargevCPU-hours + GB-hours
Billing granularityWhole instanceWhole instancePer pod
Idle capacityYou pay for itYou pay for itNone
Spot eligibleYesYesNo
Savings Plans applyYesEC2 portion onlyYes (Compute SP)
Ops effortHighestLowLowest
Best whenSteady, well-packed, spot-heavySmall team, no platform engineerSpiky or intermittent

EKS compute models compared: managed node groups, Auto Mode and Fargate, showing which capacity you pay for but do not use

Same three pods in each column. The shaded area is capacity you are billed for and not using.

The Auto Mode detail that catches people out: the management surcharge is not discounted by Savings Plans or Reserved Instances — those apply only to the EC2 portion underneath it. If you have heavy commitment coverage, the effective premium against what you actually pay is higher than the headline percentage. Model it against your committed rates, not list price.

The Fargate rule of thumb: it wins when you'd otherwise keep nodes idle. A batch job that runs twice a day, a preview environment that lives for an hour, a service with genuinely spiky traffic — Fargate bills only while pods run. For sustained throughput on well-packed nodes, EC2 is cheaper, and the gap widens once spot is in play.

Plenty of clusters should run both: Fargate for bursty work, node groups for the baseline. For what Auto Mode actually does beyond cost, see our guide to Amazon EKS Auto Mode.

3. Replace Cluster Autoscaler with Karpenter

If you're still on Cluster Autoscaler, this is usually the single biggest available win on an EKS cluster.

The difference is structural. Cluster Autoscaler scales node groups you defined in advance — it can only add more of what you already told it about. Karpenter looks at pending pods and provisions the instance that actually fits them, choosing from the whole EC2 catalogue.

Cost behaviourManaged node groups (fixed)Cluster AutoscalerKarpenter
Instance selectionYou pick, up frontLimited to defined groupsChooses per workload
Bin-packing qualityWhatever you guessedConstrained by group shapesOptimised at provision time
Scale-downManualOnly fully empty nodesActively consolidates
Spot handlingManual setupSeparate groups neededNative, with fallback
Mixed instance typesOne per groupOne per groupAny, in one pool
ARM alongside x86Separate groupSeparate groupSame pool

Cluster Autoscaler leaving four half-empty nodes running versus Karpenter repacking the same pods onto two and terminating the rest

A single DaemonSet pod is enough to keep a 5%-utilised node alive under Cluster Autoscaler — and billing.

Consolidation is the row that matters. Cluster Autoscaler removes a node only once it's completely empty, which in practice rarely happens — a single DaemonSet pod keeps it alive and billing. Karpenter actively repacks workloads onto fewer nodes and terminates what's left over.

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]      # opens the spot lever
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]          # opens the Graviton lever
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"]
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized   # the cost-relevant line
    consolidateAfter: 30s
    budgets:
      - nodes: "10%"                                # caps churn

Three settings in that block do most of the work — allowing spot, allowing arm64, and enabling consolidation. The budgets stanza limits how much disruption happens at once.

Before you enable aggressive consolidation, make sure workloads carry PodDisruptionBudgets. Otherwise you'll trade cost for availability incidents, which is not a trade anyone thanks you for. For the mechanics rather than the economics, see our Karpenter autoscaling guide.

4. Run Spot Properly

Spot is the largest discount AWS offers and the one teams most often adopt badly, then abandon after an incident. The EKS-specific pieces are what make it safe.

  • Diversify across instance families and zones. A pool spanning c6i, m6i, r6i and their Graviton equivalents across three AZs is far less likely to be reclaimed at once than a pool of one type.
  • Use capacity-optimized allocation rather than lowest-price. It picks from the deepest capacity pools, which materially reduces interruption frequency for a marginal price difference.
  • Enable Capacity Rebalancing. AWS signals when an instance is at elevated risk before the two-minute notice, giving you time to drain gracefully rather than react.
  • Handle the interruption. Karpenter does this natively; on managed node groups you want AWS Node Termination Handler.
  • Keep an on-demand floor for stateful workloads, controllers, and anything with a hard availability target.
# Only workloads that explicitly tolerate spot land there
tolerations:
  - key: "karpenter.sh/capacity-type"
    operator: "Equal"
    value: "spot"
    effect: "NoSchedule"

# And keep replicas spread so one reclamation cannot take the service out
topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: ScheduleAnyway
    labelSelector:
      matchLabels:
        app: checkout

5. Move to Graviton Where You Can

AWS Graviton instances offer better price-per-vCPU than their x86 equivalents, and on EKS the switch is usually less work than teams expect — because Karpenter can run both architectures from a single NodePool.

The blocker is almost never Kubernetes. It's whether your container images are built for arm64. Most mainstream base images and official charts already publish multi-arch manifests; the friction sits with your own images and any vendored binaries.

# Does this image already support arm64?
docker manifest inspect <your-image>:<tag> \
  | jq -r '.manifests[].platform | "\(.os)/\(.architecture)"'

# Build multi-arch going forward
docker buildx build --platform linux/amd64,linux/arm64 -t <image>:<tag> --push .

Start with stateless services that use standard base images. Leave anything with compiled native dependencies until last.

6. Stop Paying to Move Your Own Bytes

This is the cheapest item on the page and the most neglected. None of it appears under "EKS" on your bill — it's VPC and EC2 line items — which is exactly why nobody owns it.

  • Gateway VPC endpoints for S3 and DynamoDB are free. They route that traffic off your NAT Gateway entirely, removing per-GB processing charges. There is no downside and no reason not to have done this already.
  • Interface endpoints for ECR and CloudWatch Logs beat NAT above roughly 626 GB/month to that service. On a cluster that scales nodes frequently, image pulls alone usually clear that.
  • Cross-AZ traffic is charged in both directions. Kubernetes doesn't consider zones when scheduling by default, so chatty service pairs generate volume silently. Topology-aware routing keeps traffic zone-local where the workload tolerates it.
  • Collapse LoadBalancer Services. Every Service of type LoadBalancer provisions a billable AWS resource. Routing through a shared Ingress controller turns many into one.
# How many billable load balancers is this cluster running?
kubectl get svc -A --field-selector spec.type=LoadBalancer --no-headers | wc -l

# Keep Service traffic in-zone where the workload allows it
kubectl annotate service checkout \
  service.kubernetes.io/topology-mode=Auto

7. Fix Storage Defaults

Small numbers that persist forever, which makes them worth an afternoon.

  • Migrate gp2 to gp3. Cheaper per GB at equivalent baseline performance, with IOPS and throughput provisioned independently. If your StorageClass still defaults to gp2, every new volume compounds the problem.
  • Reclaim orphaned volumes. A PersistentVolume with persistentVolumeReclaimPolicy: Retain outlives the claim that created it. Nobody goes back for these.
  • Add lifecycle policies to snapshots. Automated snapshots without expiry are an invisible, compounding leak.
# Volumes still billing after their claim is gone
kubectl get pv -o custom-columns='NAME:.metadata.name,CAP:.spec.capacity.storage,STATUS:.status.phase,POLICY:.spec.persistentVolumeReclaimPolicy' \
  | grep -E 'Released|Available'

# Is your default StorageClass still gp2?
kubectl get storageclass -o custom-columns='NAME:.metadata.name,TYPE:.parameters.type,DEFAULT:.metadata.annotations.storageclass\.kubernetes\.io/is-default-class'

8. Consolidate Control Planes

Every EKS cluster costs $73/month before a single pod runs. That's noise on one production cluster and real money across twenty.

Cluster-per-team and cluster-per-environment patterns multiply more than the control plane fee: each cluster in its own VPC needs its own NAT Gateways, which is roughly another $98/month for a three-AZ setup. Ten clusters is $730 in control planes plus close to $1,000 in NAT before any compute.

Namespace-based multi-tenancy with ResourceQuotas and NetworkPolicies achieves most of the isolation for a fraction of the cost. That isn't always the right call — regulatory separation and blast-radius requirements are legitimate reasons to keep clusters apart — but "each team wanted their own" usually isn't.

9. Commit Last, Not First

Savings Plans and Reserved Instances lower your rate in exchange for a one- or three-year commitment. Compute Savings Plans apply flexibly across instance families, sizes and regions, and also cover Fargate — which suits EKS well, because your node mix changes as workloads do.

Sequence matters more than the discount. Commit before optimising and you lock in capacity you were about to stop needing. Do levers 2–5 first, let the new baseline settle for a few weeks, then commit to the floor you're confident about and let spot and autoscaling cover everything above it.

Three things commitments never cover: data transfer, NAT Gateway charges, and EBS. Those stay at list price no matter what you sign — which is why levers 6 and 7 keep their value even on a heavily committed account.

A Worked Example

An illustrative mid-size production cluster in us-east-1 — three AZs, on-demand managed node groups, Cluster Autoscaler, a version that lapsed into extended support four months ago. Figures are modelled from published rates, not measured from a specific customer.

Line itemBeforeActionAfter
Control plane$438Upgrade off extended support$73
EC2 nodes$3,360Karpenter + consolidation + 60% spot~$1,750
NAT processing$90Gateway + ECR endpoints~$35
Cross-AZ transfer$80Topology-aware routing~$45
EBS$164gp3 + reclaim orphans~$120
Load balancers$60Consolidate behind Ingress~$25
Total$4,192 → ~$2,048

The control plane line is the one worth staring at. It contributes $365 of the saving for an afternoon of upgrade work, and it wasn't a cost anyone chose to incur.

What This Guide Deliberately Doesn't Cover

Kept separate on purpose, so each question has one home:

Key Takeaways

  • Check extended support today. It's $365/month per cluster, applied automatically, and most teams don't know which of their clusters are on it.
  • Karpenter over Cluster Autoscaler is usually the biggest single win — consolidation is the reason, not instance selection.
  • Auto Mode's surcharge isn't covered by Savings Plans. Model it against committed rates.
  • Fargate wins on intermittent workloads, EC2 on sustained ones. Many clusters should run both.
  • Gateway VPC endpoints are free. Do this before anything else on the list.
  • Commit last. Optimise, let the baseline settle, then buy commitments against the floor.
  • Commitments never cover transfer, NAT or EBS — those levers keep their value regardless.

See Which Levers Apply to Your Cluster

Every item above is worth a different amount depending on where your waste actually sits.

Frequently Asked Questions

What is the fastest way to reduce Amazon EKS costs?
Start by checking whether any cluster has rolled onto extended support, which costs $0.60 per cluster-hour against $0.10 in standard support — $365 per month per cluster for no additional benefit. After that, the largest lever is usually replacing Cluster Autoscaler with Karpenter so that underutilised nodes are actively consolidated rather than left running.
What is EKS extended support and why did my bill increase?
EKS charges $0.10 per cluster-hour while your Kubernetes version is in standard support, roughly the first 14 months after release. When that lapses the cluster moves automatically to extended support at $0.60 per cluster-hour, taking the control plane from about $73 to $438 per month. There is no approval step, and it applies per cluster.
Is Karpenter cheaper than Cluster Autoscaler on EKS?
Cluster Autoscaler can only scale node groups you defined in advance and removes a node only when it is completely empty, which rarely happens because a single DaemonSet pod keeps it alive. Karpenter provisions the instance that fits pending pods from the whole EC2 catalogue and actively repacks workloads onto fewer nodes, terminating the remainder.
Does EKS Auto Mode cost more than managed node groups?
It adds a management charge on top of the EC2 instance cost. The important detail for budgeting is that Savings Plans and Reserved Instances do not discount that surcharge — they apply only to the EC2 portion underneath it. If you have heavy commitment coverage, the effective premium against what you actually pay is higher than the headline rate suggests.
Is Fargate cheaper than EC2 nodes on EKS?
Fargate wins when you would otherwise keep nodes idle — batch jobs, preview environments, genuinely spiky traffic — because it bills per pod rather than per instance. For sustained throughput on well-packed nodes, EC2 is cheaper, and the gap widens once spot is in use, since Fargate on EKS has no spot option.
How do I use spot instances on EKS without causing outages?
Diversify across several instance families and availability zones, use the capacity-optimized allocation strategy rather than lowest-price, enable Capacity Rebalancing so you get early warning before the two-minute notice, and handle interruptions with Karpenter or AWS Node Termination Handler. Keep an on-demand floor for stateful workloads and controllers.
What is the cheapest EKS cost optimization to implement?
Add Gateway VPC endpoints for S3 and DynamoDB. They cost nothing, take minutes to deploy, and immediately remove that traffic from NAT Gateway per-GB processing charges. For other services, interface endpoints become cheaper than NAT above roughly 626 GB per month, and ECR image pulls usually clear that on any cluster that scales nodes often.
Is it worth moving EKS workloads to Graviton?
Usually yes, provided your container images are built for arm64. Kubernetes itself is rarely the blocker, and Karpenter can run both architectures from a single NodePool, so migration can be gradual. Start with stateless services using mainstream base images, which mostly publish multi-arch manifests already, and leave anything with compiled native dependencies until last.
When should I buy Savings Plans for EKS?
Commit last. Optimise first, let the new baseline settle for a few weeks, then commit only to the floor you are confident about and let spot and autoscaling cover the variable layer. Committing before optimising locks in capacity you were about to stop needing. Note that commitments never cover data transfer, NAT Gateway charges or EBS.
How much does running multiple EKS clusters cost?
Each cluster costs $73 per month for its control plane before any pods run, and each cluster in its own VPC needs its own NAT Gateways, adding roughly $98 per month for a three-AZ setup. Ten clusters is $730 in control planes plus close to $1,000 in NAT before compute. Namespace-based multi-tenancy achieves most of the isolation for a fraction of that.
Why can I not see my real EKS costs in AWS Cost Explorer?
Almost nothing on an EKS bill is labelled EKS. Nodes appear as EC2 charges, volumes as EBS, and NAT Gateways as VPC charges, so Cost Explorer filtered to the EKS service shows little beyond the control plane fee. Tag all cluster resources at creation, enable split cost allocation data for pod-level attribution, and use a Kubernetes-native tool for per-namespace visibility.
Should I migrate EKS storage from gp2 to gp3?
Yes, and it is one of the easier wins. gp3 is cheaper per GB than gp2 at equivalent baseline performance, and lets you provision IOPS and throughput independently. Check whether your default StorageClass still specifies gp2 — if it does, every new volume compounds the problem even after you migrate the existing ones.