Ongoing service disruptions
For the most recent update on ongoing service disruptions affecting the AWS Middle East (UAE) Region (ME-CENTRAL-1), refer to the AWS Health Dashboard. For information on AWS Service migration, see How do I migrate my services to another region?
Analyzing IP Address Allocation for EKS Auto Mode Clusters
This article presents a Python script that helps you analyze IP address utilization across worker nodes and pods in your Amazon EKS cluster, enabling you to proactively manage subnet capacity and prevent IP exhaustion.
Understanding EKS Auto Mode Networking
EKS Auto Mode extends AWS management beyond the Kubernetes control plane to fully automate cluster infrastructure—including compute, networking, storage, and security. Unlike traditional EKS deployments where you configure Managed Node Groups or Auto Scaling groups, EKS Auto Mode handles day-to-day operations and infrastructure decisions on your behalf.
A key architectural change in EKS Auto Mode is how pod IP addresses are assigned. Rather than using the AWS VPC CNI plugin, EKS Auto Mode manages pod IP assignment directly. While this removes the operational burden of CNI configuration, you remain responsible for ensuring your subnets have sufficient IP address capacity for your workloads.
IP Address Allocation Mechanics
By default, EKS Auto Mode assigns a /28 IPv4 prefix to the primary Elastic Network Interface (ENI) of each worker node, drawn from the subnet's CIDR range where the node resides. This allocation provides up to 16 IP addresses per prefix, subject to the instance type's pod density limits. When a node exhausts its initial prefix allocation, EKS Auto Mode dynamically assigns additional /28 prefixes, provided the subnet has available capacity.
Planning for Scale
As your cluster scales, the original subnet allocation may become insufficient for pod IP requirements. Consider these architectural patterns to prevent IP exhaustion:
- Secondary CIDR ranges: Dedicate separate CIDR blocks exclusively for pod networking
- Subnet segmentation: Separate subnets for worker nodes and pod IP allocation
Solution Overview
The provided Python script analyzes IP address capacity across your EKS cluster by:
- Retrieving node and pod information from the Kubernetes API
- Correlating Kubernetes resources with EC2 instance metadata
- Examining ENI configurations and IPv4 prefix assignments
- Calculating per-ENI IP utilization—distinguishing between addresses actively assigned to pods and addresses available for future scheduling
- Generating a detailed breakdown of IP consumption per node and ENI
This visibility enables you to identify capacity constraints before they impact workload deployment and make informed decisions about subnet expansion or architectural adjustments.
Pre-requisites
- Python 3.12 or above installed locally. The latest boto3 package is recommended
- Make sure the IAM role/IAM user configured locally has an entry on the EKS access policy that includes enough permissions to read pods and nodes.
- The minimal permissions for the API calls to the AWS services are the following:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeSubnets"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "eks:DescribeCluster",
"Resource": "*"
}
]
}
The script
import boto3
import ipaddress
import subprocess
import json
import sys
def get_subnet_cidr(ec2, subnet_id):
return ec2.describe_subnets(SubnetIds=[subnet_id])['Subnets'][0]['CidrBlock']
def main():
if len(sys.argv) != 3:
print("Usage: check_eni_capacity.py <cluster-name> <region>")
sys.exit(1)
cluster_name = sys.argv[1]
region = sys.argv[2]
ec2 = boto3.client('ec2', region_name=region)
# Update kubeconfig
subprocess.run(['aws', 'eks', 'update-kubeconfig', '--name', cluster_name, '--region', region], check=True)
# Get nodes from Kubernetes
result = subprocess.run(['kubectl', 'get', 'nodes', '-o', 'json'], capture_output=True, text=True, check=True)
nodes = json.loads(result.stdout)
# Get pods from Kubernetes
result = subprocess.run(['kubectl', 'get', 'pods', '-A', '-o', 'json'], capture_output=True, text=True, check=True)
pods = json.loads(result.stdout)
# Map pod IPs to nodes
pod_ips_by_node = {}
for pod in pods['items']:
node_name = pod['spec'].get('nodeName')
pod_ip = pod['status'].get('podIP')
if node_name and pod_ip:
pod_ips_by_node.setdefault(node_name, []).append(pod_ip)
# Extract instance IDs from nodes
instance_ids = []
node_to_instance = {}
for node in nodes['items']:
node_name = node['metadata']['name']
provider_id = node['spec'].get('providerID', '')
if provider_id.startswith('aws:///'):
instance_id = provider_id.split('/')[-1]
instance_ids.append(instance_id)
node_to_instance[instance_id] = node_name
if not instance_ids:
print("No nodes found in cluster")
return
# Get instance details
instances = ec2.describe_instances(InstanceIds=instance_ids)
for reservation in instances['Reservations']:
for instance in reservation['Instances']:
instance_id = instance['InstanceId']
node_name = node_to_instance.get(instance_id, 'unknown')
pod_ips = pod_ips_by_node.get(node_name, [])
print(f"\n{'='*80}")
print(f"Instance: {instance_id} (Node: {node_name})")
print(f"{'='*80}")
for eni in instance['NetworkInterfaces']:
eni_id = eni['NetworkInterfaceId']
primary_ip = eni['PrivateIpAddress']
subnet_id = eni['SubnetId']
subnet_cidr = get_subnet_cidr(ec2, subnet_id)
print(f"\nENI: {eni_id}")
print(f" Primary IP: {primary_ip}")
print(f" Subnet: {subnet_id} ({subnet_cidr})")
# Check for IPv4 prefixes
if 'Ipv4Prefixes' in eni and eni['Ipv4Prefixes']:
total_ips_all = 0
assigned_all = 0
all_pod_ips = []
for prefix_info in eni['Ipv4Prefixes']:
prefix = prefix_info['Ipv4Prefix']
prefix_net = ipaddress.ip_network(prefix)
total_ips = prefix_net.num_addresses
# Count pod IPs in this prefix
pod_ips_in_prefix = sorted([ip for ip in pod_ips if ipaddress.ip_address(ip) in prefix_net],
key=lambda ip: ipaddress.ip_address(ip))
assigned = len(pod_ips_in_prefix)
total_ips_all += total_ips
assigned_all += assigned
all_pod_ips.extend(pod_ips_in_prefix)
available = total_ips - assigned
print(f" Prefix: {prefix}")
print(f" Total IPs in prefix: {total_ips}")
print(f" Assigned IPs (pods): {assigned}")
if pod_ips_in_prefix:
print(f" Pod IPs: {', '.join(pod_ips_in_prefix)}")
print(f" Available for pods: {available}")
# Show aggregated totals if multiple prefixes
if len(eni['Ipv4Prefixes']) > 1:
available_all = total_ips_all - assigned_all
print(f" --- ENI Totals ({len(eni['Ipv4Prefixes'])} prefixes) ---")
print(f" Total IPs: {total_ips_all}")
print(f" Assigned IPs (pods): {assigned_all}")
if all_pod_ips:
print(f" All Pod IPs: {', '.join(all_pod_ips)}")
print(f" Available for pods: {available_all}")
else:
print(f" No IPv4 prefixes assigned")
if __name__ == "__main__":
main()
Sample outputs
For the default EKS Auto Mode, the following sample output can be expected:
================================================================================
Instance: i-aaaaaaaaaaaaaaaaa (Node: i-aaaaaaaaaaaaaaaaa)
================================================================================
ENI: eni-aaaaaaaaaaaaaaaaa
Primary IP: 192.168.183.45
Subnet: subnet-aaaaaaaaaaaaaaaaa (192.168.160.0/19)
Prefix: 192.168.181.240/28
Total IPs in prefix: 16
Assigned IPs (pods): 1
Pod IPs: 192.168.181.240
Available for pods: 15
================================================================================
Instance: i-bbbbbbbbbbbbbbbbb (Node: i-bbbbbbbbbbbbbbbbb)
================================================================================
ENI: eni-bbbbbbbbbbbbbbbbb
Primary IP: 192.168.81.38
Subnet: subnet-bbbbbbbbbbbbbbbbb (192.168.64.0/19)
Prefix: 192.168.71.80/28
Total IPs in prefix: 16
Assigned IPs (pods): 3
Pod IPs: 192.168.71.80, 192.168.71.81, 192.168.71.82
Available for pods: 13
================================================================================
Instance: i-ccccccccccccccccc (Node: i-ccccccccccccccccc)
================================================================================
ENI: eni-ccccccccccccccccc
Primary IP: 192.168.71.108
Subnet: subnet-bbbbbbbbbbbbbbbbb (192.168.64.0/19)
Prefix: 192.168.85.160/28
Total IPs in prefix: 16
Assigned IPs (pods): 1
Pod IPs: 192.168.85.161
Available for pods: 15
Now, for the scenario where there is a separation of subnets (one for the main ENI of the worker node and another for the pods), the output looks similar to this:
================================================================================
Instance: i-aaaaaaaaaaaaaaaaa (Node: i-aaaaaaaaaaaaaaaaa)
================================================================================
ENI: eni-bbbbbbbbbbbbbbbbb
Primary IP: 100.64.51.12
Subnet: subnet-bbbbbbbbbbbbbbbbb (100.64.32.0/19)
Prefix: 100.64.43.16/28
Total IPs in prefix: 16
Assigned IPs (pods): 3
Pod IPs: 100.64.43.16, 100.64.43.17, 100.64.43.18
Available for pods: 13
ENI: eni-aaaaaaaaaaaaaaaaa
Primary IP: 192.168.137.198
Subnet: subnet-aaaaaaaaaaaaaaaaa (192.168.128.0/19)
No IPv4 prefixes assigned
================================================================================
Instance: i-0e8217b21d04ed516 (Node: i-0e8217b21d04ed516)
================================================================================
ENI: eni-ccccccccccccccccc
Primary IP: 192.168.177.125
Subnet: subnet-0e614ee078068825e (192.168.160.0/19)
No IPv4 prefixes assigned
ENI: eni-ddddddddddddddddd
Primary IP: 100.64.70.70
Subnet: subnet-bbbbbbbbbbbbbbbbb (100.64.64.0/19)
Prefix: 100.64.72.176/28
Total IPs in prefix: 16
Assigned IPs (pods): 2
Pod IPs: 100.64.72.176, 100.64.72.177
Available for pods: 14
References:
- Language
- English
Relevant content
- asked 3 years ago
- asked 9 months ago
AWS OFFICIALUpdated 6 months ago