Skip to main content

Kubernetes Networking β€” CNI, Calico BGP & Outage Internals

Kubernetes enforces a declarative, flat IP-per-Pod networking model. Every Pod receives its own unique routable IP address, eliminating port conflicts and allowing microservices to communicate across physical worker nodes without Network Address Translation (NAT).

Under the hood, this abstraction is powered by Linux network namespaces, Container Network Interface (CNI) plugins, BGP routing fabrics (Calico / BIRD), Netfilter kernel pipelines, and increasingly, eBPF socket programs.


1. The Fundamental Kubernetes Network Model

Kubernetes imposes four foundational networking invariants:

  1. All Pods can communicate with all other Pods without relying on NAT.
  2. All Nodes can communicate with all Pods (and vice versa) without NAT.
  3. The IP that a Pod sees itself as is the exact same IP that all other Pods see it as (no masquerading).
  4. Agents on a node (e.g. kubelet) can communicate with all Pods on that node.
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ THE FOUR K8S NETWORKING COMMUNICATION TIERS β”‚
β”‚ β”‚
β”‚ 1. Container-to-Container (Within same Pod): Shared network namespace (localhost) β”‚
β”‚ 2. Pod-to-Pod (Across different Nodes): Coordinated by CNI (Flat L3 Routing) β”‚
β”‚ 3. Pod-to-Service: Virtual IP (ClusterIP) translated in-kernel via Kube-Proxy / eBPF β”‚
β”‚ 4. External-to-Service: Ingress, Gateway API, or Cloud Load Balancers β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Kubernetes 4-Layer Networking & CNI Overlay Architecture
🌍 External Client βž” Ingress Controller / Cloud LoadBalancer (Port 443)⚑ Service VIP: 10.96.0.45:80 (kube-proxy DNAT Load Balancing)Worker Node 1 (Host IP: 192.168.1.10)Pod A (10.244.1.5)veth0 βž” br0 bridgeApp Container (:8080)Pod B (Sidecar Pod)127.0.0.1 localhostShared Net NamespaceVXLAN TunnelWorker Node 2 (Host IP: 192.168.1.11)Pod C (10.244.2.8)veth1 βž” br0 bridgeOrder Service TargetPod D (10.244.2.9)Healthy ReplicaNo NAT Traversal
1. Container-to-ContainerSame Pod (Loopback)

Containers inside the same Pod share the exact same Linux network namespace. They bind to different ports on 127.0.0.1 with zero network latency (kernel socket copy).

πŸ’‘ Spring Boot app on :8080 communicates with Envoy sidecar on :15001 via localhost.
2. Pod-to-Pod (Cross-Node CNI)Node-to-Node Overlay

Every Pod receives a real, unique IP in the cluster CIDR. Pods communicate directly across nodes without NAT via CNI plugins (Calico, Cilium, Flannel) using VXLAN/Geneve encapsulation.

πŸ’‘ Pod A (10.244.1.5) on Node 1 sends packets directly to Pod B (10.244.2.8) on Node 2.
3. Pod-to-Service (ClusterIP)Internal Virtual IP (VIP)

Services provide a stable virtual IP that never changes even when Pods scale or die. kube-proxy programs iptables, IPVS, or eBPF maps to load-balance traffic to healthy backing Pod IPs.

πŸ’‘ Calling http://order-service (10.96.0.45:80) round-robins packets across 3 backend replica pods.
4. External-to-Service (Ingress / LB)Outside Traffic Ingress

Exposes services to public internet users. Cloud Load Balancer (AWS ALB / GCP NLB) or Ingress Controller (Nginx, Envoy) terminates TLS and performs L7 path-based routing.

πŸ’‘ https://api.company.com/orders routes to the Ingress controller, which proxies to the ClusterIP Service.

2. Pod-to-Pod Plumbing: Linux Namespaces & veth Pairs

