Cilium isn’t just another Kubernetes networking plugin; it’s a fundamentally different approach to enforcing network policies, leveraging eBPF to make your cluster’s network security invisible and incredibly performant.
Let’s see it in action. Imagine we have two deployments, frontend and backend, and we want to ensure that frontend can only talk to backend on port 8080.
First, we need a basic Cilium installation. If you’re using Helm, it’s straightforward:
helm install cilium cilium/cilium --version 1.14.5 \
--namespace kube-system \
--set ipam.mode=kubernetes \
--set kubeProxyReplacement=strict \
--set k8sServiceHost="$(kubectl -n kube-system get pod -l k8s-app=cilium -o jsonpath='{.items[0].spec.nodeName}')" \
--set k8sServicePort="$(kubectl -n kube-system get service cilium -o jsonpath='{.spec.ports[0].port}')" \
--set tunnel=vxlan \
--set autoDirectNodeRoutes=true
This sets up Cilium with Kubernetes IPAM, replaces kube-proxy for better performance, and uses VXLAN for pod-to-pod tunneling.
Now, let’s create our sample applications.
frontend-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
backend-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: httpd
image: httpd:latest
ports:
- containerPort: 8080
Apply them:
kubectl apply -f frontend-deployment.yaml
kubectl apply -f backend-deployment.yaml
By default, Kubernetes networking allows all pods to talk to each other. Let’s verify this. Get the IP of a backend pod:
BACKEND_POD_IP=$(kubectl get pods -l app=backend -o jsonpath='{.items[0].status.podIP}')
From a frontend pod (you’ll need to exec into it), try to curl the backend:
# First, get a frontend pod name
FRONTEND_POD_NAME=$(kubectl get pods -l app=frontend -o jsonpath='{.items[0].metadata.name}')
# Then exec into it and curl
kubectl exec -it $FRONTEND_POD_NAME -- curl http://$BACKEND_POD_IP:8080
This should succeed. Now, let’s enforce our policy using Cilium’s CiliumNetworkPolicy.
frontend-to-backend-policy.yaml:
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "frontend-to-backend"
spec:
endpointSelector:
matchLabels:
app: frontend
ingress:
- fromEndpoints:
- matchLabels:
app: backend
toPorts:
- ports:
- port: "8080"
protocol: TCP
Apply this policy:
kubectl apply -f frontend-to-backend-policy.yaml
Now, try the curl command again from the frontend pod. It will fail with a connection refused or timeout error. The traffic is being dropped by Cilium’s eBPF programs.
Let’s analyze the mental model. Traditional Kubernetes Network Policies operate by injecting iptables rules into nodes. This can become a performance bottleneck as the number of policies and pods grows, leading to complex and slow rule chains. Cilium, on the other hand, uses eBPF programs that are directly attached to network interfaces (or sockets). These programs can inspect and manipulate network packets at a much lower level, enabling very granular and high-performance policy enforcement.
Instead of iptables, Cilium uses a combination of eBPF programs and a user-space agent. The eBPF programs, loaded into the kernel, make decisions about packet forwarding, dropping, or modification based on policy rules. These rules are managed by the Cilium agent running as a DaemonSet on each node. When you apply a CiliumNetworkPolicy, the agent translates it into eBPF bytecode and loads it into the kernel.
The key advantage is that eBPF allows for "bypassing" the traditional kernel networking stack for policy decisions. Packets can be processed directly by eBPF, and if they are allowed, they can be forwarded to their destination without going through many layers of iptables. This significantly reduces latency and increases throughput.
Consider the endpointSelector. This targets pods that have the label app: frontend. The ingress section defines what traffic is allowed into these frontend pods. The fromEndpoints specifies that the traffic must originate from pods matching app: backend. Finally, toPorts restricts this allowed ingress traffic to only TCP port 8080.
The surprising part is how Cilium achieves this without explicit packet filtering for allowed traffic. By default, if no policy applies to a pod, all traffic is allowed. When you introduce a CiliumNetworkPolicy, Cilium’s eBPF programs start enforcing allow rules. If a packet doesn’t match any explicit allow rule for the destination pod, it’s implicitly dropped. This "default deny" behavior for traffic not explicitly permitted is a powerful security posture.
This means that even if you have a default-allow policy for a pod, adding a more specific CiliumNetworkPolicy that only allows traffic from a certain source on a specific port will override the default and restrict traffic accordingly. Cilium’s policy model is additive in terms of defining what is allowed.
The next logical step is to explore how to implement service-level policies, allowing traffic to Kubernetes Services instead of just individual pods, and how to integrate with external network security tools.