CNI (Container Network Interface)
The CNCF standard that Kubernetes uses for pod networking
What Is CNI?
CNI (Container Network Interface) is a CNCF specification that defines a standard interface between container runtimes and network plugins. It answers one question: how does a container get a network interface, an IP address, and connectivity?
Key Design Principles
- Runtime-agnostic — not Docker-specific. Kubernetes, containerd, CRI-O, rkt, Podman all use CNI
- Plugin-based — network setup is delegated to external executables (binaries in
/opt/cni/bin/) - Composable — multiple plugins can be chained together (network + IPAM + portmap + bandwidth)
- Minimal contract — only 4 operations: ADD, DEL, CHECK, VERSION
- Configuration-driven — JSON config files in
/etc/cni/net.d/
Note
Note: Kubernetes deliberately chose CNI over Docker's native networking (libnetwork/CNM) because CNI's simpler, plugin-first design aligned better with K8s's philosophy of composable, swappable components.
The Plugin Contract
CNI plugins are ordinary executables. The container runtime invokes them with environment variables and a JSON config on stdin. The plugin performs its work and returns a JSON result on stdout.
| Operation | Action | When Called |
|---|---|---|
ADD |
Create interface, assign IP, setup routes, configure DNS | Pod sandbox creation (new network namespace is ready) |
DEL |
Tear down interface, release IP, clean up routes | Pod termination (before namespace is destroyed) |
CHECK |
Verify networking is still correctly configured | Periodic health checks (optional, runtime-dependent) |
VERSION |
Report supported CNI specification versions | Runtime version negotiation |
How the Runtime Invokes a Plugin
Environment Variables Passed to Plugin
CNI_COMMAND— the operation:ADD,DEL,CHECK, orVERSIONCNI_CONTAINERID— unique container/sandbox IDCNI_NETNS— path to the network namespace (e.g.,/var/run/netns/abc123)CNI_IFNAME— the interface name to create inside the container (e.g.,eth0)CNI_PATH— search path for plugin binaries (e.g.,/opt/cni/bin)
# Example: runtime invoking the bridge plugin for ADD
$ CNI_COMMAND=ADD \
CNI_CONTAINERID=abc123def456 \
CNI_NETNS=/var/run/netns/abc123 \
CNI_IFNAME=eth0 \
CNI_PATH=/opt/cni/bin \
/opt/cni/bin/bridge < /etc/cni/net.d/10-bridge.conflist
# Plugin returns JSON result on stdout:
{
"cniVersion": "1.0.0",
"interfaces": [{ "name": "eth0", "sandbox": "/var/run/netns/abc123" }],
"ips": [{ "address": "10.244.1.5/24", "gateway": "10.244.1.1" }],
"routes": [{ "dst": "0.0.0.0/0", "gw": "10.244.1.1" }],
"dns": { "nameservers": ["10.96.0.10"] }
}
File Locations
/opt/cni/bin/ Plugin binaries
bridge
ptp
macvlan
ipvlan
vlan
host-local IPAM plugin
dhcp IPAM plugin
static IPAM plugin
portmap Meta plugin
bandwidth Meta plugin
flannel 3rd-party plugin
calico 3rd-party plugin
/etc/cni/net.d/ Configuration files
10-bridge.conflist Loaded in alphabetical order
99-loopback.conf
Warning
Warning: The runtime loads the first config file in /etc/cni/net.d/ (alphabetically). If you have multiple conflist files, the naming prefix (e.g., 10-, 99-) determines priority. A stale config file from a previous CNI plugin installation can silently break networking.
IPAM (IP Address Management) Plugins
IPAM is separated from network plumbing by design. The network plugin calls the IPAM plugin to allocate/release IPs. This separation means any network plugin can work with any IPAM plugin.
| IPAM Plugin | Allocation Method | State Storage | Use Case |
|---|---|---|---|
host-local |
Allocate from configured range on the local host | /var/lib/cni/networks/<name>/ |
Default for most setups. Each node gets a subnet slice. |
dhcp |
DHCP request to network DHCP server | DHCP lease (external) | Containers needing real LAN IPs (macvlan setups) |
static |
Fixed IP from config | None (config-based) | Testing, specialized infra with known IPs |
host-local Configuration Example
{
"type": "host-local",
"ranges": [
[{
"subnet": "10.244.1.0/24",
"rangeStart": "10.244.1.10",
"rangeEnd": "10.244.1.250",
"gateway": "10.244.1.1"
}]
],
"routes": [{ "dst": "0.0.0.0/0" }],
"dataDir": "/var/lib/cni/networks"
}Tip
Tip: If pods get stuck in ContainerCreating with IPAM errors, check /var/lib/cni/networks/<name>/ on the node. Stale IP reservations from crashed pods accumulate here. Safe to clear if the pods are gone (but let the CNI plugin handle cleanup if possible).
Network Plugins (Reference Implementations)
The CNI project ships several reference plugins that handle the actual network plumbing. Third-party plugins (Calico, Cilium, Flannel) often build on or replace these.
bridge
- Creates a Linux bridge on the host
- Creates a veth pair: one end in pod, other on bridge
- Functionally identical to Docker's bridge mode
- Used by Flannel (in bridge mode) and standalone K8s
ptp (point-to-point)
- Creates a veth pair with no bridge
- Host-side veth gets a route to the pod IP
- Used by Calico — each pod gets a /32 route on the host
- More scalable than bridge (no ARP, no broadcast domain)
macvlan / ipvlan
- Attach containers directly to host's physical interface
- macvlan: unique MAC per container (real L2 identity)
- ipvlan: shared MAC, unique IP (better for cloud/switch limits)
- Zero overlay overhead — native L2/L3 performance
VXLAN-based (via Flannel, Weave)
- Overlay networking: encapsulate L2 frames in UDP
- Enables pod-to-pod communication across hosts
- ~50 byte overhead per packet (VXLAN header)
- Works anywhere — no special network infrastructure needed
Plugin Chaining
CNI supports executing multiple plugins in sequence via a conflist (configuration list). Each plugin receives the result of the previous plugin and can augment it. This is how additional features like port mapping and bandwidth limiting are layered on.
bridge
Create veth, attach to bridge, assign IP
portmap
Setup iptables DNAT for hostPort
bandwidth
Apply tc (traffic control) rate limits
Conflist Example
{
"cniVersion": "1.0.0",
"name": "my-k8s-network",
"plugins": [
{
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"ranges": [[{ "subnet": "10.244.1.0/24" }]],
"routes": [{ "dst": "0.0.0.0/0" }]
}
},
{
"type": "portmap",
"capabilities": { "portMappings": true }
},
{
"type": "bandwidth",
"capabilities": { "bandwidth": true }
}
]
}Note
Note: Plugins in the chain are executed in order for ADD and in reverse order for DEL. This ensures clean teardown — bandwidth limits are removed before the interface is deleted.
How CNI Wires a Pod (Detailed Flow)
This is the full sequence from kubelet deciding to create a pod to the pod having a working network interface. Understanding this flow is essential for debugging networking failures.
- kubelet creates pod sandbox
kubelet calls CRIRunPodSandbox(). The CRI runtime (containerd/CRI-O) creates a pause container — a minimal container whose sole purpose is to hold the network namespace. All other containers in the pod will share this namespace. - CRI creates network namespace
The runtime callsunshare(CLONE_NEWNET)to create a new network namespace for the sandbox. At this point it has only a loopback interface. - CRI invokes CNI ADD
The runtime setsCNI_COMMAND=ADD,CNI_NETNS=/proc/<pid>/ns/net,CNI_IFNAME=eth0, and pipes the conflist JSON to the first plugin binary on stdin. - CNI plugin creates veth pair
The plugin creates a veth pair. One end stays in the host namespace (attached to a bridge or routed), the other is moved into the pod's network namespace. - Attach to bridge or setup routing
Bridge plugins attach the host-side veth to a bridge (e.g.,cni0). Routing plugins (Calico/ptp) add a host route for the pod IP instead. - Call IPAM plugin
The network plugin invokes the IPAM plugin (e.g.,host-local) to allocate an IP address from the configured range. IPAM writes the allocation to its state file. - Configure pod namespace
The plugin enters the pod's namespace and configures: IP address on eth0, default route to the gateway, DNS in/etc/resolv.conf(typically pointing to kube-dns at10.96.0.10). - Return result to CRI / kubelet
The plugin chain returns a JSON result (IPs, routes, interfaces, DNS) to stdout. The CRI runtime passes this back to kubelet, which records the pod's IP in the API server.
Warning
Warning: If the CNI ADD call fails, the pod stays in ContainerCreating. Common causes: missing CNI binaries in /opt/cni/bin/, missing or malformed config in /etc/cni/net.d/, IPAM range exhaustion, or the CNI plugin's backend (e.g., etcd for Calico) being unreachable.
CNI vs Docker Networking
Docker has its own networking model called CNM (Container Network Model), implemented by libnetwork. Kubernetes chose CNI instead. Understanding why illuminates fundamental design differences.
- Tightly coupled to Docker daemon
- Containers are behind NAT by default
- Cross-container communication requires port mapping or links
- Port conflicts are the caller's problem
- Discovery via Docker's embedded DNS (per-network)
- Multi-host: Docker Swarm overlay (VXLAN)
- More complex spec (Network, Endpoint, Sandbox abstractions)
- Runtime-agnostic (containerd, CRI-O, etc.)
- Every pod gets a routable IP — no NAT between pods
- Any pod can reach any other pod by IP directly
- Flat network model — simpler, more like real networking
- Discovery via kube-dns / CoreDNS (cluster-wide)
- Multi-host: pluggable (Flannel, Calico, Cilium, etc.)
- Minimal spec (4 operations, stdin/stdout JSON)
Why Kubernetes Chose CNI
- Simpler spec — CNI is just exec a binary, pass JSON. CNM has a complex plugin registration and lifecycle model.
- No Docker dependency — K8s didn't want to be locked to Docker's daemon. CNI works with any OCI-compliant runtime.
- Flat network model — K8s requires pod-to-pod communication without NAT. Docker's NAT-by-default model conflicts with this.
- Pluggable — swapping network backends is just replacing a binary and a config file. No daemon restart needed.
- Overlay AND native routing — CNI supports both VXLAN overlays (Flannel) and BGP-based routing (Calico) equally well. Docker's model is overlay-centric.
| Aspect | Docker (CNM) | Kubernetes (CNI) |
|---|---|---|
| IP model | Private IPs, NAT to host | Routable IPs, no inter-pod NAT |
| Inbound traffic | Port mapping (-p) |
Direct to pod IP (via Service/Ingress) |
| Service discovery | Embedded DNS per network | CoreDNS cluster-wide |
| Plugin interface | Go library (linked into daemon) | Executable binary (exec'd per operation) |
| Plugin swap | Daemon restart | Replace binary + config |
| NetworkPolicy | Not supported | Supported (Calico, Cilium, etc.) |
Popular CNI Plugins
| Plugin | Data Plane | Routing | NetworkPolicy | Encryption | Complexity |
|---|---|---|---|---|---|
| Flannel | Linux kernel (bridge + VXLAN) | VXLAN overlay (default), host-gw | No (needs Calico for policy) | WireGuard (experimental) | Low |
| Calico | Linux kernel (iptables) or eBPF | BGP native routing (no overlay) or VXLAN | Yes (full K8s + extended) | WireGuard | Medium |
| Cilium | eBPF (kernel bypass) | Native routing, VXLAN, or GENEVE | Yes (L3/L4 + L7 + DNS-aware) | WireGuard / IPsec | Medium-High |
| Weave Net | Linux kernel (OVS/VXLAN) | Mesh overlay | Yes (basic) | NaCl (built-in) | Low |
Plugin Deep Dive
Flannel
- Simplest CNI plugin — great for learning/dev clusters
- Default mode: VXLAN — encapsulates L2 frames in UDP between nodes
- host-gw mode: no overlay, adds host routes directly (requires L2 adjacency between nodes)
- Assigns a
/24subnet per node from a configured/16supernet - Stores subnet allocation in etcd (via Kubernetes API)
- No NetworkPolicy support — often paired with Calico ("Canal" = Calico policy + Flannel networking)
Calico
- Production-grade, used by major cloud providers (EKS, AKS, GKE)
- Default mode: BGP native routing — each node is a BGP speaker, advertises pod CIDRs
- No overlay overhead — pods are reachable at native line rate
- Falls back to VXLAN or IP-in-IP when BGP isn't possible (cross-subnet, cloud VPC)
- Full NetworkPolicy implementation (plus Calico-specific extensions for DNS, service accounts, global policies)
- Uses ptp plugin internally — each pod gets a /32 route on the host
Cilium
- eBPF-based data plane — bypasses iptables entirely
- Replaces kube-proxy for service load balancing (eBPF-based ClusterIP, NodePort, LoadBalancer)
- L7-aware NetworkPolicy — can filter by HTTP method, path, DNS name, gRPC service
- Hubble: built-in observability platform (flow logs, service map, DNS visibility)
- Identity-based security — labels, not IPs (handles pod IP churn gracefully)
- Covered in detail in Net 10 — eBPF
Weave Net
- Mesh overlay — every node connects to every other node
- Auto-discovers peers via gossip protocol (no external coordination)
- Built-in encryption (NaCl-based, enabled by default)
- Simple to install — single DaemonSet, zero config
- Basic NetworkPolicy support
- Less actively developed; Cilium and Calico are preferred for production
Comparison: Bridge vs Routing Data Planes
Tip
Tip: For production clusters: use Calico if you need NetworkPolicy and BGP routing (most common choice). Use Cilium if you need L7 policies, advanced observability, or want to replace kube-proxy entirely. Use Flannel for simplicity in dev/test environments.
Note
Note: Many managed Kubernetes services ship with their own CNI: AWS VPC CNI (assigns ENI secondary IPs to pods — real VPC IPs), Azure CNI, GKE's native CNI. These integrate directly with the cloud's SDN and skip overlay networking entirely.