How does a packet physically travel from Container A on Node 1 to Container B on Node 2?

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ POD NETWORK NAMESPACE & VETH WIRING β”‚
β”‚ β”‚
β”‚ NODE 1 (Worker VM) β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ POD NETWORK NAMESPACE (netns: /var/run/netns/pod-1) β”‚ β”‚
β”‚ β”‚ IP: 10.244.1.5/24 β”‚ β”‚
β”‚ β”‚ [ eth0 ] ──────────────────────────────────────────────┐ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ (Virtual Ethernet Cable) β”‚
β”‚ HOST ROOT NAMESPACE (netns: default) β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ [ veth-pod1 / caliXXX ] <β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β–Ό β”‚ β”‚
β”‚ β”‚ [ Linux Kernel FIB / Routing Table ] ──> [ Netfilter / eBPF ] ──> [ eth0 ] β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ Physical Network β”‚ 192.168.1.100 β”‚
β”‚ (Direct L3 BGP / β–Ό β”‚
β”‚ VXLAN Encapsulation) [ Physical Network] β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The CNI Execution Lifecycle

When a Pod is scheduled to a worker node, the following exact sequence occurs:

  1. CRI Invocation: kubelet connects via gRPC over Unix Domain Socket (/run/containerd/containerd.sock) to the container runtime.
  2. Sandbox Creation: The runtime launches the pause container, creating a new, isolated Linux network namespace (netns).
  3. CNI Plugin Execution: The runtime executes the CNI plugin binary (e.g. /opt/cni/bin/calico) passing standard JSON configuration via stdin (CNI ADD verb):
    • Veth Pair Provisioning: CNI creates a bidirectional veth (virtual ethernet) pair in the kernel.
    • Namespace Placement: One peer is moved into the Pod netns and renamed eth0. The other peer remains in the host root netns (named cali<container-id-suffix> in Calico or veth<suffix> in flannel).
    • IPAM Allocation: The IPAM plugin (e.g. calico-ipam) allocates an IP from the node's assigned Pod CIDR block and assigns it to eth0.
    • Default Gateway: Inside the Pod netns, the default route is set to point out eth0 toward the host's link-local IP (169.254.1.1).
  4. Container Launch: Once networking is plumbed, the runtime starts the application containers inside the pause container's network namespace, inheriting eth0 and sharing loopback (127.0.0.1).

3. Calico Under-the-Hood: Felix, BIRD & Direct L3 Routing

Unlike overlay networks (such as Flannel VXLAN) which wrap every packet inside an outer UDP tunnel packet, Calico operates by default as a pure Layer 3 routed network.

Calico BGP Routing & The Reddit Pi Day Outage Telemetry
Click any internal component below to inspect its under-the-hood engine mechanics, system calls, and configuration files.
WORKER NODE 1 (192.168.1.10)Pod Netns (10.244.1.5)eth0App Containerveth paircali9b4ccalico-node DaemonSetβš™οΈ Felix (Kernel Programmer)πŸ“‘ BIRD (BGP Engine - Port 179)πŸ”Œ Calico CNI Plugin (Kubelet CRI)🧠 Linux Kernel FIB & Netfilter (Routing Table & iptables/eBPF)10.244.2.0/26 via 192.168.1.20 dev eth0 (Direct L3 Routing - No Encap)eth0
Felix (The Kernel Programmer)
Host Linux Kernel FIB & Firewall Sync Agent
Runs as a daemon inside calico-node. Watches the Kubernetes API/datastore for workload endpoints and NetworkPolicies. Directly programs the Linux kernel FIB (ip route add <pod-ip> dev caliXXX) and configures iptables chains or eBPF maps for microsegmentation.
SYSTEM & CONFIG TELEMETRY:
ip route add 10.244.1.15 dev cali4f981e scope link

The Three Internal Engines of Calico

