Kubernetes cost on EKS and GKE: the same playbook, different levers
My AKS playbook took a production cluster from 7 to 4 nodes by sizing requests on real p95 usage and letting the autoscaler do the shrinking. The method carries over to Amazon EKS and Google Kubernetes Engine unchanged. What changes is where the bill comes from and which buttons each cloud gives you: Karpenter and split cost allocation on AWS, Autopilot's Pod-based billing and cost allocation on Google Cloud. This is the companion guide for both.
10 min read
1. Know where the bill comes from
Both clouds charge a flat fee for the managed control plane: $0.10 per cluster per hour on Amazon EKS and the same on GKE, where the free tier credits one Autopilot or zonal Standard cluster per billing account each month. That is roughly 73 dollars a month per cluster, and rarely what hurts. The real bill is the compute underneath: EC2 instances or Fargate pods on AWS, and on GKE either the nodes of a Standard cluster or, in Autopilot, the resources your Pods request.
One line item deserves a calendar reminder. When an EKS cluster runs a Kubernetes version past standard support, it moves into extended support, and the cluster fee rises to $0.60 per hour. Keeping versions current is cheaper than paying for the delay.
2. Put a price on every namespace
Without attribution, a shared cluster is one big number that nobody owns. AWS solves this with split cost allocation data: the Cost and Usage Report then contains container-level rows, so EKS pods get their share of the instance they ran on, by CPU and memory. On Google Cloud, GKE cost allocation adds the namespace and Kubernetes labels of your workloads to the Cloud Billing export, where a query in BigQuery shows cost per team or per service.
Turn this on before you change anything, and agree on two or three labels everybody uses, such as team and service. The first report almost always surfaces a namespace nobody remembers creating.
# GKE: namespace and label breakdown in the Cloud Billing export (BigQuery)
gcloud container clusters update prod-cluster \
--location=europe-west6 --enable-cost-allocation
# EKS: enable split cost allocation data in the Billing and Cost Management
# preferences; the CUR then has pod-level rows you can sum per namespace and label3. Size requests on p95, exactly as on AKS
This step is cloud-neutral. Pull CPU and memory per container from Prometheus over two to four weeks that include a real peak, set CPU requests around p95 plus headroom and memory requests around the observed maximum plus a buffer, and change one namespace at a time through Git. On EKS, Amazon Managed Service for Prometheus collects the metrics; on GKE, Google Cloud Managed Service for Prometheus does. The queries are the same ones as in my AKS article.
The calculator below shows how many nodes you need once requests follow real usage instead of guesses. That number is your target for the next two steps.
Interactive
How many nodes do your requests really need?
Move the sliders to your numbers. Requests decide where pods fit, so oversized requests buy nodes nobody uses.
Node size
5
nodes today
2
nodes after right-sizing (200m)
60%
less node capacity
CPU only, about 90% of a node is allocatable, at least 2 nodes for availability. Check memory the same way before you remove a node.
All calculators and checklists4. EKS: let Karpenter pick and pack the nodes
On EKS, Karpenter replaces fixed node groups with NodePools that describe what is allowed: instance families, architectures, spot or on-demand. It launches the instance that fits the pending pods and, with the default consolidation policy WhenEmptyOrUnderutilized, removes or replaces nodes when their pods fit elsewhere or on a cheaper machine. That is the moment honest requests turn into fewer nodes.
Allow spot capacity for workloads that tolerate interruptions, such as stateless web tiers, workers and CI runners, and keep databases and anything with long shutdowns on on-demand. If your images build for both architectures, include arm64 so Graviton instances are in the mix. Small, spiky jobs can go to Fargate, which bills per pod instead of per node.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: general
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"] # spot only where interruptions are fine
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
limits:
cpu: "200" # a ceiling, so a bug can't scale the bill
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 1mTip from practice
Set limits on every NodePool. Karpenter will happily launch whatever the pending pods ask for, including the result of a deployment that accidentally requests 64 replicas.
5. GKE: choose Autopilot or tune Standard
GKE gives you two billing models, and they reward different things. In Autopilot, general-purpose workloads are billed on the resources their Pods request. There are no nodes to pack; every millicore you remove from a request comes straight off the bill. That makes Autopilot the place where right-sizing pays the fastest, and where inflated requests hurt the most.
In Standard mode you pay for the nodes, as on AKS, so packing matters again. The cluster autoscaler has an optimize-utilization profile that prioritizes using nodes fully over keeping spare capacity, and it removes nodes more aggressively. Add Spot VMs for tolerant workloads in a separate node pool, and keep the default pool on regular VMs for everything else.
# GKE Standard: pack nodes tighter, scale down sooner
gcloud container clusters update prod-cluster \
--location=europe-west6 --autoscaling-profile optimize-utilization
# A separate Spot pool for workloads that tolerate interruptions
gcloud container node-pools create spot-pool --cluster=prod-cluster \
--location=europe-west6 --spot --enable-autoscaling --min-nodes=0 --max-nodes=56. Commit last, on both clouds
Only once the footprint has shrunk do commitments make sense. On AWS, Compute Savings Plans cover EC2, Fargate and Lambda for a one- or three-year commitment measured in dollars per hour, so they keep working when Karpenter switches instance families. On Google Cloud, committed use discounts come resource-based or spend-based. Cover the stable base that is left after right-sizing, and leave peaks and spot on demand pricing.
The order matters more than the discount: a commitment bought before the clean-up locks in capacity you are about to remove, and the return rules for commitments are strict on every cloud.
7. Keep it small on both
Clusters grow back on every cloud the same way: copied requests, forgotten test namespaces, a scaled-up pool after a load test. The routine is the same too. A short daily cost report per namespace in the team chat, built from the CUR on AWS or the billing export on Google Cloud. Budgets with forecast alerts. A ResourceQuota per namespace so requests can't grow unnoticed. And a calendar reminder for Kubernetes upgrades, so no EKS cluster drifts into extended support.
The method is the same on AKS, EKS and GKE: measure p95, change through Git, let the nodes follow, commit last, report daily. Only the buttons have different names.
Takeaway
EKS and GKE need the same discipline as AKS: attribute per namespace, size requests on p95, let Karpenter or the autoscaler shrink, and commit only to what is left.
Real projects behind this
Sources and official guides
- Amazon EKS pricing
- Amazon EKS: Kubernetes versions, standard and extended support
- AWS: Understanding split cost allocation data
- Amazon EKS best practices: Cost optimization
- Karpenter: NodePools
- Karpenter: Disruption and consolidation
- AWS Compute Savings Plans pricing
- Google Kubernetes Engine pricing
- GKE: Cost allocation
- GKE: Cluster autoscaler and autoscaling profiles
- Google Cloud: Best practices for running cost-optimized Kubernetes applications on GKE
- Compute Engine: Committed use discounts
More articles
- 3,025 pull requests in ten months: an AI-assisted delivery process, from the inside
- The EU AI Act for mid-sized companies: what applies to you, and when
- FinOps for AI spend: tokens, caching, routing and budgets that hold
- Making a restaurant network visible in AI answers: a GEO project, step by step
- How this site is built: static where it can be, streaming where it must
- A landing zone for mid-sized companies, in Terraform or Bicep: the parts that matter
- The legacy migration playbook: waves, parallel runs, and identical row counts and checksums
- MCP security in depth: how to let AI assistants into your product without opening the doors
- Monitoring on business outcomes: green dashboards, empty checkout
- Phone AI agents: the architecture, and the pitfalls nobody shows in the demo
- Restore tests: a backup you have never restored is only a hope
- Streaming AI answers on Azure Static Web Apps: the one header that buffered everything
- Building an AI assistant for WhatsApp, end to end: what it takes beyond the demo
- Air-gapped systems: running critical software with no internet
- Production AI on AWS: Amazon Bedrock and AgentCore in a private setup
- Production AI on Microsoft Foundry: a private reference setup for agents
- /goal, /loop or routines: stop babysitting your coding agent
- Claude's new plans: Max, Ultra and what they mean for teams
- OpenAI Codex at the end of 2026: goal mode, dots and how to lock it down
- GitHub Copilot, Claude Code or Codex? How I'd choose for a company
- GEO basics: how to show up in AI answers
- Production AI on Google Cloud: from Vertex AI to Gemini Enterprise Agent Platform
- Local LLMs in an air-gapped network: AI for your most sensitive data
- I want to run a local LLM: which server should I buy?
- MiniMax M3: an open frontier model you can run yourself?
- Prompt research at scale with an open model
- SEO and GEO together: a practical audit in 6 steps
- Right-size Kubernetes: the playbook that took a cluster from 7 to 4 nodes
- Private AI for sensitive data: a local LLM setup that passes the security review
- From AI prototype to production: the eight layers I add, in order
