Cost Optimization in Kubernetes: Bin Packing and Spot Nodes
Why Kubernetes bills grow faster than traffic, how to pack workloads tightly onto fewer machines, how to run safely on discounted spot capacity, and how to keep the savings, explained from zero.
Picture a moving day. You have a hundred boxes of different sizes and a fleet of trucks you rent by the hour. If you toss boxes into trucks at random, most trucks leave half empty, and you pay for all that empty air. If you pack carefully, you need fewer trucks, and your bill drops. Now imagine that some trucks are offered at a big discount, with one catch: the rental company may take the truck back with only two minutes of warning. Would you use them? Only if your plan survives a truck disappearing in the middle of the road.
Running applications on Kubernetes is exactly this moving day. Your applications are the boxes. Your servers, called nodes, are the trucks. Bin packing is the art of fitting boxes tightly into as few trucks as possible. Spot nodes are the discounted trucks that can be taken back at short notice. Together, these two ideas are the biggest levers for lowering a Kubernetes bill. This post explains both from the very beginning, assuming you know nothing yet. By the end, you will understand where the money goes, why clusters are usually far emptier than anyone expects, how the scheduler and autoscalers pack work, how to use spot capacity without waking up at night, and how to talk about all of it clearly in a design review.
1The Problem: A Bill That Grows Faster Than Traffic
Almost every team that adopts Kubernetes goes through the same surprise. The platform works well, and then the cloud invoice arrives and nobody can explain it. Let us start by understanding why.
What you actually pay for
A cloud provider does not charge you for the applications you run. It charges you for the machines you reserve, for every hour they exist, whether or not they are busy. Kubernetes sits on top of those machines. It decides which application runs on which machine, but it does not change the fact that the machines are rented by the hour. So the real question of cost optimization is simple: how many machines do we rent, how big are they, how much do each cost, and how much useful work happens on them?
Think of an office building. The landlord charges rent for the floor space, not for the number of people who sit at the desks. If you rent ten floors but only four are in use, you still pay for ten. Cost optimization is the work of renting only the floors you need, filling them well, and negotiating a better rent.
Why the bill drifts upward
Generous guesses
Teams ask for more CPU and memory than they need, because asking for too little causes outages and asking for too much causes nothing visible.
Forgotten workloads
Test environments, old experiments, and unused services keep running because nobody is sure they can be deleted.
Always-on nodes
Machines stay up all night and over weekends, even when almost no one uses the applications.
Poor packing
Applications are spread across many machines in a way that leaves small unusable gaps everywhere.
Full-price purchasing
Every machine is bought at the standard on-demand rate, even for work that could tolerate interruptions.
No one owns the number
The invoice belongs to the whole company, so no single team feels the cost of its own choices.
The two levers in this post
Cost work has many parts, and later chapters cover several. But two levers stand out. The first is density: getting more useful work out of each machine through bin packing. The second is price: paying less per machine through spot capacity and commitments. Density reduces the number of machines. Price reduces the cost of each one. They multiply: if you need half as many machines, and each costs a third less, the bill falls to a third of what it was.
This post gives ranges and rules of thumb rather than exact prices. Cloud prices, discount sizes, and product names change over time and differ between providers and regions. Use the ideas here, and check current prices and documentation for your own cloud.
2Where the Money Goes
Before cutting costs, you must know what you are cutting. A Kubernetes bill has several parts, and they do not all shrink with the same tools.
The main cost buckets
| Bucket | What it is | Typical share | Main lever |
|---|---|---|---|
| Compute | The machines that run your pods | Usually the largest part | Right-sizing, bin packing, spot, commitments |
| Storage | Disks attached to pods, snapshots, and backups | Often surprising and slowly growing | Delete unused volumes, pick the right disk type, set retention |
| Network | Traffic between zones, out to the internet, and through load balancers and gateways | Can be large for chatty services | Keep chatty services in one zone, reduce cross-zone traffic |
| Control plane and add-ons | The managed cluster fee, logging, monitoring, and security tools | Small per cluster, large across many clusters | Fewer clusters, careful log retention |
| Idle and waste | Reserved but unused capacity | Often a third or more of compute | Everything in this post |
Compute is the big one
In most clusters, compute dominates. Compute cost depends on three numbers: how many nodes, how large each is, and the hourly price of each. The first number is driven by how well work is packed. The second is a design choice. The third depends on how you buy the machines, which is the subject of the spot and commitment chapters.
The hidden costs that surprise people
Costs people usually see
- Node hours on the invoice.
- Load balancer fees.
- Managed database fees.
Costs people usually miss
- Cross-zone data transfer between chatty services.
- Orphaned disks left behind by deleted workloads.
- Verbose logs stored for years.
- Idle development clusters running all weekend.
A household budget has rent, groceries, and utilities. Rent is the biggest bill, so you look there first. But a few small subscriptions that you forgot to cancel can add up to a surprising total. Cost work in Kubernetes has the same shape: one big bill, plus a long tail of small leaks.
Production example: finding the first savings
Teams that begin cost reviews often report that the first and easiest wins are not clever. They are shutting down forgotten environments, deleting unused disks, and noticing that development clusters ran at full size over weekends. Only after clearing this surface waste do the more technical levers, such as bin packing and spot, become the main story.
3Requests, Limits, and the Scheduler
To understand bin packing, you must understand how Kubernetes decides where to put an application. Three ideas are needed: requests, limits, and the scheduler.
Pods and nodes
A pod is the smallest unit Kubernetes runs. It holds one or more containers, which are the running copies of your applications. A node is a machine, virtual or physical, that runs pods. A cluster is a group of nodes managed together. The scheduler is the part of Kubernetes that looks at each new pod and chooses which node it should run on.
Requests: what you reserve
Every container can declare a request for CPU and memory. A request is a promise from the cluster: “this container is guaranteed at least this much.” The scheduler uses requests, and only requests, to decide whether a pod fits on a node. If a node has four CPU cores and pods already on it have requested three, then a pod requesting two will not fit, even if the three existing pods are actually using only a tenth of a core.
Limits: the ceiling
A limit is the maximum a container may use. If a container tries to use more CPU than its limit, it is slowed down. If it tries to use more memory than its limit, it is killed and restarted. Limits protect neighbors from a greedy container. They do not affect where the pod is placed.
Think of booking seats on a train. The request is the number of seats you reserve. The railway holds those seats for you whether or not you sit in all of them, and it will not sell them to someone else. The limit is the most seats you are allowed to spread into if the train is empty. The scheduler is the ticket office that checks only reservations when deciding whether a new traveler fits.
Why the scheduler looks at requests, not usage
This choice is deliberate. Real usage changes second by second, so if the scheduler packed pods by current usage, a quiet moment could lead to a crowded node that then collapses when everyone gets busy. Requests are stable promises, so they give predictable placement. The price of this safety is that requests, not real usage, decide how many nodes you need. If requests are inflated, you pay for capacity that is reserved but empty.
flowchart LR
P["New pod with CPU and memory requests"] --> S["Scheduler"]
S --> F{"Filter: which nodes have enough unreserved room?"}
F -->|"none"| PEND["Pod stays pending, autoscaler may add a node"]
F -->|"some"| SC["Score the remaining nodes"]
SC --> BEST["Choose the best-scoring node"]
BEST --> RUN["Pod runs on that node"]
Allocatable capacity is smaller than the machine
A node’s advertised size is not all available for your pods. The operating system, the node agent called the kubelet, the container runtime, and system pods reserve a share. What remains is called allocatable capacity. On small nodes, the reserved share is a larger fraction, which is one reason very small nodes waste more. Chapter 12 returns to this point.
You pay for nodes, nodes are filled according to requests, so the quality of your requests decides the size of your bill.
4The Slack Gap: Requested Versus Used
The most common source of waste is a simple gap between what applications ask for and what they use. We call this gap slack.
How the gap appears
When a team deploys a new service, nobody knows how much CPU and memory it will need. Asking for too little risks slow responses or crashes in front of customers. Asking for too much risks nothing that anyone sees. So teams pick generous numbers, often copied from another service, and then never revisit them. Months later, the service may be using a fifth of what it reserves.
flowchart TD
NODE["Node: 8 CPU cores allocatable"] --> R["Requested by pods: 6 cores"]
NODE --> U["Unreserved: 2 cores, can still schedule"]
R --> USED["Actually used: 1.5 cores"]
R --> SLACK["Slack: 4.5 cores reserved but idle"]
Imagine a restaurant that lets guests reserve tables. Every group asks for a table for eight, just in case friends show up. Most groups arrive as three people. The restaurant turns away walk-in customers because every table is reserved, while half the chairs are empty. Slack is empty chairs at reserved tables.
Typical sizes of the gap
Surveys of real clusters by cost tools and cloud vendors have repeatedly found that average CPU usage is a small fraction of what is requested, often well under half, and memory usage is higher but still well below requests. Exact numbers vary widely from company to company, so measure your own. The key lesson is that the gap is usually large enough that fixing it can shrink the node count noticeably, before any other technique is applied.
Why the gap persists
Fear of outages
An outage from too little memory is visible and painful. A bill from too much is quiet and diffuse.
Peak thinking
Teams size for the busiest minute of the year, and then pay for that peak every minute of every day.
Copy and paste
New services inherit the numbers of old ones, including the old ones’ waste.
Missing feedback
Developers rarely see the cost of their requests, so there is no pressure to tune them.
Reducing requests without measuring can cause throttling and crashes. The goal is to match requests to real needs plus a safety margin, not to squeeze to the bone. The next chapter shows how to do it with evidence.
5Right-Sizing: Fixing the Requests First
Bin packing works on the numbers you give it. If the numbers are inflated, even perfect packing wastes money. So the first job is to make requests honest.
The right-sizing loop
Measure real usage
Collect CPU and memory usage over a long enough window, ideally including the busiest days, such as month-end or sale periods.
Choose a target percentile
Look at a high percentile of usage, such as the ninety-fifth or ninety-ninth, rather than the average, so normal peaks are covered.
Add a safety margin
Add a cushion for growth and surprises, commonly ten to thirty percent depending on the service.
Change gradually
Lower requests in steps and watch latency, error rates, and restarts after each step.
Repeat regularly
Usage changes as products change. Revisit the numbers every few months, or automate the loop.
CPU and memory behave differently
CPU is a compressible resource. If a container wants more than it has, it simply runs slower. Memory is incompressible. If a container needs more memory than its limit, the system kills it. This means you can be bolder when trimming CPU requests, and you must be more careful with memory. Many teams set memory requests equal to memory limits, for predictability, and treat CPU limits with caution, since very tight CPU limits can slow down applications even when the node has spare capacity.
CPU is like water pressure in a shared pipe: if many people use it at once, everyone gets a weaker stream, but nobody is cut off. Memory is like seats in a lifeboat: when they run out, someone must get off. You can share water more freely than seats.
Tools that help
Vertical Pod Autoscaler
Watches real usage and recommends, or applies, new requests. It can run in a recommendation-only mode, which is a safe way to start.
Cost dashboards
Tools such as OpenCost and Kubecost show requested versus used resources per workload, and highlight the biggest gaps.
Metrics systems
Prometheus-style monitoring gives the raw usage history that all right-sizing depends on.
Cloud vendor advisors
Managed platforms offer recommendations based on their own usage data. Confirm what is available for your provider.
Automatic versus manual changes
Recommendation-only
- Safe: humans review each change.
- Builds trust in the numbers.
- Works with any release process.
Fully automatic
- May restart pods to apply new values, depending on version and mode.
- Can clash with other autoscalers if both watch the same metric.
- Needs careful rollout for critical services.
If a vertical autoscaler and a horizontal autoscaler both react to CPU for the same workload, they can fight each other. Common practice is to use the horizontal one for CPU or traffic, and the vertical one in recommendation mode, or to use them on different signals. Check the current documentation before combining them.
Production example: a right-sizing campaign
Platform teams at larger companies commonly run a recurring campaign. A report ranks services by wasted requested resources, owners receive a list of suggested new requests, and each change is applied during normal releases. Teams often describe this as the least glamorous and most reliable saving, because every other technique in this post works better afterwards.
6What Bin Packing Really Means
Now we reach the first main idea. Bin packing is an old problem from computer science, and it describes exactly what a cluster must do all day long.
The classic problem
You have items of different sizes, and bins of a fixed size. You want to fit all the items using as few bins as possible. In Kubernetes, the items are pods, sized by their requests. The bins are nodes, sized by their allocatable capacity. Fewer bins means fewer nodes, which means a smaller bill.
Think of packing a suitcase. If you throw in clothes at random, the lid will not close and you need a second suitcase. If you place the big items first and fill the gaps with socks, everything fits in one. A good packer thinks about shapes and gaps, not just the total volume.
Two dimensions at once
Real packing in Kubernetes is harder than packing a single number, because every pod has at least two sizes: CPU and memory. A node can be full on one and empty on the other. A memory-hungry pod and a CPU-hungry pod can share a node well, because they need different things. Two memory-hungry pods may fill the memory while leaving CPU unused. This mismatch is called stranded capacity, and we meet it again in the next chapters.
No perfect answer, only good ones
Finding the absolute best packing is a famously hard problem. Real systems use fast, simple rules that give good results most of the time. A common rule is to place larger items first and put each into the fullest bin that still has room. The Kubernetes scheduler and the node autoscalers use rules of this kind, plus extra constraints such as zones, affinities, and spreading requirements.
flowchart LR
subgraph A["Poor packing: 4 nodes"]
A1["Node 1: 40 percent full"]
A2["Node 2: 35 percent full"]
A3["Node 3: 30 percent full"]
A4["Node 4: 45 percent full"]
end
subgraph B["Good packing: 2 nodes"]
B1["Node 1: 90 percent full"]
B2["Node 2: 60 percent full"]
end
A -->|"consolidate"| B
Why tight packing is not always the goal
Packing nodes to one hundred percent leaves no room for traffic spikes, node failures, or rolling updates. A cluster with no spare room will leave new pods pending whenever anything unusual happens. So the aim is tight but not brittle: high utilization with deliberate headroom. Many teams aim for requested utilization well above half and below the top, and tune from there according to how quickly new nodes can be added and how risky an outage is.
Benefits of tighter packing
- Fewer nodes and a smaller bill.
- Less idle power and lower carbon footprint.
- Fewer machines to patch and monitor.
Risks of packing too tightly
- Little room for spikes and rolling updates.
- A single node failure displaces more pods.
- Noisy neighbors can hurt each other more.
7How the Scheduler Chooses: Spread Versus Pack
The scheduler is the daily packer of the cluster. Its default behavior is tuned for safety, not for thrift, and understanding that default is the key to packing better.
Filter, then score
As Figure 1 showed, the scheduler works in two steps. First it filters: it removes nodes that cannot host the pod, because of insufficient requested room, taints, required labels, or other rules. Then it scores the remaining nodes with several plug-in rules and picks the highest total. Each rule looks at a different concern, such as whether the image is already downloaded on the node, whether the pod’s affinity wishes are met, or how balanced the node’s resources are.
The default favors spreading
By default, the scheduler tends to prefer nodes with more free room. This spreads pods across many nodes. Spreading is good for resilience, because no single node holds everything, and good for performance, because each pod has less competition. But it is bad for cost, because it keeps many nodes partly filled instead of filling a few completely, which blocks the autoscaler from removing the emptier nodes.
A hotel receptionist can assign guests in two ways. One way is to give each new guest a room on an empty floor, so every floor has a few guests and the lights stay on everywhere. The other is to fill one floor completely before opening the next, so that unused floors can be shut down. The first is comfortable. The second is cheap.
Choosing a packing strategy
| Strategy | What the scheduler prefers | Effect on cost | Effect on resilience |
|---|---|---|---|
| Least allocated (spreading) | Nodes with the most free requested room | Higher: many half-empty nodes | Stronger: pods spread widely |
| Most allocated (packing) | Nodes that are already the fullest | Lower: nodes fill up, empty ones can be removed | Weaker: a node loss displaces more pods |
| Requested-to-capacity ratio | Configurable shape between the two | Tunable | Tunable |
Where the setting lives
The scheduler can be configured through scheduler profiles, which let operators choose which scoring rules are active and how strongly each counts. On self-managed clusters you can change the default profile, or define a second scheduler profile and select it by name in specific pods. On many managed services, the default scheduler cannot be freely changed, and the cloud provider may offer a packing-friendly option or a different node provisioning tool instead. Always check what your provider allows, since this varies by service and by version.
Many production workloads carry topology spread constraints or anti-affinity rules to keep replicas in different zones or on different nodes. These rules are valuable and should stay for critical services. Remember they work against tight packing, so apply them where resilience truly matters, and not to every workload by habit.
Packing at placement time versus at removal time
There are two moments to pack well. At placement time, the scheduler chooses a good node for a new pod. At removal time, a consolidation tool moves pods off lightly used nodes and deletes those nodes. The first creates a good layout from the start. The second repairs the layout as pods come and go. Strong cost control uses both. The next chapters look at the second.
Production example: a second scheduler profile for batch work
Some organizations run a packing-oriented scheduler profile for batch and background jobs, which tolerate being crowded, while keeping the default spreading profile for customer-facing services. The two live in the same cluster, and each pod chooses its profile by name. This gives dense, cheap batch nodes without lowering the safety of the front end.
8Fragmentation and Stranded Resources
Even with a good scheduler, nodes develop small unusable gaps. Understanding these gaps explains why clusters seem full while still having plenty of unused capacity.
What fragmentation is
Fragmentation means that free capacity exists in total, but it is spread across nodes in pieces too small for any new pod. Imagine five nodes, each with one free CPU core. In total you have five free cores, yet a pod that needs four cores cannot be placed anywhere. The pod stays pending, and the autoscaler adds a new node, even though five cores of room were sitting idle.
A parking lot has twenty empty spaces, but they are scattered one by one between parked cars. A bus arrives needing three spaces in a row. There are plenty of empty spaces, but the bus cannot park. The lot is both empty and full, depending on what you are trying to fit.
Stranded resources
A related problem is stranding. Suppose a node has plenty of free CPU but no free memory, because the pods on it ask for a lot of memory. The spare CPU cannot be used by pods that also need memory, so it is stranded. Stranding happens when the mix of pod shapes does not match the shape of the nodes.
flowchart TD
N1["Node A: CPU free, memory full"] --> S1["Spare CPU is stranded"]
N2["Node B: memory free, CPU full"] --> S2["Spare memory is stranded"]
S1 --> FIX["Fix: mix pod shapes, choose node shapes that match your workloads"]
S2 --> FIX
Ways to reduce fragmentation
Use fewer pod sizes
A handful of standard sizes pack more cleanly than hundreds of unique ones. Odd request values create odd gaps.
Choose matching node shapes
If your workloads need about four times as much memory as CPU in gigabytes per core, pick node types with a similar ratio.
Separate very different workloads
Put memory-heavy and CPU-heavy workloads into separate node groups sized for each, when the mix is extreme.
Avoid huge pods
A pod that needs most of a node is hard to place and leaves large leftovers. Prefer more, smaller pods where the application allows.
Consolidate regularly
Use tools that move pods and remove emptied nodes, covered in the next chapters.
Compare the total free requested capacity in the cluster with the number of pods stuck pending. If free capacity is high while pods are pending, fragmentation or stranding is the likely cause, and the answer is better shapes, not more nodes.
9Node Autoscaling: Adding and Removing Machines
Packing pods well is only half the story. Someone must also add nodes when pods do not fit and remove nodes when they are no longer needed. That is the job of node autoscalers.
The basic loop
A node autoscaler watches for pods that are pending because no node has room. It then adds a node that can fit them. It also watches for nodes that are empty or lightly used, and removes them when their pods can be safely moved elsewhere. The cluster therefore grows and shrinks with the work, and the bill follows.
sequenceDiagram
participant D as Deployment
participant S as Scheduler
participant A as Node autoscaler
participant C as Cloud API
D->>S: Create new pods
S-->>D: No node has room, pods pending
A->>S: Sees pending pods
A->>C: Request a new node
C-->>A: Node joins the cluster
S->>S: Place pending pods on the new node
Note over A,S: Later, load falls
A->>S: Finds a lightly used node
A->>C: Drain pods and delete the node
Two families of autoscaler
| Question | Group-based autoscaler | Direct provisioning autoscaler |
|---|---|---|
| Well-known example | Cluster Autoscaler | Karpenter, and similar tools offered by cloud vendors |
| How it adds capacity | Increases the size of a pre-defined node group | Chooses and launches a node that fits the pending pods |
| Node variety | Each group has a fixed node type or a small set | Can pick from many instance types per decision |
| Packing behavior | Depends on group design and scheduler | Considers pod requests together to choose nodes, and can consolidate |
| Setup effort | Define and tune many groups | Define rules about allowed shapes and limits |
A group-based autoscaler is like a taxi company with three vehicle types, who sends another car of whichever type is assigned to a route. A direct provisioning autoscaler is like a dispatcher who looks at the passengers waiting, sees that there are two people and a piano, and sends exactly the right vehicle, from the full fleet of options.
Why scale-down is the hard half
Adding a node is easy. Removing one is careful work. Before deleting a node, the autoscaler must check that every pod on it can be moved: that other nodes have room, that no pod has a rule that forbids moving, and that moving it will not break a service. Many clusters keep nodes alive for a long time because of a single stubborn pod. Common blockers are listed below.
Pods with local storage
Pods that use storage tied to the node cannot be moved freely.
No controller
Pods created directly, without a Deployment or similar owner, will not be recreated, so tools avoid removing them.
Strict disruption budgets
A budget that allows zero interruptions prevents any pod from being evicted.
Do-not-evict markers
Annotations that explicitly forbid removal, sometimes added by well-meaning teams and then forgotten.
Tight anti-affinity
Rules that require each replica on its own node limit where pods can go.
Daemon-style pods
Pods that must run on every node do not block removal by themselves, but badly configured ones can.
When nodes refuse to scale down, look at the autoscaler’s events and logs. It usually names the exact pod or rule that blocked removal, which is the fastest path to the fix.
10Consolidation: Repacking a Running Cluster
Pods come and go all day. Over time, a once tidy cluster becomes patchy, with many half-empty nodes. Consolidation is the practice of tidying it again while it keeps running.
Why clusters get messy
Imagine a cluster at the end of a busy day. Traffic peaked at noon, so the autoscaler added many nodes. By evening, most pods were removed, but each node still holds a few survivors. Nothing is fully empty, so no node can be deleted, yet the total load would fit on far fewer machines. This is the natural drift toward waste.
After a party, guests leave at different times. The living room, kitchen, and balcony each still have two or three people. The host could ask everyone to gather in one room and turn off the lights in the others. Consolidation is that polite request, applied to pods.
How consolidation works
Find candidates
The tool looks for nodes that are empty or underused, whose pods could fit elsewhere.
Simulate the move
It checks that all pods can be rescheduled on the remaining nodes, respecting every rule.
Respect disruption budgets
It evicts pods gently, never taking too many replicas of one service at a time.
Drain and delete
Pods are evicted and recreated elsewhere, and the emptied node is removed.
Sometimes replace
A smarter tool may replace two lightly used nodes with one cheaper node of a different size, when that lowers cost.
Tools that consolidate
Cluster Autoscaler scale-down
- Removes nodes below a utilization threshold after a delay.
- Well understood and widely used.
- Works inside the fixed node groups you defined.
Karpenter consolidation
- Can delete empty nodes, merge underused nodes, and replace nodes with cheaper types.
- Governed by a policy and optional time windows.
- Powerful, so it needs good disruption budgets.
The descheduler
A separate tool called the descheduler evicts pods that are badly placed so that the scheduler can place them again. It can, for instance, evict pods from lightly loaded nodes so that those nodes empty out, or fix placements that no longer follow the rules. It does not add or remove nodes. It only moves pods, and then a node autoscaler removes the emptied nodes. Used together, they form a repacking pipeline.
flowchart LR
A["Patchy cluster: many half-empty nodes"] --> B["Descheduler or consolidation evicts pods from light nodes"]
B --> C["Scheduler repacks pods onto fuller nodes"]
C --> D["Emptied nodes are deleted by the node autoscaler"]
D --> E["Tighter cluster and lower bill"]
Every move restarts pods. Applications that start slowly, hold long connections, or keep state in memory feel this the most. Set disruption budgets, graceful shutdown periods, and, if available, time windows so that consolidation does not run during your busiest hours.
Production example: consolidation with guard rails
Teams using direct provisioning autoscalers commonly report large drops in node count after enabling consolidation, but they also describe early surprises, such as a burst of restarts at an unlucky hour. The usual remedy is to protect critical services with disruption budgets, mark a few workloads as not to be disrupted, and limit how many nodes may be consolidated at once. After that, consolidation becomes a quiet daily saving.
11Choosing Node Sizes: Big, Small, and Mixed
The size of your nodes changes how well pods pack. This is one of the quietest, most powerful design choices in a cluster.
The trade-off in plain words
Large nodes pack well, because big gaps are rare and fixed overhead is shared by more pods. But a large node holds many pods, so losing one hurts more. Small nodes limit the damage of a failure and add capacity in small steps, but they waste more on overhead and leave awkward gaps. Neither extreme is right for most clusters.
Think of choosing between a few large moving trucks or many small vans. The big truck carries furniture easily and costs less per box, but if it breaks down, a lot of your move is stuck. Small vans can be added one at a time and a breakdown matters less, but each van needs a driver and cannot carry a wardrobe.
| Factor | Larger nodes | Smaller nodes |
|---|---|---|
| Overhead share | Lower: system reservations are spread across more pods | Higher: reservations eat a bigger fraction |
| Fragmentation | Lower for typical pod sizes | Higher: small leftovers cannot hold a pod |
| Blast radius of a node failure | Larger: many pods displaced together | Smaller |
| Scaling granularity | Coarse: capacity added in big steps | Fine |
| Pod count limits | May hit per-node pod limits or network address limits before CPU is used | Rarely hit them |
| Fit for large pods | Good | May not fit at all |
Hidden limits to check
Some cloud networking models limit how many pods a node can hold, based on how many network addresses the machine type supports. A large node may therefore run out of pod slots before it runs out of CPU, wasting capacity again. Check the pod-per-node limit for the instance types you plan to use, and the options your provider gives to raise it. These limits differ by provider and by version.
Mixed fleets win
The best answer is often not one size but a range. Direct provisioning autoscalers can choose from many instance types according to what is pending, which fits big pods onto big nodes and small leftovers onto small ones. Even with group-based autoscalers, a small number of groups with different sizes can help. Mixed fleets also help for a second reason that later chapters explain: they make spot capacity far more reliable.
flowchart TD
Q1{"Are your pods large, with a few needing many cores?"} -->|"yes"| BIG["Include large node types"]
Q1 -->|"no"| Q2{"Do you hit pod-per-node limits before CPU?"}
Q2 -->|"yes"| MID["Prefer medium nodes or raise the limit"]
Q2 -->|"no"| Q3{"Is a single node failure very costly?"}
Q3 -->|"yes"| SMALL["Prefer more, smaller nodes"]
Q3 -->|"no"| MIX["Use a mixed fleet and let the autoscaler choose"]
Matching the CPU-to-memory ratio
Cloud machine families come in shapes: some have much memory per core, some have little. If your workloads use about four gigabytes per core, a family with that ratio avoids stranding. If a few workloads are unusual, such as large caches or compute-heavy jobs, put them on separate node pools labeled for them. Check your actual ratio from your own usage data before deciding.
Newer processor families
Many clouds offer processors based on different designs, often priced lower for similar performance on suitable workloads. If your applications run on more than one processor architecture, a mixed fleet can lower costs further. This requires images built for each architecture and testing, so treat it as a later optimization once the basics are done.
Production example: widening the instance list
A common lesson from teams adopting direct provisioning is that allowing many instance families and sizes, rather than naming one, gives the autoscaler room to pick the cheapest node that fits, and to find spot capacity more easily. Restricting the list to a single type is the most frequent reason savings fall short of what vendors promise.
12Spot Nodes: Cheap Machines With a Catch
We now turn to the second lever: price. Spot capacity is the largest single discount most teams can access, and also the one that demands the most care.
What spot capacity is
Cloud providers keep large amounts of spare machines so they can meet demand. When some of those machines are idle, the provider sells them at a steep discount. This is called spot capacity on some clouds, and goes by similar names such as preemptible or low-priority machines on others. The discount is commonly large, often well over half off the standard price, though the exact figure changes by time, region, and machine type. The catch is that the provider can take the machine back when it needs the capacity, usually with a short warning.
It is like a hotel selling empty rooms at a deep discount on the day of stay. You get the room cheaply, but if a full-price guest arrives and no other rooms are free, the hotel may ask you to leave after a short notice. If your plans can handle moving to another room, you save a lot. If you must stay put, the discount is not for you.
The three ways to buy compute
| Option | Price | Commitment | Can be taken back? | Best for |
|---|---|---|---|---|
| On-demand | Standard | None | No, apart from failures | Critical and unpredictable workloads |
| Committed use (reserved or savings plans) | Discounted, commonly by a quarter to a half or more depending on term | One to three years of spend or capacity | No | Steady baseline load |
| Spot | Deepest discount | None | Yes, with short notice | Fault-tolerant, flexible workloads |
Why spot fits Kubernetes so well
Kubernetes was built on the assumption that machines fail. It restarts pods elsewhere, replaces unhealthy nodes, and keeps the desired number of copies running. A spot interruption is like a machine failure that comes with advance notice, which is actually easier to handle. So if your applications already tolerate a node disappearing, spot is a natural fit. The remaining work is making that tolerance real instead of assumed.
Each provider has its own notice period, interruption signals, and pricing model, and these have changed over the years. Before building anything, read your provider’s current documentation on its spot or preemptible offering.
13Surviving an Interruption
The central skill of running on spot is handling the moment a node is taken away. Let us walk through what happens, and what makes the difference between a calm event and an outage.
The life of an interruption
sequenceDiagram
participant Cloud as Cloud provider
participant H as Termination handler
participant K as Kubernetes
participant P as Pods on the node
participant A as Autoscaler
Cloud->>H: Interruption notice with short countdown
H->>K: Mark node unschedulable (cordon)
H->>K: Drain node, evict pods gently
K->>P: Send termination signal, start grace period
P-->>K: Finish requests and shut down cleanly
K->>K: Recreate pods on other nodes
A->>Cloud: Request replacement capacity if needed
Cloud->>Cloud: Take the machine back
The pieces that make it safe
Interruption handling
Something must notice the warning and start draining the node before it vanishes. Managed offerings and autoscalers often include this, and standalone handlers exist when they do not.
Several replicas
A service with only one copy goes down when that copy’s node is reclaimed. Run at least two, spread across nodes.
Pod disruption budgets
A budget tells Kubernetes how many replicas may be unavailable at once, so that a drain does not remove too many together.
Graceful shutdown
The application must finish in-flight work and close connections within the grace period after it receives the termination signal.
Fast startup
Pods that start quickly recover quickly. Slow startup stretches every interruption into a longer dip.
Readiness checks
Accurate readiness checks ensure traffic only reaches pods that can serve it, including freshly started replacements.
The two-minute reality
Warnings are short. Some clouds give around two minutes, others give less, and some give very little for certain machine types. That means a drain must be quick, and your pod’s shutdown grace period should fit inside the warning window. A pod that takes five minutes to shut down will simply be killed halfway. Test your actual shutdown time before trusting it.
It is a fire drill. Everyone must leave the building in the time the alarm allows. A drill is only useful if people know the exits, close their work quickly, and meet at a known place. A plan that needs ten minutes will not help in a building with a two-minute alarm.
Pod disruption budgets in practice
A pod disruption budget states the smallest number, or share, of replicas that must stay available during voluntary disruptions such as drains. It protects against well-behaved removals, which include consolidation and graceful spot handling. It cannot stop a hard failure or an abrupt reclaim. Set budgets that match reality: a budget that allows no disruption at all blocks consolidation and draining, while one that is too loose may allow too many replicas to leave together.
Signs your service is spot-ready
- Three or more replicas spread across nodes and zones.
- Pod disruption budget in place.
- Shutdown completes well within the warning window.
- No local state that cannot be rebuilt.
Signs it is not ready
- Single replica.
- Long, stateful startup or shutdown.
- Data stored only on the node’s local disk.
- Long-running jobs with no checkpoints.
Production example: interruption handling as a standard add-on
Teams running spot at scale usually deploy interruption handling as a standard part of their cluster build, and rehearse it by deliberately terminating spot nodes in a test environment. They treat the first successful rehearsal as the moment spot becomes approved for a service, and not before.
14Spreading Spot Risk: Many Pools, Many Zones
Spot interruptions are not random in one way: machines of the same type in the same zone are often reclaimed together. The way to stay safe is to avoid putting all your eggs in one pool.
What a capacity pool is
A capacity pool is the set of spare machines of one type in one zone. Prices and interruption chances are set per pool. When demand rises in a pool, spot machines there may be reclaimed together, and new ones may be unavailable for a while. If your whole fleet is one instance type in one zone, a single pool event can remove everything at once.
Imagine buying vegetables from only one farm, in one village, on one road. If a flood closes that road, you have nothing. If you buy from many farms in many villages, a flood hurts only a part of your supply. Diversifying pools is the same idea applied to machines.
How to diversify
Use many instance types
Allow a wide list of sizes and families that suit your workloads, such as several that have similar CPU and memory.
Use several zones
Spread nodes across at least two or three zones in the region, so a zone-wide shortage affects only part of the fleet.
Choose a sensible allocation policy
Many platforms offer strategies that pick pools with lower interruption chance, rather than only the lowest price. The cheapest pool is often the one most likely to be reclaimed.
Keep an on-demand fallback
Allow the autoscaler to fall back to on-demand machines when spot is unavailable, so pods do not stay pending.
Spread replicas across pools
Use topology spread rules so replicas of one service sit on different nodes and zones, not all in a single pool.
flowchart TD
W["Workload needs capacity"] --> SP{"Is spot capacity available in any allowed pool?"}
SP -->|"yes"| PICK["Pick a pool with low interruption chance and good price"]
SP -->|"no"| OD["Fall back to on-demand capacity"]
PICK --> RUN["Run pods on spot nodes"]
OD --> RUN2["Run pods on on-demand nodes"]
RUN -.->|"interruption"| REPL["Replace in another pool"]
The cost of a narrow list
When teams choose only one or two instance types for spot, they see frequent interruptions and long periods with no capacity, and conclude that spot is unreliable. Often the real cause is the narrow list. Widening it usually improves both price and stability. It does require your workloads to run acceptably on any of the allowed types, so avoid requiring a very specific machine unless truly necessary.
Even a well-diversified fleet can hit times when spot capacity is scarce, such as during large demand events in a region. Always plan for the case where a meaningful share of spot capacity cannot be obtained, and make sure the fallback to on-demand is automatic and has enough budget headroom.
Production example: a mixed fleet for a streaming-style service
Large consumer platforms that stream video or process events commonly run most of their stateless processing on spot across many instance types and zones, with a small on-demand core. The core keeps the service alive during rare shortages, while the spot majority provides the savings.
15Which Workloads Belong on Spot
Not every workload should run on spot. The skill is sorting your applications into the ones that welcome interruption and the ones that cannot take it.
A simple sorting guide
| Workload type | Spot suitability | Why |
|---|---|---|
| Stateless web and API services with several replicas | Good, with a mixed floor of on-demand | Replicas replace each other quickly |
| Queue workers and event processors | Very good | Work returns to the queue and another worker picks it up |
| Batch and data processing jobs | Very good, with retries or checkpoints | Cheap to restart, big savings on long runs |
| Continuous integration builds | Very good | Failed builds can simply rerun |
| Machine learning training with checkpoints | Good | Resume from the last saved point |
| Development and test environments | Very good | Interruption is an inconvenience, not an outage |
| Databases and stateful stores | Usually poor | Losing a node triggers recovery, and some designs risk data loss or long outages |
| Single-replica critical services | No | Any interruption is downtime |
| Cluster control services and ingress | Usually no | The cluster itself depends on them |
Think of a courier company using a mix of company trucks and hired freelancers. Routine parcels go with freelancers, because a missed pickup just means another driver takes it. The vault shipment with valuables always goes in a company truck. Sorting by what a failure costs is the whole decision.
The mixed-floor pattern
For customer-facing services, a popular pattern keeps a floor of on-demand capacity that can serve the minimum safe traffic by itself, and runs the rest of the replicas on spot. If every spot node vanishes, the service continues at a reduced but safe level while replacements arrive. This pattern gives most of the discount with a modest risk.
flowchart LR
subgraph FLOOR["On-demand floor: always on"]
F1["Replica 1"]
F2["Replica 2"]
end
subgraph SPOTS["Spot layer: cheap, can disappear"]
S1["Replica 3"]
S2["Replica 4"]
S3["Replica 5"]
S4["Replica 6"]
end
LB["Traffic"] --> FLOOR
LB --> SPOTS
How to steer workloads
Kubernetes lets you steer pods using node labels, taints and tolerations, and affinity rules. Spot nodes are usually labeled and sometimes tainted so that only workloads that explicitly accept them land there. A pod can say it prefers spot but accepts on-demand, or that it must avoid spot entirely. Preferences, rather than hard requirements, keep pods schedulable when spot is scarce.
Prefer rather than require
- Pods still run if spot is unavailable.
- The autoscaler has more freedom to pack.
- Fewer stuck pending pods.
Require spot only for tolerant jobs
- Good for batch jobs that can wait.
- Bad for services where waiting is an outage.
- Needs a clear owner who accepts delays.
Move development environments, continuous integration, and batch jobs to spot first. They give early savings, teach your team how interruptions behave, and carry little customer risk. Move customer-facing services only after you have rehearsed interruptions.
16The Layers of Autoscaling
Cost savings come from many scaling mechanisms working together, each at a different layer. Knowing which layer does what stops them from fighting each other.
Four layers, four questions
| Layer | Question it answers | What it changes | Typical tool |
|---|---|---|---|
| Pod size | How big should each pod be? | CPU and memory requests | Vertical Pod Autoscaler |
| Pod count | How many pods do we need right now? | Number of replicas | Horizontal Pod Autoscaler, event-driven autoscalers such as KEDA |
| Node count | How many machines hold those pods? | Number and type of nodes | Cluster Autoscaler, Karpenter |
| Purchase type | How do we pay for those machines? | Spot, on-demand, committed | Allocation policies, commitments |
Think of running a delivery business. Pod size is how big each delivery van is. Pod count is how many vans are on the road. Node count is how many garages you rent to park them. Purchase type is whether you lease, rent daily, or buy at a discount. A smart manager adjusts all four, in the right order.
flowchart TD
T["Traffic rises"] --> H["Horizontal autoscaler adds pods"]
H --> PEND["New pods may be pending"]
PEND --> N["Node autoscaler adds nodes"]
N --> BUY["Allocation policy chooses spot or on-demand"]
V["Vertical recommendations tune pod sizes over time"] -.-> H
T2["Traffic falls"] --> H2["Horizontal autoscaler removes pods"]
H2 --> C["Consolidation removes empty nodes"]
The horizontal autoscaler
The Horizontal Pod Autoscaler changes the number of replicas according to a measured signal, most often average CPU use, and in newer setups custom metrics such as requests per second. Because it measures usage as a share of the request, an inflated request makes it scale too late, since utilization appears low. Another reason right-sizing comes first.
Event-driven scaling
Event-driven autoscalers, with KEDA being a widely used example, scale workloads according to external signals such as queue length, and can scale to zero when there is no work. This is useful for background workers, which often idle for long stretches. Scaling to zero removes their cost entirely during those stretches, and lets nodes be released.
The pitfalls of stacking
Slow node start
If new nodes take minutes to join, pods wait pending during spikes. Keep some headroom or use faster provisioning.
Flapping
Scaling up and down too eagerly wastes work and causes restarts. Use stabilization windows so a brief dip does not trigger removal.
Competing signals
Two autoscalers reacting to the same metric can fight. Give each layer its own job.
Ceiling without a floor
Maximum limits protect budgets, but minimums protect users. Set both on purpose.
Headroom: paying a little for speed
When a spike arrives, adding a node takes time, commonly from tens of seconds to several minutes. Some teams run a small amount of placeholder pods at low priority that reserve space. When real pods arrive, the placeholders are evicted instantly, the real pods start at once, and the autoscaler quietly adds a node to restore the placeholders. This costs a little but protects user experience during bursts.
If tight cost settings make users wait during every traffic rise, the saving is false economy. Measure both the bill and the user experience, and tune headroom to the point where both are acceptable.
17The Baseline: Commitments and Discounts
Spot is for flexible work. For the steady core of your load, there is a different discount: promise the cloud provider a certain amount of usage in return for a lower price.
How commitments work
Cloud providers offer discounts, commonly called reserved instances, savings plans, or committed use discounts, in exchange for committing to a level of spending or capacity for one to three years. The discount can be substantial, often between a quarter and well over a third or more depending on term and flexibility. The risk is that you pay for the commitment even if you use less than promised.
It is like a gym membership versus paying per visit. If you go every week, the yearly membership is far cheaper. If you stop going, you still pay. The wise move is to commit only to the number of visits you are sure to make.
Layering the three purchase types
flowchart TD
L["Total compute demand over time"] --> B["Steady baseline: covered by commitments"]
L --> M["Predictable variable load: on-demand"]
L --> S["Flexible, interruption-tolerant load: spot"]
B --> C1["Lowest guaranteed price"]
M --> C2["Full price but no commitment"]
S --> C3["Cheapest per hour but can be reclaimed"]
The right order of operations
Right-size and pack first
Reduce waste before buying commitments, otherwise you commit to capacity you would have removed.
Introduce spot for flexible work
This lowers and reshapes the on-demand demand curve.
Measure the stable floor
Look at the lowest level of on-demand use that persists over weeks, not at peaks.
Commit to a share of that floor
Start with a conservative share, such as most of the stable minimum, and add later as confidence grows.
Review regularly
Check coverage and utilization of commitments, and renew or adjust as plans change.
A common and costly mistake is buying large commitments before right-sizing. When requests are later reduced, the committed capacity is no longer needed, and the savings from optimization are partly swallowed by commitment payments that cannot be undone.
Production example: a three-layer strategy
Finance-minded engineering teams often describe their strategy as three layers: committed spend that covers the always-on core, spot for the elastic and fault-tolerant layers, and on-demand as the flexible buffer between them. Reviews every quarter check each layer’s share of total spend and move work between layers when habits change.
18Storage and Network: The Costs Beyond Compute
After compute, the next surprises are usually disks and data transfer. They are less visible, and often easier to reduce than people expect.
Storage costs
Pods that need durable data request persistent volumes, which are disks from the cloud. These disks are billed whether or not anything reads them. Several patterns inflate the bill: disks left behind after a workload was deleted, volumes sized far above real need, expensive high-performance disk types used for data that is rarely touched, and snapshots kept forever.
Orphaned volumes
Check which volumes are not attached to any pod. Many clusters carry a surprising number, and reclaim policies decide whether they are removed automatically.
Oversized claims
Volumes are often created with a round, generous size. Many cloud disks can be grown later, so start modest and expand.
Wrong disk class
Use faster, costlier disks only where performance demands them. Logs, archives, and rarely read data fit cheaper classes.
Snapshot retention
Set retention windows. A backup from three years ago is rarely useful and always billed.
Network costs
Cloud providers usually charge for data that crosses zone boundaries, and more for data that leaves to the internet. Inside a cluster that spans several zones, services that talk to each other a lot can generate large cross-zone transfer without anyone noticing. A chatty service pair in different zones, exchanging a steady flow of data, can cost more in transfer than the machines themselves.
Imagine a courier company that charges a small fee whenever a parcel crosses a district boundary. If two offices that exchange hundreds of parcels a day sit in different districts, the fees add up. Moving them into the same district removes the fees without changing any work.
Ways to reduce transfer costs
Measure first
Use network cost reports or flow data to find which services generate the most cross-zone and outbound traffic.
Prefer same-zone routing
Kubernetes offers topology-aware routing options that prefer endpoints in the same zone as the caller. Check which option your version supports and its conditions.
Co-locate chatty pairs
Use affinity preferences to place closely coupled services near each other, balancing against resilience.
Cache and compress
Reduce the volume of data crossing boundaries by caching results close to callers and compressing payloads.
Watch gateways and NAT
Outbound traffic through address translation gateways is often billed per gigabyte. Private endpoints to cloud services can avoid some of it.
Benefit of same-zone preference
- Lower transfer fees.
- Lower latency between services.
Trade-off
- Uneven load if one zone has fewer pods.
- Reduced spread if a zone fails, depending on settings.
Production example: the log bill
A frequent surprise in production is that observability costs rival compute. Verbose debug logs shipped to a paid logging service at high volume can cost more than the nodes that produce them. Teams cut this by lowering log levels, sampling, dropping noisy fields, and setting short retention for low-value logs.
19Time-Based Savings: Turning Things Off
The cheapest machine is one that is not running. Many environments are used only during working hours, yet run all day and night.
The simple arithmetic
A week has one hundred sixty-eight hours. A development environment used from nine to six on weekdays is needed for only about forty-five of them, which is under thirty percent. Running it around the clock means paying for more than seventy percent of the time it sits idle. Switching it off outside working hours cuts its compute cost by well over half.
It is the same reason you switch off the lights when you leave the room. One bulb costs little. A building with every light on all night is another matter. Environments that nobody is using are rooms with the lights on.
Ways to turn things off
Scheduled scale-down
A scheduled job or tool sets the replica count of development workloads to zero in the evening and restores it in the morning. Node autoscaling then removes the empty machines.
Scale to zero on idle
Event-driven tools scale workloads to zero when there is no traffic or work, and start them on demand.
Ephemeral environments
Create a full test environment for each change and delete it when the change is merged, rather than keeping long-lived copies.
Expiry labels
Require a time-to-live label on temporary namespaces, and have a cleaner delete expired ones.
The cold start question
Switching things off has a price: when someone arrives in the morning, the environment must start. If starting takes ten minutes, people complain. Good designs start environments a little before working hours, keep images cached, and keep startup times short. For on-demand scale-to-zero, the first request may be slow, which is acceptable for internal tools but not for customer-facing paths.
Scaling down one service that others quietly depend on can break far-away tests or jobs. Keep a list of dependencies, and announce the schedule clearly so that people on other time zones or night shifts are not surprised.
Production example: nightly and weekend shutdowns
Many engineering organizations report that simply shutting non-production clusters or namespaces outside working hours was among their first, and most popular, savings. It requires almost no technical change in the applications, only agreement on the schedule and a reliable way to bring things back.
20Visibility and Accountability: Making Cost Everyone’s Job
Technical levers only work if the organization notices cost. People optimize what they can see. This chapter covers how to make spend visible and owned.
Allocation: who spent what
Cloud invoices list machines, not teams. In a shared cluster, a single machine may host pods from ten teams. To divide the bill, tools measure each pod’s share of the node, based on its requests and sometimes its usage, and add up the shares per namespace, label, or team. Open-source and commercial tools, such as OpenCost and Kubecost, do this by combining cluster metrics with cloud price data.
Think of an apartment building with one shared electricity bill. Without meters, nobody saves energy, because the cost is split no matter what. Install a meter in each flat, and behavior changes. Cost allocation tools are the meters for a shared cluster.
Showback and chargeback
| Approach | What happens | Effect on behavior |
|---|---|---|
| Showback | Teams see their cost in reports, but are not charged | Gentle awareness, easy to start |
| Chargeback | The cost is moved to each team’s budget | Strong incentive, needs fair and trusted numbers |
Guardrails inside the cluster
Resource quotas
Cap the total CPU, memory, and object counts a namespace may request, so one team cannot take the whole cluster.
Limit ranges
Set default requests and limits for pods that do not state them, and maximums for those that do.
Policy checks
Admission rules can require cost labels, reject missing requests, and block unsafe settings such as oversized requests without approval.
Budget alerts
Send a message when a team’s spend rises faster than its forecast, so surprises are caught in days rather than at month end.
Labels make everything possible
Consistent labels for team, service, environment, and cost center are the foundation of allocation. Without them, a large share of spend stays “unallocated” and nobody feels responsible. Make labels required by policy at deployment, rather than asking people to remember.
The FinOps habit
The discipline of managing cloud cost as a shared engineering and finance activity is often called FinOps. Its core cycle is to inform teams with clear data, to optimize with the techniques in this post, and to operate with regular reviews and ownership. The practice matters as much as any single tool, because savings that are not reviewed tend to slip away.
When developers see an estimate of cost in their pull request or dashboard, they start asking cost questions on their own. A number placed where decisions are made is worth more than a monthly report nobody opens.
Production example: a monthly cost review
Many platform teams run a short monthly review. They look at the top spenders, the largest requested-versus-used gaps, orphaned resources, and the share of compute on spot and under commitments. Owners take away a short list of actions, and the next review begins by checking whether they were done.
21A Worked Example: Cutting a Cluster Bill Step by Step
Let us walk through a story, using plain numbers and no code. The company and figures are invented to teach the order of steps. Real results depend on your workloads, prices, and starting waste.
The starting point
A mid-sized online shop runs its services on one cluster with sixty identical on-demand nodes, running day and night. A review shows that nodes are about thirty percent utilized by real usage, while the scheduler sees them as around seventy percent full by requests. Development and test environments sit in the same cluster. The monthly compute bill is the baseline we call one hundred units.
The five steps
Clean the surface
The team deletes forgotten test namespaces, unattached disks, and idle load balancers. The bill falls a few units, without touching any production service.
Right-size requests
Using a high percentile of real usage plus a margin, the team lowers requests for the largest services over several weeks. Requested utilization drops, so fewer nodes are needed for the same pods. The node count falls by roughly a quarter.
Enable consolidation and a mixed fleet
The team adopts a direct provisioning autoscaler with a wide list of instance types and consolidation turned on, behind disruption budgets. The cluster now follows daily traffic, and nodes shrink at night.
Move flexible work to spot
Queue workers, batch jobs, continuous integration, and test environments move to spot, while customer-facing services keep an on-demand floor and use spot for extra replicas.
Commit to the new floor
After a few months of stable data, the team commits to a share of the remaining always-on on-demand usage, with a conservative amount.
An illustrative result
| Stage | Illustrative compute cost index | What changed |
|---|---|---|
| Start | 100 | Sixty on-demand nodes, always on |
| After cleanup | 95 | Waste removed |
| After right-sizing | 72 | Fewer nodes needed |
| After consolidation and mixed fleet | 58 | Night shrink, better packing |
| After spot for flexible work | 40 | Large discount on a large share |
| After commitments on the floor | 34 | Lower rate for always-on baseline |
The percentages above are invented to show how savings stack. Some organizations find more, some less. The lesson is the order: reduce waste first, then pack, then pay less per machine, then commit.
flowchart LR
S0["Start: index 100"] --> S1["Cleanup: 95"]
S1 --> S2["Right-size: 72"]
S2 --> S3["Consolidate and mix: 58"]
S3 --> S4["Spot for flexible work: 40"]
S4 --> S5["Commit the floor: 34"]
What the team measured along the way
At each step, the team watched user-facing latency, error rates, pod restarts, and pending pods. One step, right-sizing memory, caused a handful of restarts that were fixed by raising two requests. Without these measurements, the team would not have known whether a saving was healthy or a hidden risk.
22An Incident Walkthrough: When Spot Capacity Disappears
The following story is a fictional composite of events that teams commonly describe. It shows how a good design absorbs a spot shortage, and how a weak one does not.
The setting
A ticketing company runs its booking front end on spot nodes, to save money. On a busy afternoon, with a popular event on sale, a large number of spot nodes in one zone receive interruption notices within minutes of each other.
Version one: the weak design
One instance type, one zone
The whole spot fleet was a single machine type in a single zone, chosen because it was cheapest.
No on-demand floor
Every replica of the booking service lived on spot nodes.
The pool dries up
The provider reclaims most nodes at once. Replacement spot capacity of the same type is unavailable, so new pods stay pending.
Outage
With most replicas gone and few new ones starting, customers see errors at the busiest moment of the year.
Version two: the redesigned system
After the incident, the team made five changes. They widened the allowed instance list to many families and sizes, spread nodes over three zones, kept a small on-demand floor able to serve minimum traffic, set pod disruption budgets, and rehearsed interruptions monthly by terminating spot nodes in a staging cluster.
sequenceDiagram
participant Cloud as Cloud provider
participant K as Cluster
participant F as On-demand floor
participant S as Spot layer
participant U as Users
Cloud->>S: Interruption notices in one pool
S->>K: Nodes cordoned and drained
K->>F: Floor keeps serving traffic
K->>Cloud: Autoscaler requests other pools and zones
Cloud-->>K: Capacity found in other pools
K->>S: New spot nodes join, pods rescheduled
U->>F: Requests continue, slightly slower
What the team learned
Lessons that worked
- Diversify pools: many types, many zones.
- Keep a floor that can carry minimum safe traffic.
- Rehearse the failure before it happens.
- Alert on pending pods and on spot fallback rates.
Mistakes to avoid
- Choosing the single cheapest pool.
- Running every replica on spot.
- Assuming replacement capacity will always exist.
- Never testing the interruption path.
The saving from running the last few replicas on spot is small compared with the cost of one serious outage. The wise line is to stop spot at the point where one more replica on spot saves a little and risks a lot.
23The Trade-Offs: Cost, Reliability, and Speed
Every technique in this post trades something for money. Good engineering does not hide these trades. It names them and chooses them on purpose.
The triangle
Think of three goals pulling against each other: low cost, high reliability, and fast response to change. You can improve any two easily, but the third suffers. A cluster packed tightly and fed on spot is cheap, and may be less reliable and slower to absorb spikes. A cluster with generous headroom and on-demand machines is reliable and quick, and expensive. The art is choosing the right mix for each workload.
flowchart TD
C["Low cost"] --- R["High reliability"]
R --- F["Fast response to spikes"]
F --- C
C -.- NOTE1["Pushing cost down: tighter packing, more spot"]
R -.- NOTE2["Pushing reliability up: spread, headroom, on-demand"]
F -.- NOTE3["Pushing speed up: spare capacity, warm nodes"]
Technique by technique
| Technique | What you gain | What you risk | How to soften the risk |
|---|---|---|---|
| Lower requests | Fewer nodes | Throttling, memory kills | Use a high percentile and a margin, change gradually |
| Packing scheduler profile | Fuller nodes, easier scale-down | Larger blast radius per node | Keep spread rules for critical services |
| Consolidation | Fewer nodes automatically | Frequent pod restarts | Disruption budgets and quiet-hour windows |
| Spot nodes | Deep price discount | Sudden node loss | Diversify, on-demand floor, graceful shutdown |
| Scale to zero | No cost when idle | Slow first request | Use only for tolerant paths |
| Commitments | Lower rate | Paying for unused commitment | Commit to a conservative share of the stable floor |
| Same-zone routing | Lower transfer cost | Uneven load, less spread | Keep enough replicas in every zone |
Think of a hospital emergency room. A hospital that ran every bed at one hundred percent would be very efficient on paper, and would turn away a car crash victim. Some empty beds are not waste; they are insurance. The cost question is how much insurance is worth buying for each service.
Different services deserve different settings
Not every workload needs the same balance. A payment service deserves generous headroom and on-demand machines. A nightly report generator deserves the cheapest spot nodes and tight packing. Create a small number of service tiers, each with a clear recipe for requests, spread, purchase type, and disruption rules. Teams then choose a tier instead of arguing about every setting.
Critical
On-demand nodes, generous headroom, strict spread, strong disruption budgets.
Standard
On-demand floor plus spot extras, balanced packing, moderate headroom.
Flexible
Spot first, tight packing, retries and checkpoints, delay tolerated.
Non-production
Spot, scheduled shutdown, expiry labels, smallest sizes.
24Best Practices
Here are the habits that teams with lean, stable clusters tend to share. Think of them as a checklist to return to.
Make requests honest
Base them on a high percentile of measured usage plus a margin, and review them on a schedule.
Fewer, standard pod sizes
A small menu of sizes packs better than hundreds of unique values.
Wide instance lists
Let the autoscaler pick among many families and sizes that suit your workloads.
Diversify and keep a floor
Many pools, several zones, an on-demand fallback, and a floor for customer-facing services.
Rehearse interruptions
Terminate spot nodes in a safe environment regularly, and measure what users feel.
Set budgets and grace periods
Disruption budgets and realistic shutdown times make every drain safe.
Commit last
Right-size, pack, and add spot before buying long commitments.
Label everything
Require team and environment labels so every dollar has an owner.
Switch off the idle
Schedule non-production to shut down outside working hours.
Meet monthly
Review top spenders, gaps, and orphaned resources, and track actions to completion.
A starter sequence for a new team
Week one: see
Install a cost allocation tool, turn on labels, and find the top ten spenders and the biggest requested-versus-used gaps.
Weeks two and three: clean
Remove unused resources and schedule non-production shutdowns.
Weeks four to eight: right-size
Lower requests gradually for the largest services, watching health signals.
Weeks nine to twelve: pack and spot
Enable consolidation, widen the instance list, and move flexible workloads to spot after rehearsal.
Quarter two: commit and govern
Commit to a share of the stable floor and add quotas, policy, and a review rhythm.
25Anti-Patterns and Decision Records
An anti-pattern is a habit that looks sensible but leads to trouble. Each one below is written as a short decision record so you can see why people choose it and what to do instead.
Why teams do it
A bill alarm leads to a mandate to lower all requests by a fixed percentage.
What goes wrong
Services that really needed their memory are killed, and incidents follow. Trust in cost work collapses.
Better choice
Use measured high-percentile usage plus a margin, and change in small steps with monitoring.
Why teams do it
The cheapest single instance type looks like the best deal.
What goes wrong
One pool event removes the entire fleet, and replacement may be unavailable.
Better choice
Diversify across types and zones, keep an on-demand floor, and allow automatic fallback.
Why teams do it
Commitment discounts are visible, fast, and impressive in a report.
What goes wrong
Later optimization reduces usage below the commitment, so the team pays for capacity it no longer needs.
Better choice
Right-size and add spot first, then commit to a conservative share of the measured stable floor.
Why teams do it
A budget that allows no interruption feels like the safest setting for every service.
What goes wrong
Nodes can never be drained, so consolidation, upgrades, and spot handling are blocked, and the cluster stays large.
Better choice
Allow a sensible number of replicas to be unavailable, and keep strict settings for truly critical services only.
Why teams do it
Strict anti-affinity and spread rules are copied from a critical service into every template.
What goes wrong
Pods cannot share nodes, packing collapses, and the cluster fills with half-empty machines.
Better choice
Use soft preferences by default, and strict rules only where a failure would truly hurt.
Why teams do it
A central team runs a one-time cleanup and considers the problem solved.
What goes wrong
Without labels, reports, and review, waste slowly returns, and nobody notices until the next bill shock.
Better choice
Assign ownership by label, show costs to the owners, and keep a regular review.
26Managed Cloud Options and Where They Differ
The ideas in this post are the same on every cloud, but the product names, defaults, and limits differ. This chapter gives a map, so you know what to look up for your own platform.
The same ideas under different names
| Idea | Amazon (EKS) | Google (GKE) | Microsoft (AKS) |
|---|---|---|---|
| Discounted interruptible machines | Spot instances | Spot or preemptible virtual machines | Spot virtual machines |
| Node autoscaling | Cluster Autoscaler or Karpenter, plus managed node groups | Cluster autoscaler and node auto-provisioning, plus an autopilot mode that bills by pod | Cluster autoscaler and node auto-provisioning options |
| Commitment discounts | Savings Plans and reserved instances | Committed use discounts | Reservations and savings plans |
| Interruption notice | Short warning, commonly about two minutes | Short warning, commonly around half a minute | Short warning, commonly around half a minute |
Names, notice periods, product options, and billing rules change over time. The table above is a rough guide from general knowledge and may be out of date. Always confirm in your provider’s current documentation before designing around a specific number.
Two billing models
Most clusters bill by the node, so you pay for the machine whether or not pods fill it. This is why packing matters. Some managed modes bill by the pod request instead, so you pay for what your pods ask for, and the provider handles packing. In those modes, right-sizing requests becomes the single most important lever, because requests are directly your bill. Packing is still important, but it is the provider’s problem and not yours.
It is the difference between renting a whole van and paying per parcel. When you rent the van, you must fill it well. When you pay per parcel, the courier fills vans, but you pay for each parcel’s declared size, so declaring a size too large costs you directly.
Production example: choosing between modes
Teams with large, predictable workloads and strong platform skills often prefer node-based billing with their own autoscaling and spot strategy, to control cost tightly. Smaller teams, or those with variable workloads and limited platform capacity, often prefer pod-based modes for simplicity, accepting a slightly higher unit price in return for less operational work.
27A Decision Guide: What to Do First
With so many levers, where should you begin? This guide helps you pick the next step based on what you see in your own cluster.
flowchart TD
START["Cluster bill is too high"] --> Q1{"Are there unused or forgotten resources?"}
Q1 -->|"yes"| A1["Delete them and schedule non-production shutdowns"]
Q1 -->|"no"| Q2{"Is requested capacity far above real usage?"}
Q2 -->|"yes"| A2["Right-size requests with measured data"]
Q2 -->|"no"| Q3{"Are many nodes half full?"}
Q3 -->|"yes"| A3["Improve packing: consolidation, wider instance list, fewer pod sizes"]
Q3 -->|"no"| Q4{"Is most work tolerant of interruption?"}
Q4 -->|"yes"| A4["Move flexible work to diversified spot with a floor"]
Q4 -->|"no"| Q5{"Is a stable always-on floor measured?"}
Q5 -->|"yes"| A5["Commit to a conservative share of the floor"]
Q5 -->|"no"| A6["Improve visibility with labels and allocation reports"]
Comparing the main levers
| Lever | Typical effort | Typical saving | Risk | Do it |
|---|---|---|---|---|
| Delete waste, schedule shutdowns | Low | Small to medium | Very low | First |
| Right-size requests | Medium | Medium to large | Low to medium | Second |
| Better packing and consolidation | Medium | Medium | Low to medium | Third |
| Spot for flexible work | Medium to high | Large | Medium | Fourth |
| Commitments on the floor | Low | Medium | Low if conservative | Fifth |
| Network and storage tuning | Medium | Varies | Low | Alongside the others |
The best result is not the biggest cut in the first month. It is a system where waste does not creep back: honest requests, automatic consolidation, labeled ownership, and a regular review.
28Frequently Asked Questions
These are the questions people ask most often when they begin to work on Kubernetes cost.
29Summary and Key Takeaways
We began on a moving day with a hundred boxes and a fleet of rented trucks. Kubernetes cost work is the same game: fewer trucks, cheaper trucks, and a plan for the ones taken back.
What to remember
- You pay for nodes, and requests decide how many nodes you need. Inflated requests create slack, which is reserved capacity that sits idle.
- Make requests honest first. Use a high percentile of measured usage plus a margin, and change gradually while watching health signals.
- Bin packing means fewer, fuller nodes. The default scheduler favors spreading, so combine standard pod sizes, wide instance lists, and consolidation to get density, and keep headroom on purpose.
- Spot is the biggest discount and needs a plan for loss. Diversify across instance types and zones, keep an on-demand floor, set disruption budgets, and rehearse interruptions.
- Buy in layers. Commit to the stable floor last, use spot for flexible work, and use on-demand as the buffer.
- Remember storage, network, and idle time. Orphaned disks, cross-zone traffic, and always-on environments quietly drain budgets.
- Make cost visible and owned. Labels, allocation reports, quotas, and a regular review keep the savings from slipping away.