Every worker node runs a single unified Pod named calico-node (managed by a DaemonSet) containing three core subsystems:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CALICO-NODE INTERNAL ARCHITECTURE β”‚
β”‚ β”‚
β”‚ WORKER NODE β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ calico-node DAEMONSET POD β”‚ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ FELIX AGENT β”‚ β”‚ BIRD BGP DAEMON β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β€’ Watches K8s API/etcd β”‚ β”‚ β€’ Runs BGP over TCP 179 β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β€’ Programs Host Kernel FIB β”‚ β”‚ β€’ Distributes Pod CIDRs β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β€’ Programs iptables / eBPF β”‚ β”‚ β€’ Discovers peer routes β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ Writes local Pod routes β”‚ Writes remote routes β”‚
β”‚ β–Ό β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ LINUX KERNEL FORWARDING INFORMATION BASE (FIB) β”‚ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β€’ 10.244.1.5/32 dev cali9b4c scope link (Local Pod - programmed by Felix) β”‚ β”‚
β”‚ β”‚ β€’ 10.244.2.0/26 via 192.168.1.20 dev eth0 proto bird (Remote Node 2) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  1. Felix (The Linux Kernel Programmer):

    • Written in Go. Connects directly to the Kubernetes API server (or etcd datastore).
    • Whenever a Pod starts on this node, Felix programs the host kernel FIB with a /32 host route:
      # Local route written by Felix:
      ip route add 10.244.1.5 dev cali9b4c scope link
    • Felix also manages iptables chains (cali-INPUT, cali-FORWARD, cali-OUTPUT) or compiles eBPF TC (Traffic Control) programs to enforce Kubernetes NetworkPolicy microsegmentation directly in kernel space.
  2. BIRD (The BGP Routing Engine):

    • An open-source userspace internet routing daemon.
    • Listens on standard BGP port TCP 179.
    • BIRD reads the local Pod CIDR assigned to this node by IPAM (e.g. 10.244.1.0/26) and advertises a BGP NLRI (Network Layer Reachability Information) route to peer nodes:

      "To reach 10.244.1.0/26, send your packets to my Host IP: 192.168.1.10".

    • When BIRD receives route advertisements from other nodes, it writes them into the Linux kernel FIB:
      # Remote route received via BGP and injected into Linux kernel:
      10.244.2.0/26 via 192.168.1.20 dev eth0 proto bird
  3. calico-ipam (IP Address Management):

    • Slices the large cluster CIDR (10.244.0.0/16) into smaller chunks (typically /26 blocks of 64 IPs) and assigns one or more blocks to each node.
    • This hierarchical allocation prevents route table bloat: nodes only advertise their /26 block rather than individual /32 Pod IPs.

4. BGP Peering Topologies: Full-Mesh vs. Route Reflectors

How do nodes exchange BGP routes across a cluster?

Calico BGP Routing & The Reddit Pi Day Outage Telemetry
Route Reflector (RR) Hub-and-Spoke TopologyRoute Reflectors (RR)Hosted on Control-Plane NodesW1W2W3W4W5N = 1000, 3 RRs βž” Only 3,000 BGP Sessions (99.4% drop!)
Why Route Reflectors Save Large Clusters
To bypass the $O(N^2)$ barrier, Calico supports BGP Route Reflectors (RFC 4456).

Instead of peering with all 999 peers, worker nodes act as Route Reflector Clients and peer ONLY with a designated quorum of 2–3 Route Reflectors:
Sessions = k Γ— N (where k = 2 or 3 Route Reflectors)
For a 1,000-node cluster with 3 RRs: 3,000 total sessions vs 499,500.

Production Vulnerability at Reddit: Reddit co-located their Calico Route Reflectors directly onto their Kubernetes Control-Plane nodes!

The Full-Mesh BGP O(N2)O(N^2) Scalability Meltdown

By default, Calico operates in node-to-node mesh mode (nodeToNodeMeshEnabled: true). In this configuration, every node establishes a direct BGP TCP peering session with every other node in the cluster.

The number of persistent BGP sessions across the cluster is:

Sessions=N(Nβˆ’1)2\text{Sessions} = \frac{N(N - 1)}{2}

Cluster Node Count (NN)Total BGP Peering SessionsState Machine Load
10 nodes45 sessionsNegligible
50 nodes1,225 sessionsNormal
100 nodes4,950 sessionsHigh CPU overhead
500 nodes124,750 sessionsSevere keepalive degradation
1,000 nodes (Reddit scale)499,500 sessionsCatastrophic crash / TCP starvation

