Skip to content
Menu

Networking10 min read

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, or VERSION
  • CNI_CONTAINERID — unique container/sandbox ID
  • CNI_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)
console
console

    # 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

text
bash

    /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

bash
{
  "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.

  1. bridge

    Create veth, attach to bridge, assign IP

  2. portmap

    Setup iptables DNAT for hostPort

  3. bandwidth

    Apply tc (traffic control) rate limits

Conflist Example

bash
{
  "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.

  1. kubelet creates pod sandbox
    kubelet calls CRI RunPodSandbox(). 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.
  2. CRI creates network namespace
    The runtime calls unshare(CLONE_NEWNET) to create a new network namespace for the sandbox. At this point it has only a loopback interface.
  3. CRI invokes CNI ADD
    The runtime sets CNI_COMMAND=ADD, CNI_NETNS=/proc/<pid>/ns/net, CNI_IFNAME=eth0, and pipes the conflist JSON to the first plugin binary on stdin.
  4. 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.
  5. 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.
  6. 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.
  7. 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 at 10.96.0.10).
  8. 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.
Pod Network Setup Flow
kubelet
RunPodSandbox()
->
CRI (containerd)
Create pause container
->
New netns
CLONE_NEWNET
|
CNI_COMMAND=ADD
v
CNI Plugin
Create veth pair
->
IPAM Plugin
Allocate IP
->
Configure netns
IP + routes + DNS
|
JSON result (IPs, routes, DNS)
v
CRI
Returns result
->
kubelet
Records pod IP
->
API Server
status.podIP set

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.

Docker Networking (CNM/libnetwork)
  • 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)
Kubernetes Networking (CNI)
  • 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.)
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 /24 subnet per node from a configured /16 supernet
  • 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

Bridge Mode (Flannel)
Pod A
eth0
Pod B
eth0
| veth    | veth
cni0 (bridge)
| flannel.1 (VXLAN)
L2 forwarding within node, VXLAN to remote
Routing Mode (Calico)
Pod A
eth0
Pod B
eth0
| veth    | veth
Host routing table
| BGP to other nodes
L3 routing, no bridge, /32 routes per pod

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.

Solidnines — solidnines.com