Symptoms
- Pods stuck in
ContainerCreatingduring a scale-out or deployment. kubectl describe podevents showfailed to assign an IP address to containeroradd cmd: failed to assign an IP address.- Cluster Autoscaler or Karpenter adds nodes, but pods still don't start.
1. Confirm IP exhaustion
kubectl get pods -A --field-selector=status.phase=Pending -o wide | head
kubectl describe pod <pod> -n <ns> | grep -iA3 "failed"
kubectl logs -n kube-system -l k8s-app=aws-node --tail=50 | grep -iE "error|insufficient|ipamd"
Check subnet free addresses:
aws ec2 describe-subnets --filters Name=tag:kubernetes.io/cluster/eks-prod,Values=shared,owned \
--query "Subnets[].{id:SubnetId, az:AvailabilityZone, cidr:CidrBlock, free:AvailableIpAddressCount}" --output table
| Finding | Meaning |
|---|---|
| Subnets show very few free IPs | Subnet exhaustion; VPC CNI gives each pod a VPC IP |
| Plenty of subnet IPs, node at max pods | ENI / IP-per-ENI limit of the instance type |
aws-node pods crash-looping | CNI itself broken (IAM, version) |
2. Quick relief
- Reduce warm IP waste: by default ipamd keeps a whole spare ENI's worth of IPs per node. Lower it:
kubectl set env daemonset aws-node -n kube-system WARM_IP_TARGET=2 MINIMUM_IP_TARGET=10
Warning
Very low warm targets increase EC2 API calls and pod start latency, and can hit API throttling on large clusters. Use moderate values and revisit after the incident.
- Scale down non-critical workloads to free IPs for critical pods.
- Remove idle nodes holding warm IPs (cordon, drain, terminate) if autoscaling over-provisioned.
3. Fix node-level limits: prefix delegation
With prefix delegation, each ENI slot gets a /28 prefix (16 IPs), greatly raising pods per node on Nitro instances.
kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true WARM_PREFIX_TARGET=1
Existing nodes keep their old allocation; new nodes use prefixes. Also raise maxPods for new nodes (managed node groups compute it with the right settings; for custom AMIs set it in the bootstrap). Prefix delegation needs contiguous free /28 blocks in the subnet, which fragmented subnets may not have.
4. Fix subnet exhaustion: more address space
Add a secondary CIDR to the VPC and new subnets for pods with custom networking:
aws ec2 associate-vpc-cidr-block --vpc-id vpc-0abc --cidr-block 100.64.0.0/16
aws ec2 create-subnet --vpc-id vpc-0abc --cidr-block 100.64.0.0/19 --availability-zone eu-west-1a
Then enable custom networking and create an ENIConfig per AZ:
kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true \
ENI_CONFIG_LABEL_DEF=topology.kubernetes.io/zone
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata:
name: eu-west-1a
spec:
subnet: subnet-0pods1a
securityGroups: [sg-0nodes]
New nodes then place pods in the 100.64.x.x subnets. Replace nodes in a rolling way to move existing ones.
5. Verify
kubectl get pods -A --field-selector=status.phase=Pending
kubectl get nodes -o custom-columns=NAME:.metadata.name,PODS:.status.allocatable.pods
Prevent it next time
- Size pod subnets for peak pods plus warm IPs (plan /19 or larger per AZ for busy clusters).
- Turn on prefix delegation for new clusters on Nitro instances.
- Alarm when
AvailableIpAddressCountin cluster subnets drops below a threshold. - Keep the VPC CNI add-on on a supported version and managed as an EKS add-on.