Why Full-Mesh Fails Beyond 100 Nodes:​

  • BGP Keepalive Flooding: BGP peers exchange periodic KEEPALIVE packets (every 15–30 seconds) to detect dead peers. With 1,000 nodes, each node must process β‰ˆ1,000\approx 1,000 keepalives continuously, consuming significant CPU cycles inside userspace BIRD.
  • Route Flap Storms: If a single node reboots or drops off the network, it triggers 999 simultaneous BGP WITHDRAW notifications across all nodes. The ensuing route recalculation locks BIRD event loops and triggers cascading connection drops.

The Route Reflector (RR) Solution (O(N)O(N) Hub-and-Spoke)

To scale past 100 nodes, network architects deploy BGP Route Reflectors (RFC 4456):

  1. Hub-and-Spoke Topology: Instead of peering with 999 other nodes, worker nodes act as Route Reflector Clients and peer exclusively with a small quorum of 2 to 3 Route Reflectors:

TotalΒ Sessions=kΓ—N(k=numberΒ ofΒ RouteΒ Reflectors,Β usuallyΒ 3)\text{Total Sessions} = k \times N \quad (k = \text{number of Route Reflectors, usually 3})

  1. Route Redistribution: When Worker Node 1 advertises its local Pod subnet to Route Reflector A, the Route Reflector reflects (redistributes) this route to all other 999 client nodes.
  2. Session Reduction: For a 1,000-node cluster, migrating from full-mesh to 3 Route Reflectors drops total BGP sessions from 499,500 down to 3,000 β€” a 99.4% reduction in connection state!
# Configuring Calico BGPConfiguration to disable full mesh
apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
name: default
spec:
logSeverityScreen: Info
nodeToNodeMeshEnabled: false # Disables O(N^2) full-mesh peering
asNumber: 64512

5. Case Study: Reddit's 314-Minute Pi Day Outage (The True Story)

On March 14, 2023 (Pi Day), Reddit suffered a catastrophic cross-platform outage that lasted 314 minutes. The postmortem ("You Broke Reddit: The Pi-Day Outage") published by Reddit's engineering team reveals one of the most famous real-world Kubernetes internal networking failures in cloud-native history.

Calico BGP Routing & The Reddit Pi Day Outage Telemetry
Step 1: Steady State (K8s v1.23)HEALTHY

Reddit operates a 1,000+ node "pet" cluster built with kubeadm. Control plane nodes hold the legacy node-role.kubernetes.io/master label. BGP Route Reflectors running on control plane nodes maintain healthy BGP peering sessions with all 1,000 worker nodes. Cross-node routing and CoreDNS resolution work perfectly.

CALICO BGPPEER MANIFEST (THE TIME BOMB):
apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
  name: peer-with-route-reflectors
spec:
  nodeSelectors: all()
  # Fatal line: Kubernetes 1.24 removed this label!
  peerSelector: node-role.kubernetes.io/master == ''
βœ… In K8s 1.23, peerSelector matches 3 master nodes.
SYSTEM HEALTH TELEMETRY:
BGP Peering Sessions:3,000 / 3,000 (ESTABLISHED)
Kernel Pod Routes:Active (Direct L3 wire)
CoreDNS Resolution:100% OK
Public Traffic Load:100% Active

The Architecture & The Vulnerability

  • The Cluster: Reddit operated their oldest, largest, and most critical production cluster: a 1,000+ node "pet" cluster bootstrapped with kubeadm.
  • The Peering Architecture: Because the cluster had over 1,000 nodes, full-mesh BGP was impossible. Reddit used Calico Route Reflectors to maintain L3 routing.
  • The Co-location Pattern: Reddit deployed the Calico Route Reflectors directly onto their Control-Plane nodes.
  • The Declarative Configuration: Worker nodes were instructed to peer with the Route Reflectors using a Calico BGPPeer custom resource:
apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
name: peer-with-route-reflectors
spec:
nodeSelectors:
- all()
peerSelector: node-role.kubernetes.io/master == '' # <--- THE TIME BOMB

The Trigger: Upgrading from Kubernetes 1.23 to 1.24

