Skip to content
All articles

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.

Where the money goes on EKS and GKE, and the levers that move it. The loop itself is the same on every cloud.
Kubernetes cost levers on EKS and GKE side by side: on AWS the EKS control plane at $0.10 per hour, Karpenter consolidating EC2 nodes with spot and on-demand, Fargate billed per pod, split cost allocation data in the Cost and Usage Report and Compute Savings Plans; on Google Cloud the GKE management fee at $0.10 per hour with a free tier cluster, Autopilot billed on Pod resource requests, Standard with the optimize-utilization autoscaler profile and Spot VMs, cost allocation in BigQuery and committed use discounts.

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.

bash
# 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 label

3. 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 checklists

4. 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.

yaml
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: 1m

Tip 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.

bash
# 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=5

6. 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.

More articles

    Get in touch

    Tell me what you're building. I'll reply within 1–2 business days.

    Dominik Heinrich
    Company
    MADO Consulting

    Available worldwide, remote. On-site on request, depending on the country.

    I reply within 1–2 business days. Free slots: see the calendar

    Opens Calendly's booking calendar here. Loading it sends data to Calendly (see Impressum).