Kubernetes imposes a flat network model with specific guarantees. Understanding these guarantees — and how they're implemented — is essential for debugging connectivity issues, designing NetworkPolicies, and choosing CNI plugins.
1. The Three Guarantees
Kubernetes requires that any networking implementation (CNI plugin) satisfies these rules:
| # | Guarantee | Meaning |
|---|---|---|
| 1 | Every Pod gets its own IP | No sharing. Containers within a Pod share an IP (via pause container), but each Pod has a unique cluster-wide IP. |
| 2 | Pods can communicate with all other Pods without NAT | Pod A on Node 1 can reach Pod B on Node 2 using Pod B's IP directly. No port mapping. No translation. |
| 3 | Agents on a node can communicate with all Pods on that node | kubelet, kube-proxy, and node-local daemons can reach Pods directly. |
What This Looks Like in Practice
# Pod on node-1: kubectl exec pod-a -- ip addr # eth0: 10.244.1.5/24 ← Pod IP (unique across cluster) # Pod on node-2: kubectl exec pod-b -- ip addr # eth0: 10.244.2.8/24 ← Different subnet, same flat network # Connectivity test (no NAT, no port mapping): kubectl exec pod-a -- curl 10.244.2.8:8080 # Works directly — flat network
Network Planes in a Cluster
| Network | CIDR Example | Configured By | Routable? |
|---|---|---|---|
| Node network | 192.168.1.0/24 | Infrastructure (cloud VPC, bare metal) | ✅ Physical |
| Pod network (Cluster CIDR) | 10.244.0.0/16 | CNI plugin (--cluster-cidr) | ✅ Virtual overlay or routed |
| Service network (Service CIDR) | 10.96.0.0/12 | API server (--service-cluster-ip-range) | ❌ Virtual only (kernel rules) |
2. Pod-to-Pod Communication
Same Node
On the same node, Pods are connected via a virtual bridge (typically cbr0 or cni0). Each Pod has a veth pair — one end in the Pod's network namespace, the other plugged into the bridge.
Traffic between Pod A and Pod B stays on the bridge — no routing needed, just L2 switching.
Cross-Node
When Pod A (node-1) talks to Pod D (node-2), the packet must leave node-1 and reach node-2. CNI plugins solve this differently:
| Approach | How | Used By |
|---|---|---|
| Overlay (VXLAN/Geneve) | Encapsulate Pod packet inside a UDP packet between nodes | Flannel (VXLAN), Calico (optional), Cilium (optional) |
| Direct routing | Configure host routes so Pod CIDRs are routable via node IPs | Calico (BGP), Cilium (native routing) |
| Cloud routes | Cloud VPC route tables know which node owns which Pod CIDR | GKE, EKS VPC CNI, Azure CNI |
3. IP Allocation
How Pods Get IPs
- The cluster has a Cluster CIDR (e.g.,
10.244.0.0/16) - Each node gets a Pod CIDR subset (e.g., node-1 gets
10.244.1.0/24= 254 Pods max) - When a Pod is created, the CNI plugin assigns the next available IP from the node's Pod CIDR
- The IP is released when the Pod is deleted
# See node Pod CIDR: kubectl get nodes -o custom-columns=\ NAME:.metadata.name,\ POD-CIDR:.spec.podCIDR # NAME POD-CIDR # worker-1 10.244.1.0/24 # worker-2 10.244.2.0/24 # worker-3 10.244.3.0/24
IP Exhaustion
If a node's Pod CIDR is /24, it can host at most 254 Pods (minus a few reserved). Larger nodes (or many short-lived Pods) can exhaust this. Solutions:
- Use a larger per-node CIDR (
--node-cidr-mask-size=23= 510 IPs) - Use a CNI that doesn't subdivide (EKS VPC CNI allocates from the entire VPC subnet)
4. CNI — Container Network Interface
CNI is a specification for how network plugins are invoked. The kubelet calls CNI plugins at two points:
- ADD — when a Pod sandbox is created (assign IP, set up veth, configure routes)
- DEL — when a Pod sandbox is removed (release IP, remove interfaces)
Popular CNI Plugins
| Plugin | Approach | NetworkPolicy? | Best For |
|---|---|---|---|
| Flannel | VXLAN overlay | ❌ No | Simple clusters, learning |
| Calico | BGP routing or VXLAN | ✅ Yes | Bare metal, hybrid, enterprise |
| Cilium | eBPF (no iptables) | ✅ Yes (L3-L7) | High performance, observability, service mesh |
| AWS VPC CNI | VPC-native IPs | ✅ Yes (via Calico add-on) | EKS |
| Azure CNI | VNet-native IPs | ✅ Yes | AKS |
| Weave Net | Mesh overlay | ✅ Yes | Multi-cloud (less popular now) |
5. Communication Types
| Communication | Mechanism | Example |
|---|---|---|
| Container → Container (same Pod) | localhost | App talks to sidecar on localhost:9090 |
| Pod → Pod (same node) | Bridge (L2 switch) | Direct by Pod IP |
| Pod → Pod (cross-node) | CNI (overlay/route) | Direct by Pod IP — no NAT |
| Pod → Service | kube-proxy (iptables/IPVS/eBPF) | ClusterIP → DNAT to Pod IP |
| External → Pod | NodePort / LoadBalancer / Ingress | Client → LB → Service → Pod |
| Pod → External | SNAT (node IP) by default | Pod makes API call to external service |
Egress: SNAT Behavior
When a Pod connects to an external service (outside the cluster), the node SNATs the Pod IP to the node's IP. The external service sees the node IP as the source, not the Pod IP.
# Pod 10.244.1.5 on node 192.168.1.10 connects to external API: # Source: 10.244.1.5 → SNAT → 192.168.1.10 # External API sees source: 192.168.1.10 # Exception: some CNIs (Calico, Cilium) can disable SNAT # for specific destinations or when using direct routing
6. Debugging Network Issues
# Check Pod IP: kubectl get pod -o wide # Test connectivity from inside a Pod: kubectl exec debug-pod -- curl -v http://10.244.2.8:8080 kubectl exec debug-pod -- nslookup my-service.default.svc.cluster.local kubectl exec debug-pod -- wget -qO- --timeout=2 http://my-service:80 # Deploy a network debug Pod: kubectl run netshoot --image=nicolaka/netshoot -it --rm -- bash # (has curl, dig, nslookup, tcpdump, iperf, ss, ip, traceroute) # Check node routing: kubectl debug node/worker-1 -it --image=ubuntu -- ip route # Check CNI logs: kubectl logs -n kube-system -l app=calico-node --tail=50 # Check kube-proxy rules (iptables mode): kubectl debug node/worker-1 -it --image=ubuntu -- iptables-save | grep my-service
kubectl get pod -o wide to see IPs and nodes. (2) Use kubectl exec to test connectivity from inside a Pod. (3) Check DNS resolution with nslookup. (4) The nicolaka/netshoot image is your best friend for debugging — it has every network tool.
Summary
| Concept | Key Point |
|---|---|
| Flat network | Every Pod gets a unique IP; all Pods can reach all Pods without NAT |
| Three networks | Node (physical), Pod (Cluster CIDR), Service (Service CIDR) |
| Same-node | veth pairs connected to a bridge — L2 switching |
| Cross-node | Overlay (VXLAN) or direct routing (BGP/cloud routes) |
| IP allocation | Cluster CIDR → per-node Pod CIDR → individual Pod IP via CNI |
| CNI | Plugin spec (ADD/DEL); determines overlay vs routing, policy support |
| Egress SNAT | Pod → external: source NAT to node IP (external sees node IP) |
| Service IPs | Virtual — exist only in kernel rules, not routable |
📝 Quiz: The Kubernetes Network Model
Q1: Pod A (10.244.1.5) on node-1 connects to Pod B (10.244.2.8) on node-2. What source IP does Pod B see?
Q2: Pod A connects to an external API at 8.8.8.8. What source IP does the external API see?
Q3: What's the difference between an overlay network and direct routing for cross-node Pod traffic?
Direct routing: Programs routes in the network (BGP or cloud VPC route tables) so Pod CIDRs are natively routable. No encapsulation overhead, better performance, but requires infrastructure support.
Q4: A node has Pod CIDR 10.244.1.0/24. How many Pods can it host (maximum)?
--max-pods setting (default 110) which may limit it further. The IP space allows 254, but kubelet defaults may cap it lower.Q5: You chose Flannel as your CNI plugin. Can you use NetworkPolicies?
Q6: Service IPs (ClusterIPs) like 10.96.0.1 — can you ping them?