Reddit engineers initiated an in-place upgrade of the cluster from Kubernetes v1.23 to v1.24:

  1. The Upstream Breaking Change:
    • In Kubernetes 1.20, the project initiated a gradual terminology transition from "master" to "control-plane".
    • In Kubernetes 1.24, the deprecated label node-role.kubernetes.io/master was completely stripped from nodes upon upgrade. Nodes now only carried node-role.kubernetes.io/control-plane.
  2. The Zero-Match Collapse:
    • As soon as the control-plane nodes upgraded to 1.24, the master label vanished from etcd.
    • Calico's peerSelector: node-role.kubernetes.io/master == "" evaluated against the cluster and matched 0 nodes.
  3. The Cluster-Wide BGP Tear-Down:
    • Calico's BIRD daemon interpreted the zero-match selector as: "All Route Reflectors have been decommissioned."
    • All 1,000 worker nodes instantly tore down their BGP peering sessions.
    • BIRD flushed all remote Pod CIDR routes from the Linux kernel routing tables (FIB) across all 1,000 nodes.
  4. The CoreDNS Blackhole & Total Outage:
    • Pods could no longer route packets to Pods on any other physical machine.
    • CoreDNS pods were running on specific nodes. Because cross-node routing was completely severed, Pods on other 990+ nodes could no longer reach CoreDNS.
    • In-cluster DNS queries failed with NXDOMAIN and connection timeouts.
    • Microservices could not discover databases, caches, or downstream RPC services. Every health check failed. Reddit went dark.

The Rollback Fallacy: Why Couldn't Reddit Just Downgrade?

When a critical upgrade fails, the immediate instinct of many engineers is: "Roll back to version 1.23!"

In Kubernetes, an in-place cluster downgrade is officially unsupported and physically hazardous:

  1. etcd Schema Storage Version Lock-In:
    • When Kubernetes upgrades, kube-apiserver migrates internal object schemas in etcd to newer storage versions (e.g. migrating deprecated extensions/v1beta1 or autoscaling/v2beta1 to autoscaling/v2).
    • Downgrading the API server binary will cause it to crash or fail to deserialize etcd keys written in the newer schema.
  2. CRD & Controller Incompatibilities:
    • Custom Resource Definitions (CRDs) and operators that migrated their conversion webhooks during the upgrade cannot be gracefully interpreted by older API server binaries.
  3. Kubelet & CRI One-Way Transitions:
    • In Kubernetes 1.24, dockershim was officially removed from kubelet. Downgrading requires re-plumbing the container runtime socket and restarting every node daemon.

Faced with an impossible downgrade, Reddit engineers had to debug the cluster live under high pressure, identify the missing label, and restore BGP peering.

Staged Recovery vs. The Thundering Herd

Once the BGPPeer selector was corrected and BIRD re-established peering sessions, routes reappeared in kernel FIBs. However, Reddit did NOT immediately redirect 100% of user traffic back to the site.

Why Immediate 100% Traffic Would Cause a Second Collapse:​

  1. Cold Caches: Over the 314 minutes of downtime, all Redis and Memcached tiers had expired their TTLs or lost in-memory state.
  2. Thundering Herd / Cache Stampede: If millions of active users hit the platform at once, every single web request would miss cache and hammer backend PostgreSQL, Cassandra, and CockroachDB databases with raw read queries (100Γ—100\times baseline load).
  3. Queued Retry Storm: Tens of millions of client retries queued up in mobile apps and browsers would instantly saturate edge ingress load balancers.

The Safe Ramp-Up Curve:​

Reddit engineered a phased traffic restoration curve over several hours:

Traffic Ramping: 1%⟢5%⟢10%⟢20%⟢35%⟢55%⟢80%⟢100%\text{Traffic Ramping: } 1\% \longrightarrow 5\% \longrightarrow 10\% \longrightarrow 20\% \longrightarrow 35\% \longrightarrow 55\% \longrightarrow 80\% \longrightarrow 100\%

This allowed distributed caching tiers to warm up gradually, connection pools to stabilize, and database query latency to remain strictly bounded.


6. The Evolution of Kube-Proxy: Userspace to eBPF

A Kubernetes Service IP (ClusterIP) is not bound to any physical network interface or container. It is a virtual IP (VIP) that exists purely as routing rules inside the Linux kernel.

