Subnetting and IP Addressing - Street-Level Ops¶
What experienced network operators know from years of CIDR planning, VPC buildouts, and 2 AM "why can't pod A reach service B" incidents.
VPC / Cloud Subnet Planning¶
The Standard Pattern: /16 VPC, /24 Subnets¶
VPC: 10.100.0.0/16 (65,534 usable IPs)
Public subnets (one per AZ):
10.100.0.0/24 (us-east-1a) — 254 hosts
10.100.1.0/24 (us-east-1b) — 254 hosts
10.100.2.0/24 (us-east-1c) — 254 hosts
Private subnets (one per AZ):
10.100.10.0/24 (us-east-1a) — 254 hosts
10.100.11.0/24 (us-east-1b) — 254 hosts
10.100.12.0/24 (us-east-1c) — 254 hosts
Database subnets:
10.100.20.0/24 (us-east-1a) — 254 hosts
10.100.21.0/24 (us-east-1b) — 254 hosts
Spare: 10.100.30.0/24 - 10.100.255.0/24
AWS gotcha: AWS reserves 5 IPs per subnet (first four + last). A /24 gives you 251, not 254.
Remember: AWS reserved IPs per subnet: .0 (network), .1 (VPC router), .2 (DNS), .3 (future use), .255 (broadcast). Mnemonic: N-R-D-F-B — Network, Router, DNS, Future, Broadcast.
Multi-VPC Strategy¶
Production: 10.100.0.0/16
Staging: 10.101.0.0/16
Development: 10.102.0.0/16
Shared Svcs: 10.103.0.0/16
Keep them in the same /8 but different /16 blocks. Makes VPC peering and transit gateway routes clean.
K8s CIDR Planning¶
The Three CIDRs That Must Not Overlap¶
Node CIDR: 10.100.0.0/16 (VPC — where nodes live)
Pod CIDR: 10.244.0.0/16 (overlay — where pods get IPs)
Service CIDR: 10.96.0.0/12 (virtual — ClusterIP range)
Check for overlap:
# If you can AND two network addresses with the wider mask
# and get the same result, they overlap.
# Quick check — does 10.96.0.0/12 overlap 10.100.0.0/16?
ipcalc 10.96.0.0/12
# Network: 10.96.0.0, HostMax: 10.111.255.254
# 10.100.x.x falls inside 10.96-10.111 range — OVERLAP!
# Fix: use a different service CIDR
# Service CIDR: 10.200.0.0/16
Pod CIDR Sizing¶
Each node gets a /24 slice of the pod CIDR by default (256 pod IPs per node).
If you need more nodes, widen the pod CIDR or use --node-cidr-mask-size to give each node a smaller slice (e.g., /25 = 128 pods per node).
Gotcha: Changing the pod CIDR or service CIDR on a running cluster is effectively impossible without rebuilding. Plan these ranges generously at cluster creation time. Doubling back later requires a cluster migration.
Docker Bridge Conflicts¶
The Problem¶
Docker's default bridge is 172.17.0.0/16. Custom docker networks allocate from 172.18.0.0/16, 172.19.0.0/16, etc. Corporate networks often use 172.16-31.x ranges internally.
# See what Docker has allocated
docker network ls
docker network inspect bridge | jq '.[0].IPAM.Config'
# See all docker networks and their subnets
docker network inspect $(docker network ls -q) | jq '.[] | {Name: .Name, Subnet: .IPAM.Config[0].Subnet}'
Fix: Override Docker's Default Pool¶
# /etc/docker/daemon.json
{
"default-address-pools": [
{"base": "10.200.0.0/16", "size": 24}
],
"bip": "10.200.0.1/24"
}
Restart Docker after changing. This moves all Docker networks into 10.200.x.x space.
War story: A company's VPN stopped working after a developer ran
docker-compose upon their laptop. Docker allocated172.18.0.0/16for a bridge network, which shadowed the corporate172.18.0.0/16subnet. All traffic to the office network went to Docker instead. Fix: always setdefault-address-poolsindaemon.jsonon developer machines.
Calculating "How Many IPs Do I Have Left"¶
# AWS: check available IPs in a subnet
aws ec2 describe-subnets --subnet-ids subnet-abc123 \
--query 'Subnets[0].AvailableIpAddressCount'
# On a Linux box: count IPs in use on a /24
ip neigh show dev eth0 | wc -l
# Calculate total vs used
# /24 = 254 usable. If 200 are in use, you have 54 left.
# /25 = 126 usable. If 100 are in use, you have 26 left.
# ipcalc gives you the total
ipcalc 10.0.1.0/24 | grep Hosts
# Hosts/Net: 254
When to Worry¶
- Below 20% free: plan expansion
- Below 10% free: you are one autoscale event from exhaustion
- IP exhaustion in K8s pod CIDR: pods stuck in
ContainerCreating, events show "no available IPs"
IPv6 in Practice¶
Where You Actually Encounter It¶
- Dual-stack K8s clusters: Pods get both IPv4 and IPv6 addresses. Services can be dual-stack.
- AWS/GCP/Azure VPCs: Dual-stack is increasingly the default. EC2 instances get both.
- Load balancers: ALBs and NLBs serve on both stacks.
- Internal services: Most still speak IPv4. IPv6 is primarily for external-facing paths.
Dual-Stack K8s Quick Checks¶
# Verify cluster has dual-stack
kubectl get nodes -o wide
# Look for both IPv4 and IPv6 addresses
# Check a pod's addresses
kubectl get pod mypod -o jsonpath='{.status.podIPs}'
# [{"ip":"10.244.1.5"},{"ip":"fd00::5"}]
# Check service type
kubectl get svc mysvc -o jsonpath='{.spec.ipFamilyPolicy}'
# SingleStack, PreferDualStack, or RequireDualStack
IPv6 One-Liners¶
# Show all IPv6 addresses
ip -6 addr show
# Show IPv6 routes
ip -6 route show
# Test IPv6 connectivity (link-local requires interface)
ping -6 fe80::1%eth0
# Test global IPv6
ping -6 2001:db8::1
# DNS lookup for AAAA records
dig AAAA example.com +short
# curl over IPv6
curl -6 http://[2001:db8::1]:8080/health
Common Subnet One-Liners¶
# What subnet is this IP in?
ipcalc 10.0.1.137/24
# Split a /24 into four /26 subnets
ipcalc 10.0.1.0/24 -s 62 62 62 62
# List all /24 subnets in a /16
for i in $(seq 0 255); do echo "10.0.$i.0/24"; done
# Check if two CIDRs overlap (python one-liner)
python3 -c "
import ipaddress
a = ipaddress.ip_network('10.0.0.0/16')
b = ipaddress.ip_network('10.0.1.0/24')
print(a.overlaps(b)) # True — b is inside a
"
# Find which of your subnets an IP belongs to
python3 -c "
import ipaddress
ip = ipaddress.ip_address('10.0.1.137')
for cidr in ['10.0.0.0/24','10.0.1.0/24','10.0.2.0/24']:
if ip in ipaddress.ip_network(cidr):
print(f'{ip} is in {cidr}')
"
Troubleshooting IP Conflicts and Overlapping Ranges¶
Symptom: Intermittent Connectivity, ARP Flapping¶
Two hosts with the same IP. ARP table flips between MACs.
# Check for duplicate IPs
arping -D -I eth0 10.0.1.50
# If you get a reply, someone else has that IP
# Watch ARP table for flapping
ip neigh show | grep 10.0.1.50
# If the MAC keeps changing, you have a duplicate
Symptom: Route Goes to Wrong Destination¶
Overlapping CIDRs in the routing table. More specific route wins.
# Show routing table — look for overlapping entries
ip route show | sort -t/ -k2 -n
# Check which route a specific IP matches
ip route get 10.0.1.50
Symptom: VPN Tunnel Up But No Traffic¶
Local subnet and remote subnet overlap. Traffic stays local instead of going through the tunnel.
# Compare local subnets vs VPN remote subnets
ip route show # local routes
ip route show table 220 # strongSwan routes (or check your VPN's table)
# If both show 10.0.1.0/24, traffic for 10.0.1.x stays local
# Fix: re-IP one side or use NAT translation
One-liner: To quickly check CIDR math in your head: each bit in the prefix doubles the number of subnets and halves the hosts. /24 = 254 hosts, /25 = 126, /26 = 62, /27 = 30, /28 = 14.