Kubernetes Pod Density
The kubelet says 110 pods per node. Your CNI says 30. Your node has 40% idle CPU, 30% free memory, and the scheduler is still refusing pods. The max-pods flag is not the problem — it's the IP ceiling, the system reservation tax, the DaemonSet overhead, and the bin packing fragmentation that no one accounts for until a deployment fails at 4 PM on Friday.
Published: 2026-07-03 | jslet Research | 14 min read | Classification: Unrestricted
110 Pods Is a Lie: The Problem Statement
Every Kubernetes engineer has had this moment: you are looking at kubectl top nodes and it says 40% CPU, 35% memory free. You have a deployment that needs 10 more pods. You run kubectl apply and the pods sit in Pending. You describe the node — CPU request is at 55%, memory request at 60%, everything looks fine. You check events: nothing. You stare at it. And eventually you find it: the node has 58 pod IP slots and all 58 are taken. The scheduler is not refusing your pods because of resource exhaustion. It is refusing them because it has no IP address to assign.
This is not a bug. It is four independent constraints layered on top of each other, and the one that breaks first is almost never the one you are monitoring. The kubelet's --max-pods flag defaults to 110, and every Kubernetes introduction mentions this number like it is a capacity guarantee. It is not. It is a ceiling that almost no cloud-managed node reaches — not because the node runs out of CPU or memory, but because the network plugin runs out of IP addresses, or the system reservations ate more memory than you budgeted, or the DaemonSet replicas you installed for observability consume 8 pod slots before your application code gets a chance to claim one.
I built the K8s Pod Density & Bin Packing Calculator on this site to model all four constraints simultaneously — to let you see which one breaks first, and how far below 110 your real pod ceiling sits, before you discover it in production. This article walks through each constraint, quantifies it across cloud providers, and gives you a practical framework for sizing nodes that does not rely on the 110 number anyone can read off a default config value.
The Four Constraints That Actually Cap Your Node
The Kubernetes scheduler evaluates every node against four independent dimensions. The node can host pods only up to the minimum of the four. If one constraint caps at 29 pods and the other three allow 200+, the node runs 29 pods. The remaining CPU, memory, and storage sit idle — not because you misconfigured anything, but because the binding constraint is absolute.
Pod density = min(CPU constraint, memory constraint, storage constraint, IP constraint)
The smallest number wins. Everything above that number is stranded capacity — resources you paid for that no pod can use because a different dimension capped first.
| Constraint | Formula | Who Enforces It | What Happens When You Hit It |
|---|---|---|---|
| CPU | (node vCPU − system reserve) ÷ pod CPU request | Scheduler (predicate check) | Pods go Pending. CPU throttling if limits exceed requests. Measurable, survivable, reversible. |
| Memory | (node RAM − system reserve) ÷ pod mem request | Scheduler + kubelet eviction | OOM kill if usage exceeds node capacity. Pod restart cascade. Hard stop. |
| Ephemeral Storage | (node disk − system reserve) ÷ pod storage request | Scheduler + kubelet GC | DiskPressure taint → eviction. Image GC triggers at 85% disk usage. Container logs fill emptyDir. |
| IP Address Slots | Provider-specific ENI × IPs per ENI − 1 | CNI plugin | Pods go Pending. Zero workaround short of instance replacement. The #1 binding constraint on large nodes. |
The IP constraint is the one that surprises everyone. CPU and memory are visible — you see them in your monitoring dashboards, you set requests and limits for them, you alert on them. IP address slots are invisible until they run out. And on cloud-managed Kubernetes, the IP ceiling is determined by your instance type, not your configuration — you cannot kubectl edit your way out of an ENI limit on an m5.large. The only fix is a larger instance type with more ENIs, which means paying for CPU and memory you do not need, which means worse bin packing efficiency, which means higher cloud bills. This is the structural tension at the center of Kubernetes pod density: the thing that limits your pod count is often the thing you have the least control over.
The pod density calculator lets you select an instance type and a pod resource profile and immediately see which of the four constraints is the binding one. Try it: drop in an m5.2xlarge (8 vCPU, 32 GiB), set pod requests to 500m CPU and 256 MiB memory. The CPU constraint says 14 pods. The memory constraint says 122 pods. The IP constraint — EKS on this instance type — says 58. Your node will run 14 pods and strand 60% of its memory. That is not a bug. That is the real pod density for that workload on that instance.
The System Reservation Tax: What Kubernetes Takes Before Your First Pod
Before a single application pod lands on a node, Kubernetes has already consumed a chunk of the node's resources for itself. The Node Allocatable specification — defined in the kubelet configuration — reserves CPU, memory, and storage for three categories of overhead. The scheduler sees allocatable capacity, not raw capacity. If your node has 16 vCPUs and the system reserves 10%, the scheduler works with 14.4 vCPUs. Every pod you place competes for that smaller pool.
| Reservation | Default CPU | Default Memory | Default Storage | What It Covers |
|---|---|---|---|---|
| kube-reserved | — | — | — | kubelet (~200 MB RSS), container runtime (~300–500 MB containerd or CRI-O), kube-proxy. Most critical on memory-constrained nodes. |
| system-reserved | — | — | — | OS daemons (systemd, journald, sshd), kernel page cache (not fully evictable), cloud-init, package manager. More significant on self-managed nodes. |
| Eviction Threshold | — | 100 MiB | 10% disk | Hard eviction trigger: --eviction-hard=memory.available<100Mi. When free memory drops below this, kubelet evicts pods. This is a floor, not a ceiling — the node never uses these bytes for pods. |
Managed Kubernetes services (EKS, GKE, AKS) pre-configure system reservations in their optimized AMIs/VM images. Self-managed clusters using kubeadm apply default reservations; kOps and Rancher allow explicit tuning. The exact reservation values are set via kubelet flags: --kube-reserved, --system-reserved, and --eviction-hard. Check your node's allocatable field — not its capacity field — to see what the scheduler actually works with.
On a 16 vCPU node with 64 GiB of RAM at default reservation (10% CPU, 1.5 GiB RAM), the allocatable capacity is 14.4 vCPU and 62.5 GiB. That 1.6 vCPU and 1.5 GiB difference looks small — less than 3% of total capacity. But the 1.5 GiB of reserved memory is enough for approximately 6 additional pods at 256 MiB per pod. On a cluster with 50 nodes, that is 300 pods of stranded capacity — the equivalent of three entire nodes that you paid for but cannot use for application workloads because the system called dibs first.
Cloud-managed Kubernetes services hide these reservations. They do not appear in your cloud bill as a separate line item, and they do not trigger resource alerts because they are subtracted before the scheduler counts capacity. The only way to see them is to compare kubectl describe node output — the Capacity section vs the Allocatable section — line by line. The difference is the system tax. The pod density calculator surfaces this in its YAML output block — it shows both the raw node spec and the allocatable remainder, so you can see exactly what the scheduler sees.
What Each Cloud Provider Actually Allows: The Pod Count Ceiling by Instance
The kubelet's default 110-pod limit is the starting point. Each cloud provider then applies its own IP allocation model on top, and the resulting per-instance pod ceiling is frequently less than half of 110 — and sometimes under 20. Here is the actual ceiling across common instance types, mid-2026.
AWS EKS: The ENI Multiplier Game
EKS uses the AWS VPC CNI, which assigns each pod an IPv4 address from the VPC subnet via an Elastic Network Interface (ENI) attached to the EC2 instance. The maximum pods for any EC2 instance type is:
max pods = (ENI count × IPv4 addrs per ENI) − 1
The −1 accounts for the kubelet's own IP on the primary ENI
| EC2 Instance | ENIs | IPs per ENI | Max Pods | Typical Memory Wastage at 500m CPU/Pod |
|---|---|---|---|---|
| t3.medium (2 vCPU / 4 GiB) | 3 | 6 | 17 | CPU limits to 3 pods at 500m. IP ceiling is irrelevant. |
| t3.large (2 vCPU / 8 GiB) | 3 | 12 | 35 | CPU limits to 3 pods. IP ceiling is 12× actual demand. |
| m5.large (2 vCPU / 8 GiB) | 3 | 10 | 29 | CPU limits to 3 pods. Memory limits to ~20. IP at 29 — not binding. |
| m5.2xlarge (8 vCPU / 32 GiB) | 4 | 15 | 59 | CPU limits to ~14 pods at 500m. IP ceiling irrelevant. |
| m5.4xlarge (16 vCPU / 64 GiB) | 8 | 30 | 239 | CPU limits to ~28 pods at 500m. Memory limits to ~240. IP ceiling of 239 may bind for memory-light, CPU-light pods. |
| c5.9xlarge (36 vCPU / 72 GiB) | 8 | 30 | 239 | CPU at 500m: 65 pods. But IP ceiling caps at 239 — AND the kubelet max-pods flag typically caps at 234 on EKS AMIs. |
The pattern is clear: on small and medium instances (2–8 vCPU), the CPU constraint is the binding limit for typical web workloads at 250–500m per pod. On large instances (16+ vCPU), the IP ceiling becomes binding for CPU-light workloads — and the ENI model makes the transition non-linear. An m5.xlarge (4 ENIs, 15 IPs each) gets 59 pods. An m5.4xlarge (8 ENIs, 30 IPs each) gets 239 pods. The 4× vCPU increase gives you a 4× pod ceiling increase — not from CPU, but from ENI count. The instance type determines the pod density, and the pod density determines the instance type. This is circular, and the only way out is to model it explicitly — which is what the calculator does.
GKE: Memory-Tiered Limits
GKE takes a different approach. Rather than tying pod limits to network interfaces, GKE scales the max-pods ceiling with node memory, auto-detected from the machine type at node registration:
| Node Memory | Default Max Pods | Example Instance |
|---|---|---|
| ≤ 15 GiB | 110 | n1-standard-4 (4 vCPU / 15 GiB) |
| 15–30 GiB | 128 | n2-standard-8 (8 vCPU / 32 GiB) |
| 30–60 GiB | 256 | n2-standard-16 (16 vCPU / 64 GiB) |
| > 60 GiB | 256 | n2-standard-32 (32 vCPU / 128 GiB) |
GKE's auto-detected max-pods can be overridden with --max-pods-per-node on node pool creation, up to 256. This is simpler than the EKS model — no ENI arithmetic — but means GKE nodes on the same instance type as EKS will often report different pod ceilings.
Azure AKS: The 30-Pod Default
Azure AKS with the default Azure CNI has the most restrictive pod limit of the three major clouds: 30 pods per node by default, configurable by adjusting the subnet prefix size. This is the worst-case binding constraint in the entire ecosystem — a node with 16 vCPUs and 64 GiB can run at most 30 pods, regardless of resource headroom. The newer Azure CNI Overlay (with Cilium) raises this to 250 pods and is the recommended migration path for teams hitting the 30-pod wall. If you are on Azure AKS and haven't checked which CNI mode you are running, check now. The difference between 30 and 250 pods per node is the difference between running 10 nodes and running 2.
Bin Packing: Why vCPUs ÷ Pod CPU Request Is Always Wrong by 40–60%
The naive pod density formula is seductive: take your node's vCPU count, divide by your pod's CPU request, and that's how many pods fit. On a 16 vCPU node with 500m per pod: 16 ÷ 0.5 = 32 pods. This number is wrong. It is wrong because the formula omits four structural losses that compound before the scheduler places a single pod.
🔑 The Bin Packing Losses
Actual pod density = naive formula × (1 − system reservation %) × (1 − DaemonSet slot %) × (1 − fragmentation %) — with a hard cap at the IP ceiling.
On a typical 16 vCPU production node: 32 pods naive → 28.8 after system reservation (10%) → 24.8 after DaemonSet pods (6 slots) → ~19 after fragmentation (15% unplaceable fragments) → capped at whatever the CNI allows. The naive formula overestimates by 40–60% on CPU-bound workloads, and by 80–90% on IP-bound workloads.
Fragmentation is the invisible loss. The scheduler places pods one at a time using a greedy first-fit algorithm. It does not look ahead. It does not defragment. A node that has 2 vCPU free but fragmented across 4 non-contiguous chunks of 500m each cannot place a pod requesting 600m — not because 2 vCPU is insufficient, but because no single chunk is large enough. This is the knapsack problem, and the Kubernetes scheduler does not solve it optimally. It solves it greedily. The result is stranded capacity: CPU and memory that are free on aggregate but unusable for any single pod.
This is why the pod density calculator reports "wasted CPU" and "wasted memory" as percentages rather than absolutes. On a 16 vCPU node running 19 pods at 500m each, total CPU requested is 9.5 vCPU out of 14.4 allocatable — 34% wasted, not from overprovisioning, but from the fragmentation that the greedy scheduler leaves behind when the IP ceiling or memory constraint caps pod count before CPU is fully utilized.
The same bin packing math appears in the container resource limit calculator on this site — the gap between requests and limits is another fragmentation source, because the scheduler counts requests but the node experiences usage. A pod requesting 500m but actually using 80m at idle is occupying a slot that could have held a pod requesting 200m and using 180m. The scheduler does not see actual usage. It sees declared requests. And declared requests are almost always higher than actual usage because engineers add safety margins — a rational choice at pod-creation time that becomes expensive at cluster scale.
The DaemonSet Tax: Pods You Didn't Count That Consume Slots
A DaemonSet places one pod on every node. It is invisible in your application manifests — you did not write it, you did not budget for it — but it consumes a pod slot, a slice of CPU, and a chunk of memory on every node in your cluster. In a production cluster that has accumulated the standard observability, networking, and security stack, the DaemonSet tax is 5–10 pods per node before your application code deploys a single replica.
| DaemonSet | CPU (request) | Memory (request) | Notes |
|---|---|---|---|
| CNI agent (Calico-node / Cilium / AWS VPC CNI) | 100–250m | 100–300 MiB | Non-negotiable. Manages pod networking, route tables, iptables/eBPF rules. Cilium in eBPF mode is the heaviest (250m/300Mi); AWS VPC CNI is lighter (100m/100Mi). |
| kube-proxy | 100m | 100 MiB | iptables/IPVS rule management. Optional if Cilium replaces it with eBPF, but still present on most clusters. |
| Node exporter / monitoring agent | 50–100m | 50–100 MiB | Prometheus node_exporter, Datadog agent, Grafana Alloy — at least one per cluster, often two. |
| Log collector (Fluent Bit / Fluentd / Vector) | 50–200m | 100–300 MiB | Fluent Bit is the lean option (50m/100Mi). Fluentd can consume 200m/300Mi under high log volume. |
| CSI driver (EBS / EFS / Portworx) | 50–100m | 50–128 MiB | Storage provisioner. Usually one DaemonSet per CSI driver. EBS CSI: ~50m/50Mi. |
| Policy engine (Gatekeeper / Kyverno audit) | 100–200m | 128–256 MiB | Optional but common in regulated environments. |
| Service mesh sidecar injector (Istio / Linkerd init) | 10–50m | 20–64 MiB | The init container per DaemonSet pod is usually negligible, but the sidecar injected into each application pod is material — and those sidecars consume slots too. |
Resource values are approximate and vary with configuration and traffic load. Measure your own cluster's DaemonSet consumption with kubectl top pods -A | grep -E 'kube-system|monitoring|logging' — the totals will differ from these averages.
On a minimal production cluster (CNI + kube-proxy + node-exporter + Fluent Bit), the DaemonSet tax is approximately 4 pod slots, 300m CPU, and 350 MiB memory per node. On a fully-loaded production cluster with service mesh, policy engine, and a commercial monitoring agent, it can exceed 8 pod slots, 900m CPU, and 1 GiB memory. On a node with a 58-pod IP ceiling (m5.2xlarge on EKS), 8 DaemonSet pods consume 14% of the total pod slots. On a node with a 30-pod limit (Azure AKS with default CNI), 8 DaemonSet pods consume 27%.
This is the structural reason small nodes are inefficient for pod-dense workloads: the DaemonSet tax is per-node and constant, so it consumes a larger fraction of a small node's capacity. On a 4 vCPU node with 29 pod slots, 6 DaemonSet pods = 21% of slots and 350m of 3.6 allocatable vCPU = 10% of CPU. On a 16 vCPU node with 234 pod slots, 6 DaemonSet pods = 2.6% of slots and a rounding error on CPU. Large nodes amortize the DaemonSet tax. The tradeoff is that a large node failure takes down more pods — a blast radius vs efficiency trade that each team must calibrate to their own recovery SLAs.
Overcommit Strategies: The CPU vs Memory Asymmetry
Not all resource constraints are equal under pressure. The fundamental difference between CPU and memory — compressible vs incompressible — determines how much you can overcommit each dimension before things break. And the breakage modes are different: CPU throttling makes your pods slower. Memory exhaustion kills your pods. The scheduling strategy for each should reflect the cost of getting it wrong.
⚖️ The Resource Asymmetry
CPU is compressible: overcommit as much as your latency SLO tolerates. Memory is incompressible: set request = limit and never overcommit past physical capacity.
CPU overcommit works because most pods idle most of the time. A web server handling 100 requests/second at 50m CPU that has a request of 500m is using 10% of its declared allocation. The scheduler counts 500m. The node experiences 50m. The 450m difference is overcommit headroom that the scheduler does not know exists but the kernel distributes transparently via CFS. This is why production clusters routinely run at 3–5× CPU overcommit without visible throttling — the declared requests are safety margins, and the actual usage is much lower.
The risk: if enough pods spike simultaneously — a traffic surge, a cron job firing, a cache flush — the aggregate CPU demand can exceed the node's physical cores, and CFS throttles every container proportionally. Throttling shows up as container_cpu_cfs_throttled_seconds_total in Prometheus metrics. A few seconds of throttling per hour is normal. Sustained throttling across many pods manifests as tail latency spikes that cascade through upstream callers. The latency budget calculator on this site models exactly this: a 50ms throttle on a 300ms end-to-end budget consumes 17% of the total latency allocation — for one hop.
Memory overcommit is different. When the sum of pod memory usage exceeds the node's physical memory, the kernel OOM killer selects a container to terminate based on oom_score — not based on which pod is least important, but based on which pod is using the largest fraction of its declared request. The pod restarts. If it is a stateless web server behind a load balancer, this is an annoyance. If it is a single-replica stateful workload holding an in-memory cache, this is a data loss event. The practical rule: set memory request = memory limit for any workload where an OOM kill has operational consequences. The small efficiency gain from setting a lower request is not worth the surprise restart at 2 AM when the node's memory pressure spikes. Use the container resource limit calculator to model the overcommit ratio and see the probability of OOM kill at different request-to-limit ratios.
For pod density specifically, the asymmetry means: if you are CPU-bound, you can increase density by lowering CPU requests to match observed usage (p95 + 20% buffer) without changing limits. If you are memory-bound, lowering memory requests risks OOM kills, and the safer path is increasing node memory — either by choosing a memory-optimized instance type (r5/r6i on AWS, n2-highmem on GCP) or by adding more nodes. The pod density calculator shows you which dimension is binding, so you know which lever to pull.
How to Size Your Nodes: A Practical Decision Framework
After modeling every constraint across every cloud provider, the node sizing decision collapses to a few rules. They are not the rules in the Kubernetes documentation, which assumes a self-managed cluster with a /24 CIDR and 110 pods per node. They are the rules that account for the cloud provider IP models, the DaemonSet tax, and the bin packing fragmentation that the documentation ignores.
Rule 1: Know your binding constraint before you pick an instance type. Run your pod resource profile through the pod density calculator for each candidate instance type. Do not assume the binding constraint is CPU or memory. On EKS, the IP ceiling is the binding constraint for any instance with 8+ ENIs running pods at ≤250m CPU. On AKS with default Azure CNI, the 30-pod ceiling is always the binding constraint. The constraint that is binding determines whether you should optimize for vCPUs, memory, or ENI count — and those lead to different instance families.
Rule 2: Prefer fewer large nodes over many small nodes — up to the blast radius limit. Large nodes (16+ vCPU) amortize the DaemonSet tax, reduce control plane load, and waste less capacity to fragmentation because the larger pool smooths out the greedy scheduler's allocation errors. The counterforce is the blast radius: a 16 vCPU node running 60 pods that fails takes down 60 pods. If your application can tolerate that — because it is stateless, horizontally scaled, and spread across at least 3 nodes — large nodes are strictly more efficient. If your application is stateful or has tight recovery time objectives, smaller nodes reduce the per-failure impact at the cost of more stranded capacity.
Rule 3: Measure actual usage before setting requests. The most common source of stranded pod capacity is requests that are 3–5× actual usage. Engineers set requests high because it is safer, and the scheduler rewards conservatism by never placing a pod on a node that cannot satisfy its declared request. But across a 50-node cluster, a 2× safety margin on every pod translates to roughly double the node count — and double the cloud bill. Install the Vertical Pod Autoscaler in recommendation mode. Let it profile your workloads for two weeks covering both weekday peak and weekend trough. Set requests to the p95 of observed usage plus a 20% buffer. The difference between this number and the original guess is typically 40–60%, and every percentage point translates directly to pod density.
Rule 4: Account for DaemonSets in your node budget. Count every DaemonSet pod running in your cluster. Multiply its resource requests by the number of nodes. Subtract those resources from each node's allocatable capacity. This is the actual pool available for your application pods. If you do not do this subtraction, your scheduler will — and it will refuse pods on nodes that appear to have headroom but do not, because the DaemonSet claimed it first. The calculator applies system reservations in its allocatable computation; add your measured DaemonSet overhead as an additional manual deduction.
Rule 5: If you are on Azure AKS with default CNI, upgrade to Azure CNI Overlay before doing anything else. The 30-pod ceiling is the single most binding constraint in managed Kubernetes. It caps a 16 vCPU, 64 GiB node at 30 pods — a utilization rate below 20% for typical web workloads. Azure CNI Overlay (with Cilium) raises this to 250 pods and is the only change your cluster needs to go from 10 nodes to 3 for the same workload. It is the highest-ROI infrastructure change available in the Kubernetes ecosystem in 2026.
And one thing that applies regardless of cloud provider or workload: profile before you scale. Most pod density problems are not capacity problems — they are configuration problems. The misconfiguration is invisible because the scheduler does not log "I could fit 20 more pods if you lowered CPU requests by 100m." It just says Pending. Use the pod density calculator to model your node against your actual pod profile, find the binding constraint, and fix that before you add more nodes. Adding nodes to solve a configuration problem is the most expensive debug strategy in Kubernetes. And it is also the most common.
Frequently Asked Questions
How many pods can a Kubernetes node run?
The kubelet's --max-pods flag defaults to 110 pods per node, but in practice the binding constraint is almost always lower. On AWS EKS, pod limits are determined by ENI count × IPs per ENI minus 1; a c5.2xlarge (8 vCPU) maxes at 58 pods, while a t3.medium (2 vCPU) caps at 17. On Azure AKS with default Azure CNI, the limit is 30 pods — less than one-third of the kubelet default. On GKE, limits auto-scale with node memory: 110 at ≤15 GiB, up to 256 at >60 GiB. For typical web workloads at 250–500m CPU per pod, the CPU constraint is the binding limit on small and medium instances (2–8 vCPU). On large instances (16+ vCPU), the IP ceiling becomes binding for CPU-light workloads. The real maximum pods per node is always the minimum of CPU, memory, storage, and IP constraints. Use the pod density calculator to find your binding constraint.
Why is my Kubernetes node refusing pods when it has free CPU and memory?
You have almost certainly hit one of four invisible ceilings: (1) The IP address limit — each pod consumes one IP from the node's VPC CIDR allocation. On AWS EKS, this is per-ENI; on Azure AKS, this is per-subnet prefix. Once all IPs are allocated, the scheduler refuses pods regardless of CPU/memory headroom. (2) The PID limit — kubelet enforces a per-pod PID cap (default 4096). If a single pod spawns too many threads, it hits the cgroup limit. (3) Ephemeral storage exhaustion — container images and emptyDir volumes consume node disk, and the kubelet refuses pods when disk pressure exceeds the eviction threshold (default 85%). (4) You are counting raw node capacity instead of allocatable capacity. System reservations (kubelet, container runtime, OS daemons) consume 5–15% of node resources before the scheduler sees them. Check kubectl describe node for the Conditions section and compare Capacity vs Allocatable. The difference is what the system took before your pods got a chance. Run your configuration through the pod density calculator to see which constraint is binding.
How does the AWS EKS pod limit work?
EKS uses the AWS VPC CNI, which assigns each pod an IPv4 address from the VPC subnet via Elastic Network Interfaces (ENIs) attached to the EC2 instance. The max pods formula is: (ENI count × IPs per ENI) − 1, where the −1 accounts for the kubelet's IP. An m5.4xlarge with 8 ENIs and 30 IPs each: (8 × 30) − 1 = 239, though EKS-optimized AMIs cap it at 234. An m5.large with 3 ENIs and 10 IPs each: (3 × 10) − 1 = 29. The ENI count is determined by instance type — you cannot increase it without changing instance size. This means the pod ceiling for a given EKS node is a function of hardware, not configuration, and the only fix is a larger instance. The full per-instance table is available via the EKS max-pods script (aws/amazon-eks-ami on GitHub). For IP-dense workloads on EKS, instance type selection should prioritize ENI count over CPU or memory.
What is Kubernetes bin packing and how do I optimize it?
Bin packing is the process of fitting pods onto nodes to minimize stranded resources. The Kubernetes scheduler uses a greedy first-fit algorithm: it scores each node by how well a pod fits, weighted by CPU and memory utilization equally, and places the pod on the highest-scoring node. This algorithm is fast — it makes placement decisions in milliseconds — but it leaves capacity stranded because it does not look ahead, defragment, or consider actual usage vs declared requests. To optimize bin packing: (1) Set resource requests to p95 of observed usage + 20% buffer — not to a safety guess. Use the Vertical Pod Autoscaler in recommendation mode. (2) Prefer homogeneous workloads on the same node pool — mixing CPU-heavy and memory-heavy pods on the same node creates fragmentation where one dimension exhausts while the other sits idle. (3) Use pod anti-affinity rules sparingly — each anti-affinity constraint reduces the scheduler's placement options and increases fragmentation. (4) Consider topology spread constraints instead of pod anti-affinity for availability — they achieve the same blast-radius reduction with less scheduling restriction. The pod density calculator shows the fragmentation waste for your node/pod profile.
Should I use large or small Kubernetes nodes?
Large nodes (16+ vCPU) are more efficient per pod — they amortize DaemonSet overhead, reduce control plane load, and suffer less bin packing fragmentation. A 16 vCPU node wastes roughly half the stranded capacity of four 4 vCPU nodes running the same workload. The counterforce is blast radius: a 16 vCPU node running 60 pods that fails takes down 60 pods simultaneously. If your application is stateless, horizontally scaled, and spread across at least 3 nodes in a regional cluster, the large-node efficiency gain is worth the blast radius increase. If your application is stateful or has sub-60-second recovery objectives, smaller nodes reduce per-failure impact at the cost of higher per-pod overhead. Practical rule for most teams: run the largest node your recovery SLA allows, not the smallest. The cost savings from improved bin packing on large nodes typically exceed the additional per-node cost. Model both options with the pod density calculator and compare the per-pod cost, not the per-node cost.
Methodology & Disclosure
Cloud provider pod limit data is sourced from public documentation current as of July 2026: AWS EKS ENI limits from the EC2 instance types reference and the EKS max-pods script (github.com/awslabs/amazon-eks-ami), GKE max-pods from the GKE node pool documentation, and Azure AKS pod limits from the AKS networking documentation. DaemonSet resource estimates are typical values from the upstream Helm charts and operator defaults for each project (Calico, Cilium, Fluent Bit, Prometheus node_exporter, Datadog agent). Actual resource consumption varies with configuration, traffic load, and log volume — measure your own cluster.
System reservation defaults are derived from the kubelet's default --kube-reserved and --system-reserved values as documented in the Kubernetes Node Allocatable specification (kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/). Cloud-managed Kubernetes services (EKS, GKE, AKS) apply provider-specific reservation overrides in their optimized AMIs/VM images. The values used in this article are the self-managed defaults; check your cloud provider's documentation for the exact reservation values on managed nodes.
Bin packing efficiency estimates assume a homogeneous workload profile (uniform pod resource requests) with the Kubernetes default scheduler's MostAllocated scoring strategy. Heterogeneous workloads, custom scheduler plugins, and topology spread constraints will produce different fragmentation patterns. The fragmentation percentages cited are averages derived from Monte Carlo simulation of the greedy first-fit algorithm on realistic pod size distributions at medium scale (50–200 nodes).
Disclosure: jslet is an independent research project. We are not sponsored by any cloud provider, CNI vendor, or Kubernetes distribution. The K8s Pod Density & Bin Packing Calculator was built because the author watched a production deploy fail at 4:47 PM on a Friday — node had 35% free CPU, 40% free memory, and zero free IP slots — and decided the next engineer should find their binding constraint before the scheduler does. No affiliate links. No referral codes. No sponsored recommendations.
References & Further Reading
- Kubernetes (2026). "Node Allocatable — Reserving Compute Resources for System Daemons." Kubelet configuration for kube-reserved, system-reserved, and eviction thresholds. kubernetes.io
- AWS (2026). "EKS Pod Networking — ENI Allocation and Maximum Pods per Node." VPC CNI IP allocation model, ENI limits by instance type, and the max-pods calculation script. github.com/awslabs
- Google Cloud (2026). "GKE Node Pools — Pod Density and max-pods-per-node Configuration." Auto-detected max-pods based on node memory, configurable per node pool. cloud.google.com
- Microsoft Azure (2026). "AKS Networking — Azure CNI vs Azure CNI Overlay Pod Limits." Default 30-pod ceiling with Azure CNI, 250-pod ceiling with CNI Overlay, and subnet prefix sizing. learn.microsoft.com
- Kubernetes SIG-Scheduling (2026). "Kubernetes Scheduler — Scoring Plugins and NodeResourcesFit." Greedy first-fit algorithm, MostAllocated/LeastAllocated scoring strategies, and bin packing behavior. kubernetes.io
- Kubernetes (2026). "Resource Management for Pods and Containers — CPU and Memory Requests vs Limits." CFS quota enforcement, OOM scoring, and compressible vs incompressible resource semantics. kubernetes.io
- Cilium (2026). "eBPF-based Networking — Replacing kube-proxy and VPC CNI Overhead." Resource consumption benchmarks for Cilium in eBPF mode vs iptables-based kube-proxy. docs.cilium.io
- CNCF FinOps Working Group (2025). "Kubernetes Resource Optimization — Industry Survey on Container CPU/Memory Utilization and Overcommit Ratios." cncf.io
📜 Copyright & Attribution
© 2026 jslet Research. This article is an original work independently researched and published on jslet (jslet.com). All rights reserved.
Sharing & Reprinting: You may share excerpts (up to 200 words) with a mandatory, do-follow link back to this article's canonical URL. Full reproduction, translation, or adaptation requires prior written permission from jslet Research. Commercial republication, AI/LLM training corpus ingestion, and paywalled syndication are expressly prohibited without a licensing agreement.
Preferred citation format:
"Kubernetes Pod Density: Why 110 Pods Is a Lie — The Hidden Limits That Cap Your Node (2026)" — jslet Research, July 2026.
https://www.jslet.com/pod-density-real
📡 Enjoyed this? If this briefing saved you from explaining "110 pods per node" to a PM for the fourth time, the RSS feed might save you from the fifth. One deep-dive per week. No ads. No tracking. Just the math. RSS Feed → | More options →