The node agent responsible for programming these virtual rules is kube-proxy:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ KUBE-PROXY ROUTING MODES EVOLUTION β”‚
β”‚ β”‚
β”‚ 1. Userspace Mode (Historic) β”‚
β”‚ Packet -> Kernel Netfilter -> Userspace Kube-Proxy -> Kernel -> Pod β”‚
β”‚ Catastrophic context switching overhead (Ring 3 <-> Ring 0). β”‚
β”‚ β”‚
β”‚ 2. iptables Mode (Default in Legacy Clusters) β”‚
β”‚ Packet -> Kernel Netfilter -> Sequential Rule Scan -> DNAT to Pod IP β”‚
β”‚ O(N) sequential search; rule updates lock kernel memory at scale. β”‚
β”‚ β”‚
β”‚ 3. IPVS Mode (High-Scale Production) β”‚
β”‚ Packet -> Kernel IP Virtual Server Hash Table -> O(1) Lookup -> DNAT to Pod IP β”‚
β”‚ Fast hash tables; supports weighted round-robin and least-connections. β”‚
β”‚ β”‚
β”‚ 4. eBPF Mode (Cilium / Calico eBPF / Modern Cloud-Native) β”‚
β”‚ Packet -> eBPF Hook at Socket / XDP Layer -> Directly rewrites packet to Pod β”‚
β”‚ Completely bypasses iptables, conntrack, and kube-proxy; maximum line rate. β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

6.1 The iptables O(N)O(N) Scalability Wall

In iptables mode, kube-proxy generates deterministic Netfilter chains (KUBE-SERVICES, KUBE-SVC-XXX, KUBE-SEP-XXX):

  • When a packet hits a ClusterIP, the kernel traverses the rules sequentially.
  • The Bottleneck: In a large cluster with 5,0005,000 Services and 40,00040,000 Pod endpoints, iptables contains over 50,000 rules.
  • Adding or terminating a single Pod forces kube-proxy to rewrite the entire iptables rule blob and commit it via iptables-restore. During heavy scaling events, this causes massive CPU spikes and increases packet latency by hundreds of milliseconds.

6.2 The eBPF Revolution (Cilium & Calico eBPF)

Modern high-performance clusters replace kube-proxy entirely with eBPF (Extended Berkeley Packet Filter):

  • eBPF programs attach directly to network driver hooks (XDP - eXpress Data Path) and traffic control (tc).
  • When a packet arrives, eBPF inspects memory maps in kernel space in O(1)O(1) time, performs destination address translation (DNAT), and forwards the packet directly to the target veth interface.
  • Bypasses Linux Netfilter and conntrack tables entirely, reducing network latency by up to 40%.

7. CoreDNS Performance & The ndots:5 Resolution Storm

A notoriously common production failure mode in Kubernetes is high DNS latency and CoreDNS CPU throttling caused by Linux resolver semantics:

The ndots:5 Trap

By default, the Kubernetes kubelet injects the following configuration into every container's /etc/resolv.conf:

nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
  • The Rule: If a domain name contains fewer dots (.) than the ndots threshold (default 5), the resolver appends all local search domains sequentially before attempting to resolve the bare domain name.
  • The Impact: When your application queries api.stripe.com (which contains 2 dots, 2<52 < 5), the resolver makes four sequential DNS requests:
    1. api.stripe.com.default.svc.cluster.local β†’\to NXDOMAIN
    2. api.stripe.com.svc.cluster.local β†’\to NXDOMAIN
    3. api.stripe.com.cluster.local β†’\to NXDOMAIN
    4. api.stripe.com β†’\to SUCCESS!
  • This causes an explosive 4x DNS query amplification for every external API call, overwhelming CoreDNS pods and triggering latency timeouts.

Production Mitigations:​

  1. Append a Trailing Dot: Query api.stripe.com. directly in application configuration to indicate an absolute domain, bypassing search expansion.
  2. Deploy NodeLocal DNSCache: Runs an in-memory caching DNS daemonset on every worker node, serving queries locally via loopback.
  3. Override Pod Spec: Set ndots:2 in the Pod's dnsConfig.

8. Architectural Trade-Off Matrix: Kubernetes Networking CNI Solutions

