General
The Cilium eBPF Connection Tracking Overflow: Why Fixed Map Limits Silently Drop Packets in Large Kubernetes Clusters
calricon / field note
Cilium and eBPF are hailed as the high-performance successor to iptables and kube-proxy in cloud-native Kubernetes networking. In high-density production clusters, however, fixed-size eBPF connection tracking maps routinely exhaust entry allocations, causing the kernel datapath to silently drop incoming TCP connections with zero visibility. In this deep dive, Calricon breaks down the mechanics of BPF map allocation and provides a production blueprint for zero-drop networking.
The Cilium eBPF Connection Tracking Overflow: Why Fixed Map Limits Silently Drop Packets in Large Kubernetes Clusters
Across modern platform engineering and Kubernetes operations, Cilium has achieved near-universal acclaim as the gold standard for cloud-native networking, security, and observability. By replacing legacy iptables and kube-proxy routing chains with Linux kernel extended Berkeley Packet Filter (eBPF) bytecode, Cilium promises O(1) routing lookups, sub-millisecond network latency, transparent encryption, and carrier-grade scalability across thousands of nodes.
For organizations migrating away from iptables rule explosions, the performance improvement initially feels miraculous. Cluster networking overhead drops, pod-to-pod throughput climbs, and service routing runs directly inside the kernel's network datapath. Then comes the enterprise scale-up: a major product launch, a high-frequency trading burst, or a sudden surge in microservice connection concurrency.
Without warning, microservices across the cluster begin experiencing sporadic, catastrophic connection timeouts. Newly spawned pods fail health checks, ingress controllers drop 15% of incoming customer requests with 504 Gateway Timeouts, and API gateways report connection resets. Yet cluster CPU utilization is nominal, node memory is plentiful, and the Linux kernel logs zero OOM kills. When SREs inspect standard Linux network statistics (netstat, conntrack -S), the operating system reports that host-level connection tracking tables are nearly empty.
The true culprit is executing inside Cilium's custom kernel datapath: the silent overflow of fixed-size eBPF connection tracking (CT) maps. Unlike standard Linux networking that dynamically pages memory or user-space proxies that allocate heap structures, eBPF maps operate under rigid compile-time memory constraints. When connection concurrency exceeds the pre-allocated entry limit of Cilium's BPF hash maps, the kernel datapath immediately drops incoming TCP SYN packets on the floor with zero kernel ring buffer warnings. Here is the internal kernel mechanics of Cilium's eBPF map sizing, why standard Linux metrics hide the crisis, and how Calricon hardens large-scale Kubernetes clusters against eBPF packet loss.
The Physics of eBPF Datapaths: Why Memory Is Fixed
To understand why eBPF drops packets silently under load, you have to understand the fundamental architectural trade-offs enforced by the Linux eBPF verifier.
In traditional Linux networking with iptables, the kernel relies on nf_conntrack (Netfilter Connection Tracking). Netfilter allocates state dynamically in kernel memory. If connection volume surges, Netfilter can allocate additional memory pages up to net.netfilter.nf_conntrack_max.
Cilium bypasses Netfilter entirely, executing custom eBPF programs attached directly to network interface hooks (such as Traffic Control tc or eXpress Data Path XDP). To guarantee safe, deterministic execution inside the kernel without causing kernel panics or unbounded memory loops, the eBPF subsystem enforces strict rules on BPF Maps (the shared in-memory data structures used by eBPF programs to track state):
- Fixed Allocation at Load Time: When the Cilium daemon initializes on a worker node, it compiles and loads eBPF maps into kernel memory. Crucially, every BPF map must define its maximum entry capacity (
max_entries) upfront. - The Hash Map Structure: Cilium tracks active connections using BPF hash maps, primarily
cilium_ct4_global(for IPv4 TCP/UDP connections) andcilium_ct_any4_global(for non-TCP protocols). - Default Sizing Constraints: By default, Cilium configures the global connection tracking map with a fixed ceiling: typically 512,000 entries per node (governed by
bpf-ct-global-tcp-max).
In modest clusters, 512,000 concurrent connection entries feels like an astronomical number. But in modern high-density Kubernetes nodes—hosting 110 pods, running high-concurrency microservices, handling short-lived HTTP/1.1 connections, or executing distributed tracing pipelines—connection tracking allocations deplete at blinding speed.
The Trap: How bpf_map_update_elem Triggers Drop Reason 135
When an incoming TCP SYN packet arrives at a node's physical network interface, Cilium's eBPF program intercepts the packet at the tc ingress hook. The datapath executes the following operational sequence:
- The eBPF program extracts the four-tuple connection signature: source IP, source port, destination IP, and destination port.
- It queries the
cilium_ct4_globalBPF hash map to check whether this packet belongs to an existing established connection. - Because this is a new TCP SYN packet, no entry exists. The program must register a new state entry using the kernel helper function
bpf_map_update_elem(). - The Map Exhaustion Failure: If the number of active connection records has reached the pre-configured
max_entriesceiling, the hash map cannot accept new keys. The kernel helper fails immediately and returns error code-E2BIG(Argument list too long) or-ENOSPC(No space left on device). - The Silent Drop: Because an eBPF program executing in the packet processing path cannot block, cannot sleep, and cannot dynamically request more heap memory, it has only one deterministic fallback: it drops the packet immediately.
/* Inside Cilium eBPF datapath (simplified) */
ret = bpf_map_update_elem(&cilium_ct4_global, &tuple, &entry, BPF_NOEXIST);
if (ret < 0) {
/* Map is full: drop packet immediately */
return send_drop_notify(ctx, DROP_CT_TABLE_FULL);
}
The packet is discarded before it ever reaches the Linux TCP stack. From the perspective of the kernel's standard networking subsystem, the packet never existed.
Calricon Rule of Architecture: Traditional Linux network monitoring tools are completely blind to eBPF datapath drops. When Cilium's connection tracking map fills up,
dmesglogs nothing,/proc/net/snmprecords zero TCP drops, and standard Netfilter counters remain flat. If you are not actively monitoring Cilium-specific BPF drop metrics, your cluster is dropping production traffic in complete darkness.
Catastrophe #1: The Phantom Outage and Partial Availability Collapse
The most deceptive aspect of a BPF connection tracking overflow is that it creates an insidious partial outage rather than a clean, obvious cluster crash:
- Existing Connections Survive: Established long-lived TCP connections (e.g., active database connection pools, gRPC persistent streams, or open Kafka consumer channels) already have entries in the
cilium_ct4_globalmap. Lookups on these existing keys succeed instantly. These connections continue operating with blazing sub-millisecond latency. - New Connections Drop: Any attempt to establish a new connection—such as an incoming customer HTTP request, a Kubernetes readiness probe ping, a DNS resolution request, or a newly autoscaled pod connecting to a cache—requires a new CT map entry. These packets are dropped silently.
This creates complete diagnostic chaos. SREs logging into worker nodes can successfully ping databases and curl external APIs using pre-warmed persistent connections, declaring the node healthy. Meanwhile, upstream load balancers are evicting the node because newly initiated HTTP health checks are failing with 100% packet loss.
Catastrophe #2: The Garbage Collection Lag and Ephemeral Port Reuse Glare
Why doesn't Cilium simply remove closed connections from the map?
Cilium does clean up closed connections, but it does so via an asynchronous userspace garbage collection loop managed by the cilium-agent daemon. By default, the garbage collector scans the BPF map periodically (every 12 seconds, governed by bpf-ct-cleanup-interval).
When a microservice opens and closes thousands of short-lived TCP connections per second (e.g., calling an external REST API without connection keep-alive):
- TCP connections transition to
FIN_WAIT,TIME_WAIT, orCLOSED. - In standard Linux, the kernel marks these sockets dead. But in Cilium, the BPF map entry remains occupied until the background garbage collector runs its next scan.
- If an application bursts 600,000 short-lived connections in a 5-second window, the connection tracking map hits 100% saturation and begins dropping traffic, even though 90% of those connections are already closed in application space.
- State Glare / Port Collision: If a client microservice rapidly recycles an ephemeral port before Cilium's garbage collector sweeps the stale tuple, the new SYN packet collides with the lingering BPF CT entry. Cilium misinterprets the packet as an invalid out-of-sequence frame and drops it with Drop Reason: CT: Invalid State.
Catastrophe #3: The Multi-Service IPCache Map Thrashing
Connection tracking is not the only fixed-size map inside Cilium. A second critical failure vector is the IPCache Map (cilium_ipcache), which maps every IP address in the cluster to its corresponding security identity for Cilium Network Policy enforcement.
In large-scale clusters running dynamic Kubernetes workloads (thousands of pods spinning up and down under Horizontal Pod Autoscalers, coupled with external services):
- Every pod IP, node IP, and CIDR block must occupy an entry in
cilium_ipcache. - If the cluster scale exceeds
bpf-ipcache-map-max(historically defaulted to 512,000 entries), new pods cannot be registered in the identity cache. - When a newly scheduled pod attempts to send network traffic, Cilium cannot resolve its security identity. The eBPF policy engine classifies the pod as identity
WORLDorUNKNOWNand drops all egress packets under default-deny security policies.
Diagnostic Playbook: Exposing eBPF Drops in Real Time
How do you verify whether Cilium is actively dropping packets due to map exhaustion? Calricon recommends this three-step diagnostic workflow:
1. Inspect Real-Time BPF Datapath Drops via Cilium CLI
Execute the low-level datapath monitor on an affected Kubernetes node:
# Exec into the cilium daemon pod on the troubled node
kubectl exec -n kube-system ds/cilium -c cilium-agent -- cilium-dbg monitor --type drop
# Look specifically for:
# xx drop (CT: Map full) flow 0x0 to-endpoint 0, identity 1->2: 10.244.1.42:58392 -> 10.244.2.15:8080 tcp SYN
# Drop reason 135: DROP_CT_TABLE_FULL
If you see DROP_CT_TABLE_FULL, your connection tracking hash map has hit its physical ceiling.
2. Query BPF Map Fill Ratios
Check the current capacity and utilization of Cilium's kernel maps directly:
kubectl exec -n kube-system ds/cilium -c cilium-agent -- cilium-dbg bpf ct list global | wc -l
# Compare this against your configured ceiling:
kubectl exec -n kube-system ds/cilium -c cilium-agent -- cilium-dbg status | grep "Connection Tracking"
3. Monitor Prometheus Alert Metrics
Ensure your observability stack alerts on Cilium's native datapath drop metrics:
# Prometheus metric tracking eBPF packet drops by reason
sum(rate(cilium_drop_count_total{reason="CT: Map full"}[2m])) by (node) > 0
Calricon’s Production Blueprint for Resilient Cilium Networking
To eliminate eBPF map overflows and guarantee zero-drop networking under massive connection concurrency, Calricon enforces five mandatory architectural standards:
1. Scale Connection Tracking Maps to Match Hardware Capacity
Never run default 512K connection tracking limits on high-density nodes. In modern enterprise nodes equipped with 64GB+ of RAM, allocating memory for larger BPF maps consumes a negligible fraction of memory.
Configure Cilium's Helm values to expand CT map capacity to 1,048,576 or 2,097,152 entries:
# Cilium Helm values configuration
bpf:
ctGlobalTcpMax: 1048576
ctGlobalAnyMax: 524288
mapDynamicSizeRatio: 0.005 # Dynamically scale maps based on node RAM
Setting ctGlobalTcpMax: 1048576 provides sufficient buffer headroom to absorb massive traffic microbursts without hitting the hard drop boundary.
2. Accelerate Connection Tracking Garbage Collection
On write-heavy or connection-dense clusters, do not allow closed connections to linger in the BPF map for 12 seconds. Tune the garbage collection loop to run aggressively:
# Accelerate cleanup frequency
bpf:
ctCleanupInterval: "3s" # Reduce sweep interval from 12s to 3s
ctTimeoutTcpClose: "5s" # Drop CLOSED state entries after 5s
ctTimeoutTcpTimeWait: "10s" # Drop TIME_WAIT entries after 10s
Accelerating the sweep interval sweeps dead entries 4x faster, keeping the active map density low and preventing ephemeral port collision glare.
3. Enforce HTTP/2 and gRPC Connection Keep-Alive
Application architecture is the first line of defense. The primary driver of CT map exhaustion is microservices using un-pooled HTTP/1.1 connections that open and close a new TCP handshake for every RPC.
Mandate that all internal microservice communication utilize gRPC or HTTP/2 connection pooling with persistent keep-alive. Multiplexing thousands of remote procedure calls across a single persistent TCP socket reduces kernel connection tracking churn by over 95%.
4. Expand IPCache and Service Map Boundaries
For clusters scaling past 1,000 nodes or hosting hundreds of thousands of ephemeral pods, expand the identity and service map ceilings:
bpf:
ipcacheMapMax: 1048576
lbMapMax: 131072
5. Verify Linux Kernel Memory Locking Limits (RLIMIT_MEMLOCK)
Ensure that worker nodes running Linux kernels prior to 5.11 explicitly set container memory locking to unlimited. In your containerd or systemd configuration, verify:
# /etc/systemd/system/containerd.service.d/override.conf
[Service]
LimitMEMLOCK=infinity
This prevents Cilium daemon pods from crashing during BPF map initialization on newly provisioned node pools.
The Bottom Line
Cilium and eBPF deliver unmatched throughput and packet processing efficiency, but they do not eliminate the laws of finite computing. When you trade the dynamic, unbounded memory allocations of traditional iptables for the high-speed determinism of in-kernel eBPF bytecode, you must manage your memory boundaries with rigorous precision.
A BPF map overflow does not announce itself with dramatic server crashes; it silently suffocates your microservices behind dropped SYN packets. By calculating your connection concurrency requirements, sizing BPF tables generously, accelerating garbage collection sweeps, and monitoring eBPF drop metrics, you ensure your Kubernetes datapath remains blindingly fast, completely transparent, and immune to silent packet death.