Kubernetes Cost Calculator
Estimate your cluster's cost on EKS, GKE or AKS, including load balancers, disks and data transfer.
Before you start: what to have readyShow
- Total CPU and memory requested by your application pods. The script on this page sums them per namespace and pod. Requests, not limits and not usage: requests decide how many nodes run, and the nodes are what you pay for. Leave out DaemonSet and system pods.
- Typical and peak use of those requests, optional. From your metrics history over a representative period, CPU and memory separately. They change the potential savings only, never the estimate.
kubectl top podsis one snapshot, not a peak. - Your node shape, and its hourly rate if you pay less than list. vCPU and memory. Each platform prices the list machine with that shape; enter your own rate for a committed or negotiated price.
- Disk per node, and the number of billed cloud load balancers. One Ingress can share a load balancer across many services, so count load balancers on the bill rather than Services. Only the base fee is priced.
- Monthly egress and traffic between zones, in GB. From your bill rather than from a guess. On AWS each GB between zones is charged on both sides; a figure from a cost export already counts both, and the page has a switch for that.
Each one fills in the fields below for a typical workload of that kind. You can change any of them.
Share of the CPU request pods use, from your metrics history; it can pass 100%. It changes the potential savings only, never the bill.
Advanced assumptionsShow
Optional. Checks one indivisible pod against a node’s allocatable capacity, which the aggregate estimate cannot. 0 skips the check.
- Nodes (45)
- $12,614.40
- Everything else included
- $1,027.55
- Potential savings, not guaranteed
- $864.96/mo
$163,703.40 a year. USD, 730 hours a month, list prices.
USD · 730 hours a month, steady capacity · Amazon EKS, us-east-1 (N. Virginia) · 8 vCPU / 32 GiB nodes at $0.384/hr (m5.2xlarge list price) · disk, load balancer and egress at AWS list rates.
| Nodes45 x $0.384/hr x 730 h (m5.2xlarge), 15 in each of 3 clusters | $12,614.40 |
| Control plane3 clusters at $0.10/hr | $219.00 |
| Node disks100 GiB per node at $0.08/GiB-month | $360.00 |
| Load balancers, base fee only6 at $0.0225/hr each; capacity units and data processed not included | $98.55 |
| Internet egress3,000 GB charged, tiered from $0.09/GB, after 0 GB free allowance | $270.00 |
| Traffic between zones$0.01/GB in each direction, $0.02 per GB entered | $80.00 |
| Total | $13,641.95 |
This is a simplified capacity estimate, not a scheduling guarantee. Large pods, affinity, topology, node pools, and failover requirements can increase node count.
Not included in this estimate
- Load balancer capacity units, rules and data processed (base fee only is included)
- NAT gateways and public IPv4 addresses
- Persistent volumes for applications, beyond the node disks
- Logging, monitoring, backups and other external services
- LLM tokens, paid agent tools, vector databases and GPU inference
- Taxes, support plans, and committed or negotiated discounts
Potential savings from rightsizing requests
Requests reserve capacity for scheduling; they are not a cap. Lowering a CPU request does not throttle a pod (a CPU limit does), but under contention CPU is shared in proportion to requests. Lowering a memory request does not by itself cause an OOM kill (passing a memory limit, or the node running out, does), but pods using more than they request are evicted first when a node runs short of memory.
How the node count is worked outShow
| Per node, then fleet | CPU | Memory |
|---|---|---|
| Node capacity | 8 vCPU | 32 GiB |
| Kubelet and OS reservation, 12% | -1 | -4 |
| DaemonSet allowance | -0 | -1 |
| Allocatable to application pods | 7 vCPU | 28 GiB |
| Usable after 20% headroom | 5 vCPU | 22 GiB |
| Fleet usable, 45 nodes | 244 | 996 |
| Requested by application pods | 240 | 900 |
| Typical use (entered) | 84 | not set |
| Peak use (entered) | 144 | not set |
Requests are split evenly across the 3 clusters, and each cluster rounds up to whole nodes, at least one. A cluster is full when its requests reach the usable capacity above. Headroom, the reservation and the DaemonSet allowance are capacity you need, not waste. CPU decides the count, so the other resource has slack; a different node shape may fit better.
And the carbon
Running this today: about 2.43 to 5.82 t COâ‚‚e a year in us-east-1 (343 g/kWh).
- Rightsize requests to peak usesaves 162 to 388 kg COâ‚‚e a year (7%)
Spot nodes changes the price, not the hardware or the hours, so it saves no carbon. An estimate of the servers’ electricity (compute and memory, with data center overhead), location-based, from Cloud Carbon Footprint’s coefficients; your provider’s own carbon tool is the authoritative figure. Estimate a whole workload or see the methodology.
Make the change
Read-only measurement first. Nothing here changes requests across the cluster from the page's aggregate figures, and nothing changes limits.
Save as k8s-requests.py and run python3 k8s-requests.py. Read-only: it lists each pod's container requests beside its live use, matched by namespace and pod name, and totals the application requests this page asks for (DaemonSet and static pods apart). Needs kubectl and Python 3.8+, and metrics-server for the use columns. Live use is one snapshot: take typical and peak figures from your metrics history before changing anything.
#!/usr/bin/env python3
"""Application pod requests beside live use, per pod. Read-only.
Needs kubectl (read access to pods), Python 3.8+, and metrics-server for the
use columns. Offline: --pods pods.json [--metrics metrics.json], from
kubectl get pods --all-namespaces -o json
kubectl get --raw /apis/metrics.k8s.io/v1beta1/pods
Pods are matched on namespace AND name. Requests are the sum over regular
containers plus restartable (sidecar) init containers. Not reproduced, only
flagged: other init containers (the effective request can be higher while
they run), pod overhead, and pod-level resources. DaemonSet and static pods
are totalled apart: the calculator's per-node allowance covers them.
Live use is ONE snapshot. It shows outliers; it does not prove a smaller
request is safe. Take typical and peak use from your metrics history over a
representative period (two weeks or more, busiest days included).
"""
import argparse, json, subprocess, sys
from decimal import Decimal
UNITS = {"Ki": 2**10, "Mi": 2**20, "Gi": 2**30, "Ti": 2**40, "Pi": 2**50, "Ei": 2**60,
"n": Decimal("1e-9"), "u": Decimal("1e-6"), "m": Decimal("1e-3"),
"k": 10**3, "M": 10**6, "G": 10**9, "T": 10**12, "P": 10**15, "E": 10**18}
GIB = Decimal(2**30)
def quantity(q):
"""A Kubernetes quantity ("250m", "1.5", "512Mi", "1G", "1e3") in base units."""
q = str(q).strip()
for suffix in sorted(UNITS, key=len, reverse=True):
if q.endswith(suffix) and q[:-len(suffix)]:
return Decimal(q[:-len(suffix)]) * Decimal(UNITS[suffix])
return Decimal(q)
def load(path, cmd):
if path:
with open(path) as f:
return json.load(f)
return json.loads(subprocess.run(cmd, check=True, capture_output=True, text=True).stdout)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--pods")
ap.add_argument("--metrics")
args = ap.parse_args()
pods = load(args.pods, ["kubectl", "get", "pods", "--all-namespaces", "-o", "json"])["items"]
try:
metrics = load(args.metrics, ["kubectl", "get", "--raw", "/apis/metrics.k8s.io/v1beta1/pods"]) \
if (args.metrics or not args.pods) else {"items": []}
except Exception as e:
print(f"# metrics unavailable ({e}); use columns show n/a", file=sys.stderr)
metrics = {"items": []}
use = {}
for m in metrics.get("items", []):
key = (m["metadata"]["namespace"], m["metadata"]["name"])
cs = m.get("containers", [])
use[key] = (sum(quantity(c["usage"].get("cpu", "0")) for c in cs),
sum(quantity(c["usage"].get("memory", "0")) for c in cs))
totals = {"app": [0, Decimal(0), Decimal(0)], "pending": [0, Decimal(0), Decimal(0)],
"system": [0, Decimal(0), Decimal(0)]}
no_metrics = 0
print("NAMESPACE\tPOD\tPHASE\tCPU_REQ\tMEM_REQ_GIB\tCPU_USED\tMEM_USED_GIB\tFLAGS")
for p in pods:
meta, spec = p["metadata"], p.get("spec", {})
phase = p.get("status", {}).get("phase", "Unknown")
if phase in ("Succeeded", "Failed"):
continue # finished pods hold no capacity
flags = []
running = list(spec.get("containers", []))
for c in spec.get("initContainers", []):
if c.get("restartPolicy") == "Always":
running.append(c); flags.append("sidecar")
elif "init" not in flags:
flags.append("init")
cpu = mem = Decimal(0)
for c in running:
req = c.get("resources", {}).get("requests", {})
if "cpu" in req: cpu += quantity(req["cpu"])
elif "no-cpu-request" not in flags: flags.append("no-cpu-request")
if "memory" in req: mem += quantity(req["memory"])
elif "no-memory-request" not in flags: flags.append("no-memory-request")
if spec.get("overhead"): flags.append("overhead")
if spec.get("resources"): flags.append("pod-level-resources")
owners = [o.get("kind") for o in meta.get("ownerReferences", [])]
system = "DaemonSet" in owners or "kubernetes.io/config.mirror" in meta.get("annotations", {})
if system: flags.append("daemonset-or-static")
key = (meta["namespace"], meta["name"])
if key in use:
cpu_used, mem_used = f"{use[key][0]:.3f}", f"{use[key][1] / GIB:.3f}"
else:
cpu_used = mem_used = "n/a"
if phase == "Running": no_metrics += 1
bucket = "system" if system else ("pending" if phase == "Pending" else "app")
totals[bucket][0] += 1; totals[bucket][1] += cpu; totals[bucket][2] += mem
print(f"{key[0]}\t{key[1]}\t{phase}\t{cpu:.3f}\t{mem / GIB:.3f}\t{cpu_used}\t{mem_used}\t{','.join(flags) or '-'}")
print()
for label, k in (("Application pods, enter these on the page", "app"),
("Pending pods, not yet on a node", "pending"),
("DaemonSet and static pods, left out of the page's inputs", "system")):
n, cpu, mem = totals[k]
print(f"# {label}: {n} pods, {cpu:.3f} CPU, {mem / GIB:.3f} GiB requested")
print(f"# Running pods with no metrics (use unknown, not zero): {no_metrics}")
print("# Live use is one snapshot: not evidence that a smaller request is safe.")
if __name__ == "__main__":
main()
What this model assumes
Show
- Requests set the node count; nodes set the bill. Requests influence how many nodes you need. You pay for provisioned nodes. Lower requests reduce your invoice only if nodes can be removed or future capacity purchases avoided. Observed utilization never changes the estimate, only the potential savings.
- Allocatable is less than capacity. Each node loses 12.0% to the kubelet and OS and a DaemonSet allowance of 0.25 vCPU and 0.5 GiB before application pods are placed. These are planning figures, not measurements: real reservations are tiered by node size. So the request inputs are application pod requests only; counting DaemonSet requests there too would count them twice.
- Potential savings use your peaks, per resource. Rightsized requests are current requests times peak use, CPU and memory separately, then the same headroom as the baseline is applied once. With memory use not set, memory requests are kept. Savings count whole nodes only, and peak use above the request shows as extra capacity needed, not a saving.
- Each platform prices its own machine. With the node shape above, EKS uses the EC2 list price in us-east-1, GKE the Compute Engine N2 list price in us-central1 and AKS the Azure Dsv5, Esv5 or Fsv2 list price in East US. A shape no listed machine has needs your price.
- Steady monthly capacity. Every node runs 730 hours a month. Autoscaling that removes nodes for part of the month is not modeled. Spot is a fleet-average share of node-hours at an assumed discount, applied to compute only.
- Load balancers are charged at the base fee only. Capacity units, rules and data processed depend on traffic, which this model does not ask for, and load balancer types are priced differently, so the real line is higher.
- Egress has one allowance for the whole estimate. AWS’s 100 GB of free internet egress is shared across services and Regions, so the default assumes none of it is left. Money is rounded half-up to the cent for display only.
Rates used and their sourcesShow
Prices verified 13 Oct 2026. List prices in USD for us-east-1, us-central1 or East US, by platform, plus the rates you enter.
Want this done on your real bill?
Cloud Cost Audit: We go through your real AWS or Google Cloud bill and hand you a ranked list of what to cut first. From $1,500, fixed. Nothing paid upfront.
Questions
How much does a Kubernetes cluster cost?
The control plane is the small, predictable part: $0.10 per cluster-hour on EKS, GKE and AKS Standard, or about $73 a month; nothing on the AKS Free tier; $0.60 on EKS and GKE extended support and AKS Premium. Everything else is nodes, load balancers, node disks and network transfer. On a small cluster the non-node charges can exceed the compute, which is why a nodes-times-price estimate is usually well under the real bill.
Is EKS, GKE or AKS cheaper?
The standard control-plane fee is $0.10 per cluster-hour on all three, so the difference is rarely there. GKE grants a $74.40 monthly credit that covers one zonal or Autopilot cluster, not a regional one, and AKS has a free tier with no SLA. Beyond that the cost is node prices, which this page reads from each cloud's own list for the same node shape, and cross-zone traffic: AWS charges it both ways, Google once, and Azure lists no charge.
Why do I need more nodes than my pod requests suggest?
Because allocatable capacity is less than node capacity. The kubelet, the OS and daemonsets reserve a share of every node before an application pod is scheduled, typically 10% to 15%, proportionally more on small nodes. Add scheduling headroom for rolling updates and bursts, and a naive division understates node count by a fifth or more.
What is the biggest source of Kubernetes waste?
Often requests set well above what pods use at their peak. The scheduler places pods by their requests, so requests decide how many nodes run, and you pay for the nodes whatever the pods use. Headroom for rollouts and the reservation for the kubelet and DaemonSets are not waste. Lowering requests toward measured peak use, workload by workload, is what can let a cluster run fewer nodes; check each change against restarts, throttling and latency.
What is EKS extended support and why did my bill jump?
When a Kubernetes version passes the end of its standard support window, EKS moves the cluster to extended support at $0.60 per cluster-hour, six times the standard $0.10. For a three-cluster estate that is roughly $1,314 a month instead of $219. It is the most common unexplained jump on an EKS bill and it is avoided entirely by upgrading on schedule.
Related tools
NAT Gateway & Data Transfer Calculator
Price the NAT gateway line that surprises everyone, and find a cheaper setup.
OpenFargate vs EC2 Calculator
Fargate costs more per vCPU, but often less for a whole cluster.
OpenCloud Run Cost Calculator
Two billing models, and how busy your instances are picks the winner.
OpenMore tools for: Kubernetes and containers, Network and data transfer, Compute and utilization
Requests drift. So does the gap.
Finitizer tracks requested versus used capacity per namespace and workload over time, attributes cluster cost back to the teams causing it, and flags the version cliffs and idle node groups before they show up on an invoice.
See how Finitizer works