The Container Network Interface (CNI) is the plugin specification that defines how networking is set up for Pods. The kubelet doesn't know how to configure networking — it delegates entirely to a CNI plugin. Understanding CNI is essential for troubleshooting node-level networking issues and making architectural decisions about cluster networking.
1. The CNI Specification
CNI is deliberately minimal — a spec for executing a binary that sets up (or tears down) networking for a container. The kubelet calls CNI at two points in a Pod's life:
| Operation | When | What the Plugin Does |
|---|---|---|
ADD | Pod sandbox created | Assign IP, create veth pair, configure routes, attach to bridge/overlay |
DEL | Pod sandbox destroyed | Release IP, remove network interface, clean up routes |
CHECK | Periodic health check | Verify network setup is still correct |
How It's Invoked
File Locations on a Node
# CNI plugin binaries: ls /opt/cni/bin/ # bridge calico flannel host-local loopback portmap ... # CNI configuration (kubelet reads first file alphabetically): ls /etc/cni/net.d/ # 10-calico.conflist (or 10-flannel.conflist, etc.)
CNI Config Example
# /etc/cni/net.d/10-calico.conflist
{
"name": "k8s-pod-network",
"cniVersion": "1.0.0",
"plugins": [
{
"type": "calico", // Plugin binary name
"ipam": {
"type": "calico-ipam" // IP Address Management plugin
},
"policy": {
"type": "k8s"
}
},
{
"type": "portmap", // Chained plugin: port mapping for hostPort
"capabilities": {"portMappings": true}
},
{
"type": "bandwidth", // Chained plugin: traffic shaping
"capabilities": {"bandwidth": true}
}
]
}
conflist runs multiple plugins in sequence. The first (main) plugin creates the network. Additional plugins add features (port mapping, bandwidth limits, IPAM). Each plugin gets the previous one's output as input.
IPAM — IP Address Management
| IPAM Plugin | How It Assigns IPs | Used By |
|---|---|---|
host-local | Allocates from a local range per node (file-based state) | Flannel, bridge |
calico-ipam | Allocates from Calico IP pools (etcd/datastore-backed) | Calico |
aws-cni | Allocates real VPC IPs via ENI (Elastic Network Interface) | AWS VPC CNI |
whereabouts | Cluster-wide IP allocation (for multi-network) | Multus |
2. CNI Plugins: Deep Comparison
Flannel — Simple Overlay
| Aspect | Detail |
|---|---|
| Data plane | VXLAN overlay (encapsulates L2 in UDP) |
| NetworkPolicy | ❌ None (must add Calico for policy) |
| Routing | Each node gets a /24 from cluster CIDR |
| Performance | Moderate (VXLAN overhead ~50 bytes/packet) |
| Best for | Learning, simple clusters, when you don't need policies |
| Install | kubectl apply -f kube-flannel.yml (one-liner DaemonSet) |
Calico — Flexible & Enterprise-Ready
| Aspect | Detail |
|---|---|
| Data plane | Linux routing (BGP for cross-node) OR VXLAN OR eBPF |
| NetworkPolicy | ✅ Full K8s NetworkPolicy + Calico-specific extensions (L7, DNS) |
| Routing | BGP peering between nodes (no encap overhead) or VXLAN fallback |
| Performance | Excellent with BGP (native routing); good with VXLAN |
| Advanced features | Global NetworkPolicies, DNS-based policies, WireGuard encryption, Egress gateways |
| Best for | Bare metal, hybrid cloud, enterprise (most deployed CNI) |
# Calico in BGP mode — no overlay, native routing: # Node 1 announces: "10.244.1.0/24 via 192.168.1.10" # Node 2 announces: "10.244.2.0/24 via 192.168.1.11" # Traffic flows directly via learned routes — no encapsulation
Cilium — eBPF-Powered
| Aspect | Detail |
|---|---|
| Data plane | eBPF (bypasses iptables entirely) |
| NetworkPolicy | ✅ K8s NetworkPolicy + L7 policies (HTTP, gRPC, Kafka, DNS) |
| Routing | Native routing, VXLAN/Geneve overlay, or DSR (Direct Server Return) |
| Performance | Best (eBPF: kernel-native, no iptables chain traversal) |
| Can replace kube-proxy | ✅ Yes (eBPF-based service load balancing) |
| Advanced features | L7 visibility (HTTP metrics per path), Hubble observability, transparent encryption, service mesh, Gateway API |
| Best for | High-performance clusters, observability-focused, platform engineering |
Head-to-Head Comparison
| Feature | Flannel | Calico | Cilium |
|---|---|---|---|
| K8s NetworkPolicy | ❌ | ✅ | ✅ |
| L7 Policy (HTTP) | ❌ | ⚠️ Extension | ✅ Native |
| eBPF dataplane | ❌ | ✅ (optional) | ✅ (primary) |
| Replace kube-proxy | ❌ | ✅ (eBPF mode) | ✅ |
| Encryption (WireGuard) | ❌ | ✅ | ✅ |
| Multi-cluster | ❌ | ✅ (Federation) | ✅ (Cluster Mesh) |
| Observability | Basic | Flow logs | ✅ Hubble (L3-L7 flow viz) |
| Complexity | Low | Medium | Medium-High |
| Resource usage | Low | Medium | Medium (eBPF is efficient at runtime) |
3. eBPF — The Modern Data Plane
eBPF (extended Berkeley Packet Filter) lets you run sandboxed programs inside the Linux kernel without modifying kernel source or loading kernel modules. For networking, this means:
Traditional (iptables) vs eBPF
What eBPF Enables in Cilium
| Feature | How eBPF Does It |
|---|---|
| Service load balancing | eBPF map of ServiceIP → Pod IPs. Replaces kube-proxy entirely. |
| NetworkPolicy | eBPF programs attached to Pod's veth enforce allow/deny per-packet |
| L7 visibility | eBPF parses HTTP/gRPC headers in-kernel — no proxy sidecar needed |
| Transparent encryption | eBPF triggers WireGuard/IPsec encryption at the interface level |
| Observability (Hubble) | eBPF exports flow events (src, dst, protocol, HTTP status) in real-time |
# Cilium without kube-proxy: # Install Cilium with kube-proxy replacement: helm install cilium cilium/cilium \ --set kubeProxyReplacement=true \ --set k8sServiceHost=API_SERVER_IP \ --set k8sServicePort=6443 # Verify kube-proxy is not needed: kubectl -n kube-system delete ds kube-proxy # Cilium handles all Service routing via eBPF
4. CNI Troubleshooting
Common Issues
| Symptom | Likely Cause | Debug Command |
|---|---|---|
Pod stuck in ContainerCreating | CNI plugin failed to assign IP or create interface | kubectl describe pod → events; journalctl -u kubelet |
| Pods can't communicate cross-node | Overlay not working or routes missing | kubectl debug node/ -- ip route; CNI DaemonSet logs |
| All new Pods fail networking | CNI binary missing or config file absent | ls /opt/cni/bin/; ls /etc/cni/net.d/ |
| IP exhaustion | Node's Pod CIDR full | kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' |
| NetworkPolicies not enforced | CNI doesn't support policies (Flannel) | Check CNI docs for policy support |
Key Debug Commands
# Check CNI DaemonSet health: kubectl get pods -n kube-system -l k8s-app=calico-node # Calico kubectl get pods -n kube-system -l app.kubernetes.io/name=cilium # Cilium # CNI plugin logs: kubectl logs -n kube-system -l k8s-app=calico-node --tail=50 kubectl logs -n kube-system -l app.kubernetes.io/name=cilium --tail=50 # Cilium-specific: kubectl exec -n kube-system cilium-xxxxx -- cilium status kubectl exec -n kube-system cilium-xxxxx -- cilium endpoint list # Check node networking: kubectl debug node/worker-1 -it --image=nicolaka/netshoot -- bash # Inside: ip route, ip link, bridge fdb, iptables-save # Calico-specific: kubectl exec -n kube-system calico-node-xxxxx -- calicoctl node status kubectl exec -n kube-system calico-node-xxxxx -- calicoctl get ippool
ContainerCreating with "network not ready" events, check: (1) Is the CNI DaemonSet running? (2) Are CNI binaries present at /opt/cni/bin/? (3) Is the config at /etc/cni/net.d/? A missing or misconfigured CNI is a common exam scenario.
5. Choosing a CNI for Your Cluster
# Decision tree:
Need NetworkPolicy?
├─ NO → Flannel (simplest, lowest resource)
└─ YES → Need L7 policies or replace kube-proxy?
├─ NO → Calico (battle-tested, BGP or VXLAN)
└─ YES → Cilium (eBPF, L7 visibility, kube-proxy replacement)
Running on AWS EKS?
└─ Default: AWS VPC CNI (native VPC IPs, best integration)
└─ Add Calico for NetworkPolicy enforcement
Running on GKE?
└─ Default: GKE Dataplane v2 (Cilium-based, eBPF)
Bare metal / on-prem?
└─ Calico with BGP (native routing, no overlay overhead)
or Cilium (eBPF + Hubble observability)
Summary
| Concept | Key Point |
|---|---|
| CNI spec | Simple binary interface: ADD (create network), DEL (remove), CHECK (verify) |
| File locations | Binaries: /opt/cni/bin/, Config: /etc/cni/net.d/ |
| Plugin chaining | conflist runs plugins in sequence (main + IPAM + extras) |
| Flannel | VXLAN overlay, simple, no NetworkPolicy |
| Calico | BGP routing (or VXLAN), full NetworkPolicy, enterprise-grade |
| Cilium | eBPF dataplane, L7 policy, replaces kube-proxy, Hubble observability |
| eBPF | O(1) kernel-native processing, bypasses iptables, programmable |
| IPAM | Handles IP allocation: host-local, calico-ipam, VPC ENI |
📝 Quiz: CNI Fundamentals
Q1: A newly joined node's Pods are all stuck in ContainerCreating. What CNI-related things do you check first?
kubectl get pods -n kube-system -o wide | grep <node>). (2) Are CNI binaries present? (ls /opt/cni/bin/). (3) Is the config file present? (ls /etc/cni/net.d/). (4) Check kubelet logs for CNI errors: journalctl -u kubelet | grep cni.Q2: What's the advantage of Calico's BGP mode over VXLAN overlay?
Q3: Why can Cilium replace kube-proxy but Flannel cannot?
Q4: What is IPAM and why does it matter?
host-local is simple (per-node range), calico-ipam is cluster-aware, and aws-cni allocates real VPC IPs.Q5: How does eBPF improve over iptables for NetworkPolicy enforcement?
Q6: You're on AWS EKS. Pods are getting IPs from the VPC subnet (172.31.x.x) instead of a Pod CIDR (10.244.x.x). Which CNI is being used and why?