Calico
Calico (also “Project Calico”) is the most widely deployed Container Network Interface (CNI) plugin for Kubernetes that pairs BGP-routed networking with a full Kubernetes NetworkPolicy enforcement engine, both running in a per-node agent named Felix. It was originally built at Metaswitch Networks around 2015, then stewarded by the spin-off Tigera (“Tigera is the creator and maintainer of Project Calico,” per docs.tigera.io). Unlike most CNIs, Calico’s design choice is pure L3 routing without overlay encapsulation in its default mode: every Pod IP is a real BGP-advertised /32 route, exchanged among nodes by an embedded BIRD (bird.network.cz) BGP daemon, and the host’s Linux routing table is what carries cross-node packets — no VXLAN, no IP-in-IP, no encapsulation overhead. Calico also supports overlay modes (IP-in-IP and VXLAN) for fabrics that won’t propagate Pod routes (docs.tigera.io), so the plugin works whether or not the underlay is BGP-aware. The NetworkPolicy side ships both the stock Kubernetes
networking.k8s.io/v1NetworkPolicy(see NetworkPolicy) and Calico’s richerprojectcalico.org/v3resources — including the cluster-scopedGlobalNetworkPolicythat supports explicit Deny rules, rule ordering, log actions, namespace-spanning selectors, and ServiceAccount-based matching (docs.tigera.io). The standard data plane uses iptables; since 2020 Calico has shipped an eBPF data plane as an opt-in alternative (still not the default as of v3.32 — see docs.tigera.io — enabling eBPF). In eBPF mode Calico replaces kube-proxy entirely with in-kernel eBPF programs, delivering O(1) service lookup, native source-IP preservation, and a roughly 1.5–3× CPU reduction at high pps (thenewstack.io). Calico is the policy companion to Flannel in the popular “Canal” combo deployment, the recommended policy engine alongside AWS VPC CNI on EKS, and a first-class option on most managed Kubernetes services. Calico is not a CNCF-hosted project — verified against the CNCF projects directory (as of 2026-05-30, Calico does not appear in graduated, incubating, or sandbox lists); it is an independent open-source project (Apache 2.0) stewarded by Tigera. The “CNCF” confusion is common because Calico implements the CNI specification, and CNI itself is a CNCF project — but the spec and its implementations are governed separately. Current latest release: v3.32.0 (released 2026-04-30, per github.com/projectcalico/calico/releases). This note covers the architecture (Felix, BIRD, confd, Typha, the CNI/IPAM plugins), the three data-path modes, the policy model, and where Calico does and does not fit.
Mental Model
flowchart TB subgraph N1["Node 1 (10.0.1.10)"] subgraph N1NS["Pod A network namespace"] PA[Pod A<br/>10.244.1.5/32] end VETH1[cali1234 veth<br/>host endpoint<br/>no bridge] FELIX1[Felix agent<br/>programs iptables/eBPF<br/>+ host routes] BIRD1[BIRD BGP daemon<br/>advertises 10.244.1.5/32] IPT1[iptables / nftables / eBPF<br/>NetworkPolicy rules] KERN1[Linux kernel<br/>routing table:<br/>10.244.1.5/32 → cali1234<br/>10.244.2.7/32 → 10.0.1.11] PA --- VETH1 end subgraph N2["Node 2 (10.0.1.11)"] subgraph N2NS["Pod B network namespace"] PB[Pod B<br/>10.244.2.7/32] end VETH2[cali5678 veth] FELIX2[Felix] BIRD2[BIRD<br/>advertises 10.244.2.7/32] KERN2[Linux kernel<br/>routing table:<br/>10.244.2.7/32 → cali5678<br/>10.244.1.5/32 → 10.0.1.10] PB --- VETH2 end BIRD1 -- "BGP iBGP peering<br/>(or via route reflector,<br/>or to ToR switches)" --- BIRD2 DS[(Datastore<br/>Kubernetes CRDs<br/>OR direct etcd)] TYPHA[Typha fanout proxy<br/>1 connection per Felix<br/>caches API server load] KCC[kube-controllers<br/>watches Pods, Nodes,<br/>NetworkPolicy → datastore] CNI[Calico CNI plugin<br/>/opt/cni/bin/calico<br/>+ calico-ipam] FELIX1 -- watch --> TYPHA FELIX2 -- watch --> TYPHA TYPHA -- watch --> DS KCC --> DS CNI -- "Pod ADD/DEL<br/>via gRPC/UDS" --> FELIX1 KERN1 -- packet --> KERN2
What this diagram shows. A Pod-to-Pod packet path in Calico’s default BGP routed mode. Two key absences are visible: there is no bridge (Calico does not use cni0) and no overlay (the packet from Pod A to Pod B does not get encapsulated in VXLAN or IP-in-IP — it travels as a plain IPv4 packet between nodes). The per-Pod host-side veth (cali1234, cali5678) is its own L3 endpoint: the kernel routes 10.244.1.5/32 to cali1234 on Node 1, and Felix has programmed a /32 route on Node 1 saying 10.244.2.7/32 → 10.0.1.11 (Node 2’s IP). That /32 route on Node 1 was learned via BGP: BIRD on Node 2 advertises the routes Felix programs for its local Pods, and BIRD on Node 1 receives them and installs them into the kernel routing table. The right-hand side shows the control plane: Felix watches Calico’s datastore (either the Kubernetes API via CRDs, or etcd directly) through the Typha fanout proxy — a critical scalability piece, because without Typha every Felix in a 1000-node cluster opens its own watch against the API server, which is then DDoS’d by its own controllers. The CNI plugin (lower-left) is the binary invoked by containerd/CRI-O during Pod sandbox creation; it talks to the local Felix agent to wire up the veth. The insight to extract: Calico’s data path is plain Linux routing. There is no Calico-specific kernel module, no proprietary tunneling protocol, nothing exotic in the packet’s path. Each node’s kernel routing table simply has a /32 entry for every Pod IP in the cluster, kept up to date by BGP. This makes Calico’s data path remarkably observable (ip route, iptables -L) and remarkably aligned with how data-center networks already work — both strengths in production.
Mechanical Walk-through
The component cast
The five core components (docs.tigera.io):
- Felix — the per-node agent. Runs as a DaemonSet (
calico-nodepod, one per node). Felix’s responsibilities: (a) reading desired state from the datastore (Pods scheduled to this node, NetworkPolicies, IP pools); (b) programming the Linux routing table to install routes for local Pods; (c) programming iptables (or, in eBPF mode, eBPF programs attached via TC) to enforce NetworkPolicy; (d) interfacing with BIRD (writes routes that BIRD will export); (e) reporting health back to the datastore. Felix is the workhorse — when something goes wrong, Felix’s logs are where you look. - BIRD — the open-source BGP daemon (bird.network.cz). Runs in the same
calico-nodepod as Felix. Its job: maintain BGP sessions to other nodes (full mesh by default, or via route reflectors for larger clusters, or to a real BGP fabric like ToR switches), import routes Felix has programmed into its export filter, and learn routes from peers. BIRD is optional: in pure VXLAN mode, BIRD is not used (Felix programs the cross-node routes directly, since VXLAN doesn’t need BGP to discover peer IPs). - confd — a template engine that watches the datastore for BGP and network configuration changes and regenerates BIRD’s config file on the fly. confd is the bridge between Calico’s CRD-shaped data model and BIRD’s traditional config-file format.
- Typha — a fanout proxy. Typha opens one watch per Pod against the kube-apiserver (or etcd), caches the state, and serves it to all Felix instances on all nodes. Without Typha, in a 1000-node cluster the API server has to fan out every NetworkPolicy update 1000 times; with Typha, it fans out once to Typha, and Typha multiplies. Required for clusters with more than ~50 nodes.
- CNI plugin + IPAM plugin — the binaries dropped into
/opt/cni/bin/. The runtime invokes/opt/cni/bin/calicoper the Container Network Interface spec on Pod sandbox creation; the calico binary talks to the local Felix (via a Unix socket) to wire up the veth and register the new endpoint. The IPAM plugin (/opt/cni/bin/calico-ipam) is Calico’s own IP-allocation logic, backed by IP Pools (cluster-scoped resources) — alternatively, the standardhost-localIPAM can be used.
Two more, less central:
- kube-controllers — watches Kubernetes API for Pods, Nodes, Namespaces, ServiceAccounts, NetworkPolicies and translates them into Calico’s datastore. Only needed when the datastore is etcd-direct; in kube-datastore mode, kube-controllers does fewer things.
- calico-apiserver — an Aggregated API Server that exposes Calico’s
projectcalico.org/v3resources viakubectl(sokubectl get globalnetworkpolicyworks). Optional but recommended.
Datastore options: Kubernetes CRDs vs etcd direct
Calico supports two storage backends (docs.tigera.io):
- Kubernetes datastore (default, recommended). Calico’s resources are stored as CRDs in the Kubernetes API. This is the “Kubernetes-native” mode — operators see Calico resources alongside Pods/Services/etc.; RBAC is integrated; no extra etcd to manage.
- etcd direct. Calico talks to an etcd cluster of its own. Used in non-Kubernetes platforms (OpenStack, bare metal) or in very large clusters where the team wants Calico’s load isolated from the K8s control plane’s etcd.
The kube-datastore is the right default for almost everyone; etcd-direct is a legacy/specialty option.
The three data-path modes
Calico can run in any of three forwarding modes, configured per-IP-pool (docs.tigera.io):
- BGP-routed (no encapsulation, default). The mode shown in the diagram. Each Pod IP is a
/32advertised by BGP; cross-node traffic is plain IPv4. Requires: the underlay network must propagate Pod routes (either by peering with ToR switches or by all nodes being L2-adjacent so iBGP works). Best performance, lowest overhead. Cannot be used on cloud underlays that filter unknown source/dest IPs (AWS VPC by default does — see “EKS” below). - IP-in-IP encapsulation. Each cross-node packet is wrapped in an outer IP packet whose source/dest are the node IPs; the inner packet has the Pod IPs. IP protocol number 4 (
ipencap). 20-byte overhead. Used when the underlay can’t see Pod routes (AWS, GCE) but supports IPIP (which most do). Felix still uses BGP to distribute routes; the encap is just at the data plane. - VXLAN encapsulation. Cross-node traffic is wrapped in VXLAN (UDP port 4789). 50-byte overhead. Better than IP-in-IP on networks that filter IPIP (some AWS VPCs do unless the security groups explicitly allow protocol 4). With VXLAN, Felix can do its own route distribution without BIRD — VXLAN doesn’t need BGP, just a list of node IPs to tunnel to, which Felix learns directly from the datastore. This is the “no BGP” mode.
The choice is per-IP-pool, so a single cluster can mix modes — e.g., a “fast” pool on BGP-capable underlay and a “fallback” pool with VXLAN.
NetworkPolicy: K8s + Calico’s own
Calico enforces standard Kubernetes networking.k8s.io/v1 NetworkPolicy objects (docs.tigera.io). On top of that, it adds projectcalico.org/v3 resources with strict supersets of stock features:
NetworkPolicy(Calico’s namespaced version) — addsorderfield (explicit rule ordering, lower numbers evaluated first),DenyandLogactions, ServiceAccount-based matching, and richer selectors.GlobalNetworkPolicy— cluster-scoped (not namespaced), can match endpoints across all namespaces and HostEndpoints (i.e., the nodes themselves, not just Pods) (docs.tigera.io).NetworkSet— a named set of IP CIDRs that policies can reference; replaces the awkward inlineipBlocklists when many policies use the same external CIDRs.HostEndpoint— make a node itself a policy target. Lets Calico firewall the node’s host interface, not just Pod interfaces. Used for host-level egress control.
Calico’s policies are then translated by Felix into kernel-level rules (iptables in the standard data plane, eBPF programs in the eBPF data plane), enforced before the packet leaves the source endpoint.
The eBPF data plane
Calico’s optional eBPF data plane (docs.tigera.io, thenewstack.io) replaces both:
- iptables for NetworkPolicy. Felix compiles each policy into an eBPF program attached to the TC (traffic control) ingress/egress hooks of each endpoint’s veth. Per-packet evaluation is O(1) in a hash map lookup rather than O(n) in an iptables chain.
- kube-proxy. Calico implements Service load balancing in eBPF: when a Pod sends a packet to a ClusterIP, an eBPF program at the socket layer rewrites the destination to a backend Pod IP before the packet is encoded — so the data path never touches kube-proxy’s iptables/IPVS rules. Calico recommends disabling kube-proxy entirely when running in eBPF mode to save resources and avoid the two implementations stepping on each other.
The eBPF data plane preserves the source IP: in iptables mode, Service traffic ends up SNAT’d to the node IP by default, so backends see the node IP rather than the original client IP. In eBPF mode, the source IP is preserved natively — useful for backends that need real client IPs for audit/locality.
Benchmarks on a 40 Gbps fabric show eBPF mode at ~1.5–3× the iptables mode’s throughput at lower CPU (superorbital.io) for high-pps workloads; for low-rate traffic the difference is negligible.
EKS deployment patterns
On Amazon EKS, the default CNI is AWS VPC CNI (not Calico). Calico is most commonly deployed on EKS in one of two modes (docs.tigera.io):
- Policy-only mode alongside VPC CNI (the recommended pattern). The VPC CNI provisions Pod IPs from VPC secondary ENI addresses; Calico runs only Felix (no IPAM, no BIRD) to enforce NetworkPolicy on top. Best of both worlds: native VPC routing + rich policy.
- Full CNI replacement. Calico replaces VPC CNI entirely, using VXLAN encapsulation (since VPCs don’t propagate Pod routes). Used when Pod IP density matters more than VPC-native semantics — VPC CNI caps Pods per node at the ENI-IP limit of the instance type.
The dispatch prompt’s claim that “Calico is the default CNI in many AWS-EKS scenarios” is not correct in 2026 — VPC CNI is the EKS default and policy-only Calico alongside is the recommended add-on.
Configuration / API Surface
A minimal GlobalNetworkPolicy example
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
name: deny-egress-to-internet
spec:
order: 1000 # (1) lower numbers evaluated first;
# this rule is low-priority
selector: 'all()' # (2) Calico selector: 'all()' = every endpoint
types: # (3) directions this policy covers
- Egress
egress:
- action: Allow # (4) first: allow intra-cluster
destination:
selector: 'projectcalico.org/namespace in {"default", "shop"}'
- action: Allow # (5) allow DNS
destination:
services:
name: kube-dns
namespace: kube-system
- action: Deny # (6) explicit Deny — not available in
destination: # stock K8s NetworkPolicy
nets:
- 0.0.0.0/0orderis Calico-specific: explicit numeric priority across policies. Lower order means evaluated earlier; if no rule matches, Calico’s default-deny kicks in.- Calico selectors support a tiny expression language including
all(),has(label),not in,&&,||— strictly richer than stock K8s NetworkPolicy’smatchLabels/matchExpressions. types: [Egress]means this policy only affects outgoing traffic; ingress is unaffected.- First rule: allow traffic between specific namespaces, identified via the
projectcalico.org/namespacesynthetic label. - Second rule: allow traffic to the kube-dns Service (Calico can express “to a Service” directly; stock K8s NetworkPolicy can’t).
- Third rule: explicit
action: Deny. This is the distinguishing Calico feature — stock K8s NetworkPolicy only allows allow-list semantics; there’s no Deny verb in the upstream API.
A stock K8s NetworkPolicy (also enforced by Calico)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-allow-from-api
namespace: shop
spec:
podSelector:
matchLabels:
app: web
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app: api
ports:
- protocol: TCP
port: 8080When Calico is the CNI, this policy is enforced by Felix at the same layer as GlobalNetworkPolicy. Calico evaluates K8s NetworkPolicy and Calico NetworkPolicy in a unified pipeline; ordering and precedence rules are documented at docs.tigera.io.
Installation (operator-based)
# Calico's operator (tigera-operator) — the modern install path
$ kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/tigera-operator.yaml
$ kubectl create -f - <<EOF
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
name: default
spec:
calicoNetwork:
bgp: Enabled # use BGP routed mode
ipPools:
- cidr: 10.244.0.0/16
encapsulation: None # no IP-in-IP, no VXLAN
EOFFor VXLAN-only (no BGP) installs, change bgp: Disabled and encapsulation: VXLAN.
Failure Modes
- BGP peer flap → cluster network outage. In BGP-routed mode, if BIRD on one node loses its BGP session, the kernel withdraws all routes from that peer and Pods on the affected nodes become unreachable. Symptom: sudden cross-node Pod-to-Pod failure on specific node pairs. Diagnostic:
kubectl exec -n calico-system <calico-node-pod> -- calicoctl node statusshows BGP peer health. Fix: investigate underlying network — usually transient packet loss or MTU mismatch on the underlay. - Typha overload at 1000+ nodes. Without Typha, Felix’s watches DDoS the API server. With Typha but insufficiently sized, Typha itself becomes the bottleneck. Symptom: NetworkPolicy updates take minutes to propagate to all nodes. Diagnostic:
kubectl top pod -n calico-system | grep typha+ Typha’s own metrics endpoint. Fix: scale the Typha Deployment; 1 Typha replica per ~200 Felix is a starting heuristic. - IP pool exhaustion. A node’s Pod CIDR allocation from the IP pool runs out (Calico allocates
/26blocks per node from the pool’s CIDR by default). Symptom: Pods stuck in ContainerCreating with “no IPs available.” Diagnostic:calicoctl ipam show --show-blocks. Fix: enlarge the IP pool (irreversible — requires migration), or tune the per-node block size. - NetworkPolicy ordering surprise. Stock K8s policies are merged into one allow-list per Pod; Calico policies have explicit
order. If aGlobalNetworkPolicywithorder: 100denies traffic that a stock K8s policy was implicitly allowing, the deny wins (lower order). Diagnostic:calicoctl get networkpolicy --all-namespaces -o yamland trace the order numbers. Fix: bump the relevant Calico policy’sorderabove the relevant range, or split into more specific rules. - iptables rule explosion on large clusters. In standard data plane mode, every NetworkPolicy generates iptables rules. With thousands of policies and tens of thousands of Pods, iptables can exceed kernel limits or become so slow that conntrack overflows. Symptom: high CPU on
xtables, dropped packets at high pps. Diagnostic:iptables -L | wc -lper node. Fix: switch to eBPF data plane. - Encapsulation MTU mismatch. IP-in-IP adds 20 bytes; VXLAN adds 50. If Pod MTU is the default 1500 but the underlay only supports 1500, encapsulated packets fragment or get black-holed. Symptom: small flows work, large packet bursts fail (classic MTU pathology). Diagnostic: ping with
-M do -s <size>from inside Pod. Fix: set Pod MTU to underlay MTU minus the encap header size. - eBPF mode without disabling kube-proxy. Both Calico-eBPF and kube-proxy program the same service rules; the order in which they’re hit depends on TC vs iptables ordering, leading to non-deterministic behavior. Symptom: random service-routing failures. Fix: when enabling eBPF, simultaneously disable kube-proxy (
kubectl -n kube-system delete ds kube-proxy). GlobalNetworkPolicyaccidentally blocks the kubelet’s traffic. Aselector: 'all()'egress-deny policy onHostEndpoints will kill the node’s kubelet, control-plane connections, and SSH access. Symptom: node goes NotReady, can’t be drained. Recovery: console access to the node +iptables -Fto flush rules, then fix the policy.
Alternatives and When to Choose Them
- Cilium — the modern competitor. eBPF-native from day one; richer L7 policy (HTTP methods, gRPC), built-in observability (Cilium Service Mesh, Cilium’s Hubble). For new clusters that prioritize eBPF and observability, Cilium is the more aggressive choice. Calico is the more conservative one — iptables data plane works on every kernel, BGP integration aligns with traditional DC networking.
- Flannel — strictly less. No policy enforcement, simpler operations. Use Flannel for dev/test clusters; pair with Calico-policy-only for “Canal” if you need policies but don’t want Calico’s full data plane.
- Weave Net — historical. Calico subsumed all its capabilities; Weave’s mesh-VXLAN is simpler to operate than Calico’s BGP but Weave is no longer recommended (the project entered maintenance mode after Weaveworks shut down in February 2024).
- AWS VPC CNI — on EKS, the default. Calico in policy-only mode is the typical add-on. Use VPC CNI when you need security-group-per-Pod, VPC-native IPs, and ENI-based scaling — and add Calico for the policy engine VPC CNI lacked until Amazon released its own eBPF-based network policy agent in 2023.
- GKE Dataplane V2 — GKE’s Cilium-powered default. On GKE, Cilium is more idiomatic than Calico.
- kube-router — like Calico-BGP but lighter (no NetworkPolicy framework, smaller code base). Niche.
- Antrea — OVS-based, VMware-stewarded. Plays well with vSphere-native environments. Smaller community than Calico/Cilium.
Production Notes
- Cisco’s Calico Network Design (cisco.com) — Cisco’s reference design for Calico-on-NX-OS with iBGP/eBGP peering to top-of-rack switches. Shows how Calico becomes a first-class participant in a data-center fabric, not just a Kubernetes-internal concern.
- Tigera’s eBPF benchmarks (thenewstack.io, superorbital.io) — independent reproductions confirm 1.5–3× throughput improvement at the same CPU on high-pps workloads. The CPU-per-Gbit reduction is the more reproducible result; raw throughput depends heavily on test conditions.
- kops support (kops.sigs.k8s.io) — the kops tool ships first-class Calico support; this is how many self-managed AWS clusters end up running Calico. Cluster API and kubeadm also commonly install Calico.
- Scale ceilings — Calico has been demonstrated at 10,000+ nodes (tigera.io cites 8M+ daily-active nodes worldwide across 166 countries). Typha is essential at scale; BIRD-mesh peering doesn’t scale much past ~50 nodes without route reflectors.
- License and stewardship — Apache 2.0; corporate steward is Tigera. Tigera also sells “Calico Enterprise” and “Calico Cloud” — commercial supersets with additional features (DNS policy, microsegmentation visualization, threat-defense). The open-source Calico is fully functional; the commercial versions are operational enhancements, not gated features.
See Also
- Container Network Interface — the plugin spec Calico implements
- Pod Networking — the universal mechanism Calico participates in
- Kubernetes Networking Model — the contract Calico delivers
- NetworkPolicy — the K8s resource Calico enforces
- kube-proxy / kube-proxy Modes — what Calico’s eBPF mode replaces
- Cilium — the modern alternative; eBPF-native sibling
- Flannel / Weave Net — simpler / older CNIs
- AWS VPC CNI / Azure CNI / GKE Dataplane V2 — cloud-native alternatives
- Cloud Native Computing Foundation — what Calico is not part of (despite confusion)
- Raft / etcd — if running Calico with the etcd-direct datastore
- Kubernetes MOC — umbrella index