← Blog

EKS Has No Pod Limit: Here's What's Actually Stopping You

EKS has no AWS-enforced cap like "N pods per node." The number you've seen quoted (e.g. 29 pods on an m5.large) is a side effect of how the default networking plugin hands out IP addresses, not a quota.

EKS ships with the AWS VPC CNI. Every pod gets a real, routable VPC IP address — the same address space as EC2 instances. Those IPs come from ENIs (Elastic Network Interfaces) attached to the node, and AWS limits how many ENIs an instance can have and how many IPs each ENI can hold — by instance type.

So the pod ceiling is really an IP-address ceiling:

maxPods = (maxENIs × (IPsPerENI − 1)) + 2

The − 1 per ENI is because the ENI's primary IP isn't usable by pods. The + 2 is a fixed allowance built into AWS's formula (it covers the host-networked aws-node and kube-proxy pods, which never consumed a pod IP to begin with).

Example — m5.large: 3 ENIs × 10 IPs each → 3 × (10 − 1) + 2 = 29 pods. Try to schedule a 30th and it sits in ContainerCreating with a "failed to assign an IP" error. Not a quota — you literally ran out of addresses.

What prefix delegation changes

With ENABLE_PREFIX_DELEGATION=true (VPC CNI ≥ 1.9), the CNI stops assigning ENI slots one IP at a time and instead assigns each slot a /28 IPv4 prefix — 16 addresses at once. Same number of ENI slots, 16× the IPs per slot:

maxPods = (maxENIs × ((IPsPerENI − 1) × 16)) + 2

Same m5.large: 3 × ((10 − 1) × 16) + 2 = 434 theoretical pods. In practice you cap it — AWS and Kubernetes guidance is 110 pods/node for ≤ 32 vCPU, and 250 for > 32 vCPU, for kubelet and control-plane stability.

That's the "more pods than EKS allows" effect: a node that maxed out at 29 now comfortably runs 110.

The mental model: two ceilings

Before the steps, hold onto this, because it's the thing most write-ups miss. There are two independent ceilings, and you have to lift both:

  • Supply — how many IPs the CNI can hand out. Prefix delegation raises this.
  • Admission — how many pods the kubelet will accept. Its --max-pods value raises this.

Lift only the supply and the kubelet still turns pods away at the old number. Lift only max-pods and pods get scheduled but hang in ContainerCreating because there aren't enough addresses. You need both, and underneath them the instance and subnet have to physically absorb the new density.

Prerequisites

Check these first — they're what the change rests on:

  • VPC CNI ≥ 1.9.0. Prefix delegation was introduced in version 1.9.0, so anything older simply doesn't have the feature. Confirm the vpc-cni add-on (or the aws-node DaemonSet image) is at least that; any current release is fine.
  • Nitro-based instances, sized for the workload. Prefix delegation only works on Nitro instance types (virtually all current-generation instances qualify). The instance needs enough ENI capacity to supply 110 pod IPs — even modest instances clear this easily — and, just as importantly, enough CPU and memory to genuinely run 110 pods. Setting max-pods to 110 on a tiny 2-vCPU node lets the IPs exist but starves the workloads.
  • Subnets with room for prefixes. Every pod IP still comes from your VPC, and prefix delegation now consumes subnet addresses 16 at a time. Your node subnets need enough free space for nodes × prefixes-per-node × 16 addresses — and, the subtle part, they must have contiguous /28 blocks available. A heavily fragmented subnet can fail to hand out a prefix even when it has plenty of individual free IPs. Size node subnets generously, a /24 or larger, so prefixes always have room.

Enabling it

Step 1 — Turn on prefix delegation in the CNI. Set ENABLE_PREFIX_DELEGATION=true on the VPC CNI. This is the heart of the change: instead of assigning each pod one secondary IP from an ENI, the CNI now assigns each ENI slot a /28 prefix — a contiguous block of 16 IP addresses at once. This is what lifts the IP ceiling that was capping you at numbers like 29.

Step 2 — Tune the warm pool (optional but recommended). Set WARM_PREFIX_TARGET=1. This tells the CNI to keep one spare /28 prefix pre-allocated on each node, so a new pod gets an IP instantly instead of waiting on an EC2 API call. Leave WARM_IP_TARGET and MINIMUM_IP_TARGET unset — those belong to the old per-IP mode and, if present, will override prefix delegation and quietly undo Step 1.

Step 3 — Restart the CNI so the change takes effect. The aws-node pods read those environment variables at startup, so roll the DaemonSet (or let the managed add-on update do it for you). Until aws-node restarts, nothing changes.

Step 4 — Raise the kubelet's max-pods to 110. This is the step people miss, and it's the reason prefix delegation often "does nothing" the first time. The kubelet enforces its own --max-pods limit, and on EKS that value is computed at boot from the old per-IP formula. Even with a sea of new IPs available, the kubelet keeps refusing pods at the old number until you tell it otherwise. On Amazon Linux 2023 nodes you set this in the nodeadm NodeConfig under kubelet.config.maxPods: 110; on Amazon Linux 2 you pass --use-max-pods false --kubelet-extra-args '--max-pods=110' to the bootstrap script. The number 110 isn't arbitrary — it's the per-node count AWS and Kubernetes recommend for nodes with 32 vCPUs or fewer.

Applying it

Step 5 — Replace the nodes. The max-pods value is baked into the node's bootstrap configuration, which only runs when a node first boots. Existing nodes keep their old limit forever; only freshly launched nodes come up at 110. Roll the node group — cordon and drain the old nodes and let new ones replace them — and make sure the CNI was already in prefix-delegation mode before the new nodes join, so they register with the full IP supply from the start.

Verify

The checks mirror the two ceilings.

1. Supply side — confirm prefix delegation is live:

kubectl -n kube-system describe ds aws-node | grep -i prefix

Expect ENABLE_PREFIX_DELEGATION: true.

2. Admission side — confirm the node advertises 110 (on a freshly rolled node):

kubectl get node <node-name> -o jsonpath='{.status.allocatable.pods}{"\n"}'

Expect 110.

Those two confirm the configuration.

← Back to all posts