110 pods, zero reservations
We raised Karpenter maxPods to 110 for prefix delegation and set no kubelet reservations. A packed 4 GB spot node ran out of memory at the OS level and took the platform stack down with it.
- Date
- 2026-05-28
- Type
- incident
- Impact
- The monitoring, ingress and auth pods on one system node became unreachable when the node wedged. Kubernetes couldn't evict or reschedule them because the kubelet had stopped responding.
A system node stopped heartbeating. EC2 said running. SSM couldn’t reach it. The
kubelet was gone, so nothing was evicted, nothing was rescheduled, and everything on
the node was stuck. That node ran monitoring, ingress and auth.
Timeline
| Date | Event |
|---|---|
| May 22 | VPC CNI prefix delegation enabled. Managed node group t3.medium goes from 17 to 110 max pods |
| May 22 | Karpenter t3a.medium nodes stay at 17. kubelet.maxPods: 110 set explicitly on EC2NodeClass |
| May 28 | Spot c6a.large system node wedges at 95% memory requests / 166% limits |
| May 28 | Root cause: maxPods 110 with no kubeReserved, systemReserved or evictionHard |
The actual cause
This came in two steps, and the first fix set up the second failure.
Step one. We enabled ENABLE_PREFIX_DELEGATION on the VPC CNI. Managed node
groups (AL2023 nodeadm) read that setting and recomputed max pods. Karpenter doesn’t.
It computes --max-pods from the old per-ENI IP formula and never looks at the
aws-node DaemonSet. So we set maxPods: 110 on every EC2NodeClass.
Step two. That EC2NodeClass had no kubelet reservations. The kubelet reserves
memory for itself and the OS, and the EKS formula scales with pod count:
255Mi + 11Mi × maxPods. At 110 pods that’s about 1465Mi, over a third of a 4 GB node.
We reserved nothing.
The scheduler did what it was told. It packed the c6a.large to 95% of memory
requests, with limits at 166%. When real usage passed physical memory, there was no
headroom for eviction to act. The OS itself ran out of memory, the kubelet died, and the
node wedged. Losing a single pod would have been fine. We lost the whole node.
# EC2NodeClass — maxPods and its memory reservation belong together
kubelet:
maxPods: 110
kubeReserved:
memory: 1465Mi # 255Mi + 11Mi * 110
What changed
- Rule: any EC2NodeClass with a high
maxPodsalso setskubeReserved,systemReservedandevictionHard, sized to that maxPods. - In Karpenter v1,
maxPodslives on the EC2NodeClass. A different pod density per pool means a separate EC2NodeClass per pool. - The microservices pool keeps high density but no longer allows
medium(2 GB) instances. Reserving about 1.46Gi out of 2 GB leaves nothing for pods. - Planned, not done yet: the system pool moves to low maxPods (~40),
mfamily and on-demand. Ten heavy infra pods on spot were never a good fit for maxPods 110.
What didn’t work
Treating maxPods as a networking setting. It controls memory just as much.
Raising pod density without raising reservations doesn’t give you more capacity. It lets the scheduler overcommit the node until the OS runs out of memory.
← all writeups