ArchitecturePacket EncapsulationPacket MTU OverheadCross-Node MechanismScalability LimitBest For
Calico (Direct BGP)None (Raw L3 IP packets)0 bytes (Full 1500 MTU)BGP route distribution via BIRD daemonNeeds Route Reflectors above 100 nodesHigh-throughput bare metal / on-prem / enterprise VPCs
Calico (VXLAN Mode)Overlay (UDP Port 4789)50 bytes (MTU 1450)Linux kernel VXLAN tunnels~1,000 nodesMulti-cloud or cloud environments blocking custom BGP
Flannel (VXLAN)Overlay (UDP Port 8472)50 bytes (MTU 1450)Userspace / Kernel VXLAN device~500 nodesSimple developer clusters / homelabs
AWS VPC CNINone (Underlay ENI)0 bytes (Full 9001 Jumbo MTU)Direct AWS VPC routingBound by EC2 ENI limits & VPC CIDR sizeEKS native deployments; zero virtualization penalty
Cilium (eBPF)Option of VXLAN or Direct L30 to 50 byteseBPF TC hooks + BGP / Geneve5,000+ nodesHigh-scale, latency-sensitive microservices, eBPF observability

Senior/Staff Interview Playbook: How to Ace Kubernetes Networking & Production Outage Interviews

1. The Senior Framing Strategy: First 60 Seconds

When asked to design Kubernetes networking or diagnose a cluster outage, a Staff/Principal engineer frames the answer around physical data plane realities rather than high-level YAML abstractions:

*"In Kubernetes, all networking abstractions eventually decompose into standard Linux kernel primitives: network namespaces, veth pairs, routing tables (FIB), and Netfilter/eBPF packet hooks.

When architecting cluster networking, I divide the system into three distinct concerns:

  1. Data Plane Path: Direct L3 routing vs. overlay encapsulation (VXLAN/Geneve) β€” trading off MTU efficiency and CPU cycles against underlying network fabric cooperation.
  2. Control Plane Route Distribution: BGP full-mesh vs. Route Reflectors β€” ensuring connection counts scale linearly O(N)O(N) rather than exploding exponentially O(N2)O(N^2).
  3. Failure Domains & Day-2 Operations: Control plane label deprecations, etcd storage version immutability (which prohibits automated downgrade), and staged post-incident ramp-up to guard against thundering herds."*

2. Classic Interviewer Traps & Principal Rebuttals

Trap 1: "If an in-place Kubernetes upgrade breaks your network, why can't you just roll back to the previous version like a standard software release?"​

  • Junior/Mid Answer: "We can run helm rollback or downgrade the kubelet and kube-apiserver binary packages to the previous version."
  • Principal Rebuttal:

    *"Kubernetes does not support automated or in-place cluster downgrades.

    First, etcd storage version lock-in: When the API server upgrades, it migrates objects to newer storage schemas (e.g., removing beta API groups and writing newer v1 representations). An older kube-apiserver binary cannot read or deserialize schema versions it does not recognize.

    Second, CRD and webhook conversions: Upgraded CRD schemas and conversion webhooks become irreversible once applied to active resources.

    Third, Node agent state: Kubelet and container runtimes (such as removing dockershim in 1.24) mutate local state.

    In production, our disaster recovery strategy treats clusters as cattle, not pets: we maintain active-passive or blue-green multi-cluster topologies, or rebuild clean clusters via GitOps rather than attempting an in-place downgrade."*

Trap 2: "Why doesn't Kubernetes networking simply use default full-mesh BGP peering for all Calico clusters?"​

  • Junior/Mid Answer: "Full mesh is fine because nodes can just talk directly to each other without intermediate hops."
  • Principal Rebuttal:

    *"Full-mesh BGP peering suffers from a mathematical O(N2)O(N^2) scaling explosion: Sessions=N(Nβˆ’1)2\text{Sessions} = \frac{N(N - 1)}{2} In a small 20-node cluster, 190 sessions are trivial. But in a 1,000-node cluster (like Reddit's), full-mesh demands 499,500 persistent TCP connections running BGP state machines and keepalives.

    Each node's BIRD process must service 999 keepalives every 15–30 seconds. A single flapping node triggers an avalanche of 999 simultaneous route withdraw events across the entire cluster.

    To scale beyond 100 nodes, BGP Route Reflectors (RFC 4456) are mandatory: worker nodes peer only with 2–3 Route Reflectors, converting exponential O(N2)O(N^2) session overhead into linear O(N)O(N) overhead."*

