CNIs compared
What happens when a pod starts · the CNI contract
kubelet asks the runtime for a pod sandbox
The scheduler bound the pod to this node. kubelet calls the CRI RunPodSandbox RPC on containerd. Kubernetes itself never touches interfaces or IPs.
RunPodSandbox {
config: { metadata: { name: "web-7d9f", namespace: "default" },
linux: { ... } }
}Encapsulation, MTU and the bytes you pay
Encapsulation explorer
The field
| Flannel | Cilium | Calico | Kube-router | Kube-OVN | Antrea | Multus | |
|---|---|---|---|---|---|---|---|
| Dataplane | Linux bridge + routes | eBPF | iptables / nftables / eBPF | routes + iptables + IPVS | OVS (OVN) | OVS | meta-plugin (delegates) |
| Pod routing | VXLAN, host-gw, WireGuard | VXLAN / Geneve or native | BGP native, IPIP, VXLAN | BGP native (+IPIP) | Geneve, VLAN underlay | Geneve, VXLAN, noEncap | whatever it delegates to |
| NetworkPolicy | none | L3/L4 + L7, identity-based | L3/L4, tiers, global | L3/L4 | L3/L4 | L3/L4, tiers (+L7) | — |
| kube-proxy replacement | no | full (eBPF) | eBPF dataplane | IPVS proxy | partial | AntreaProxy | — |
| Encryption | WireGuard backend | WireGuard, IPsec | WireGuard | no | IPsec | IPsec, WireGuard | — |
| BGP | no | BGP control plane | native, full mesh / RR | GoBGP built in | LB/pod IP export | BGP policy | — |
| Observability | — | Hubble (flows, L7, UI) | flow logs, Whisker UI | — | mirroring, tracing | Theia, traceflow | — |
| On Talos | default, built in | cni: none + Helm | cni: none + operator | cni: none + manifest | cni: none + Helm | cni: none + Helm | add on top of primary |
Pick your CNI
Requirements → candidates
- 1.CiliumeBPF4/4NetworkPolicykube-proxy replacement
eBPF dataplane, full kube-proxy replacement, identity-based L3–L7 policy, Hubble, BGP control plane, LB IPAM, WireGuard/IPsec, Gateway API. Most capable; most to learn.
- 2.CalicoBGP + policy4/4NetworkPolicykube-proxy replacement
iptables, nftables or eBPF dataplane; native BGP routing or IPIP/VXLAN; tiered GlobalNetworkPolicy; WireGuard; strong Windows support. Recent releases add flow logs + the Whisker UI.
- 3.AntreaOVS4/4NetworkPolicykube-proxy replacement
Open vSwitch dataplane, Geneve by default, AntreaProxy can replace kube-proxy, tiered ClusterNetworkPolicy, IPsec/WireGuard, first-class Windows.
- 4.Kube-routerlean4/4NetworkPolicykube-proxy replacement
- 5.Kube-OVNSDN3/4NetworkPolicykube-proxy replacement
- 6.FlannelTalos default0/4NetworkPolicykube-proxy replacement
Scores are a rough guide: 2 = first-class, 1 = partial / add-on / limited, 0 = missing (struck through). A missing hard requirement sinks the option regardless of score.
Installing it on Talos
# Nothing to do: Talos deploys Flannel (VXLAN) out of the box.
# Optional tuning, e.g. pin the interface Flannel uses on multi-NIC nodes:
cluster:
network:
cni:
name: flannel
flannel:
extraArgs:
- --iface-can-reach=192.168.1.1# patch: cni-none.yaml — apply to ALL control plane nodes before bootstrap
cluster:
network:
cni:
name: none # Talos installs no CNI; nodes stay NotReady until you do
proxy:
disabled: true # Cilium replaces kube-proxyhelm repo add cilium https://helm.cilium.io && helm repo update
helm install cilium cilium/cilium --version 1.18.x --namespace kube-system \
--set ipam.mode=kubernetes \
--set kubeProxyReplacement=true \
--set securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \
--set securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \
--set cgroup.autoMount.enabled=false \
--set cgroup.hostRoot=/sys/fs/cgroup \
--set k8sServiceHost=localhost \
--set k8sServicePort=7445# Talos: same cni.name: none patch (keep kube-proxy unless you use the eBPF dataplane)
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.30.x/manifests/tigera-operator.yaml
kubectl apply -f - <<'EOF'
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
name: default
spec:
calicoNetwork:
linuxDataplane: Nftables # or Iptables / BPF
bgp: Enabled
ipPools:
- cidr: 10.244.0.0/16 # must match cluster.network.podSubnets
encapsulation: VXLANCrossSubnet # routed within a subnet, VXLAN across
natOutgoing: Enabled
EOF- why it mattersA CNI is a contract, not a product: runtime execs a binary with ADD/DEL, it builds a veth, asks IPAM for an IP, sets routes and returns JSON. Everything users notice — policy, encryption, Service speed — is extra.
- why it mattersOverlay vs routed is a trade: tunnels work on any network but cost 20–60 bytes per packet and need a lower pod MTU; native routing is free but your fabric must learn pod routes (BGP or a flat L2).
- why it mattersFlannel is a fine default until you need NetworkPolicy — it silently ignores policies. Choose the CNI before bootstrap; swapping a live cluster's dataplane is a migration, not a config change.