Trap 3: "If worker nodes lose their BGP peering, why does cluster DNS (CoreDNS) immediately fail even if CoreDNS pods are 100% healthy and running?"​

  • Junior/Mid Answer: "CoreDNS must have crashed or run out of memory due to the network spike."
  • Principal Rebuttal:

    *"CoreDNS pods are normal Pods scheduled on specific nodes (often 2–4 replicas across a 1,000-node cluster).

    When BGP peering collapses, each worker node flushes all remote Pod CIDR routes from its Linux kernel FIB. Node A only knows how to route to local Pods on Node A.

    When an application Pod on Node 45 makes a DNS query to 10.96.0.10:53 (the kube-dns ClusterIP), kube-proxy DNATs that virtual IP to a CoreDNS Pod IP residing on Node 2. When the kernel attempts to route that packet, it finds no route to the remote subnet in the FIB and drops the packet with Network is unreachable.

    The CoreDNS pods are completely healthy, but physically unreachable across the partitioned data plane."*

Trap 4: "Once the network route is restored after a 5-hour outage, why shouldn't you immediately restore 100% of public traffic back to the cluster?"​

  • Junior/Mid Answer: "The cluster is healthy and all pods are passing readiness probes, so we should restore traffic immediately to minimize customer downtime."
  • Principal Rebuttal:

    *"Restoring 100% traffic immediately after an extended outage triggers a catastrophic thundering herd and cold-cache stampede:

    1. Cache Eviction: Over 5 hours of downtime, all TTLs in Redis and Memcached clusters have expired.
    2. Database Meltdown: If 100% of user traffic hits the cluster simultaneously, zero requests hit cache. 100% of traffic falls through to the primary database layer, multiplying query load by 50Γ—Β toΒ 100Γ—50\times\text{ to }100\times, immediately exhausting connection pools (HikariCP) and crashing databases.
    3. Client Queue Retries: Mobile apps, web clients, and API callers have millions of pending backoff retries.

    Staff engineers enforce a staged, incremental traffic ramp-up curve (1%β†’5%β†’10%β†’20%β†’35%β†’55%β†’80%β†’100%1\% \to 5\% \to 10\% \to 20\% \to 35\% \to 55\% \to 80\% \to 100\%) while actively monitoring database CPU, cache hit ratios, and connection pool queues."*


3. Mid-Level vs. Principal Candidate Cheat Sheet

Concept / ScenarioMid-Level Candidate SaysPrincipal Candidate Demonstrates
Pod-to-Pod Communication"Kubernetes gives every pod an IP and uses a virtual bridge so they talk like VMs."Details Linux netns, veth pair plumbing, CRI CNI ADD execution, and host kernel FIB routing without NAT.
Calico Architecture"Calico is a CNI plugin that makes networking work without VXLAN."Explains Felix (kernel FIB & iptables/eBPF agent), BIRD (BGP daemon over TCP 179), and IPAM CIDR block delegation.
BGP Scaling"You can run Calico on as many nodes as you want."Quantifies the N(Nβˆ’1)2\frac{N(N-1)}{2} full-mesh connection wall and designs BGP Route Reflector (RFC 4456) topologies to achieve O(N)O(N) scaling.
Cluster Upgrades"Upgrading Kubernetes is safe because rolling updates don't cause downtime."Audits label deprecations (master β†’\to control-plane), API version retirements, and explains why in-place downgrades are impossible due to etcd schema version locks.
Kube-Proxy Data Path"kube-proxy forwards packets to pods like a load balancer."Compares iptables O(N)O(N) sequential chain evaluation against IPVS hash tables and eBPF XDP/tc socket-level DNAT bypass.
Outage Recovery"Flip DNS back to 100% as soon as health checks turn green."Enforces exponential staged traffic ramp-up (1%β†’100%1\% \to 100\%) to prevent cache stampedes, thundering herds, and database connection pool exhaustion.

πŸ“–
Track Page Progress0 / 635 Read
Knowledge Base Completion0%