Cilium’s eBPF-powered networking on K3s is so efficient because it bypasses the kernel’s traditional networking stack, executing networking logic directly in the kernel via eBPF programs.

Let’s see it in action. Imagine you have a K3s cluster running a simple application: a web server pod and a client pod that needs to access it.

# web-server-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web-server
  template:
    metadata:
      labels:
        app: web-server
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

---
# web-server-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web-server
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP

And a client pod:

# client-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: client
spec:
  replicas: 1
  selector:
    matchLabels:
      app: client
  template:
    metadata:
      labels:
        app: client
    spec:
      containers:
      - name: busybox
        image: busybox:latest
        command: ["/bin/sh", "-c", "while true; do sleep 3600; done"]

Once these are deployed on your K3s cluster with Cilium as the CNI, you can exec into the client pod and try to access the web service.

# On your K3s node
kubectl get pods -o wide

# Find the client pod name, e.g., client-xxxxx-yyyyy
CLIENT_POD_NAME="client-xxxxx-yyyyy"
CLIENT_POD_NODE=$(kubectl get pod $CLIENT_POD_NAME -o jsonpath='{.spec.nodeName}')

# Get the cluster IP for the web-service
WEB_SERVICE_IP=$(kubectl get service web-service -o jsonpath='{.spec.clusterIP}')

# Exec into the client pod
kubectl exec -it $CLIENT_POD_NAME -- sh

# Inside the client pod:
wget -O - $WEB_SERVICE_IP

You’ll see the Nginx welcome page. This simple request, however, involves a complex journey. Without Cilium, this packet would traverse the kernel’s iptables and kube-proxy logic, involving multiple context switches and data copies. With Cilium, the packet enters the kernel, and an eBPF program attached to the network interface directly inspects it. Based on Kubernetes Service definitions and pod labels, the eBPF program determines the destination pod and forwards the packet directly, often without ever leaving the kernel’s memory space.

Cilium works by leveraging eBPF (extended Berkeley Packet Filter) programs that run directly within the Linux kernel. These programs are compiled from high-level languages like Go or C and are attached to specific kernel hooks, such as network device ingress/egress points. When a packet arrives at a K3s node, Cilium’s eBPF programs intercept it. They perform crucial tasks like:

  • Service Translation: Mapping ClusterIPs and ports to the actual IP addresses and ports of backend pods. This is done by looking up Service definitions and pod IPs, often stored in efficient eBPF maps.
  • Network Policy Enforcement: Applying granular network security policies defined in Kubernetes NetworkPolicy resources. This allows you to control which pods can communicate with each other based on labels and namespaces, all at the kernel level, providing performance and security benefits.
  • Load Balancing: Distributing incoming traffic across multiple backend pods for a given Service. Cilium’s eBPF can implement efficient load balancing algorithms.
  • Observability: Providing detailed network visibility, including per-pod network metrics, flow logs, and even L7 protocol visibility, all gathered by eBPF programs.

The core of Cilium’s power lies in its ability to move network functions from userspace (like kube-proxy) into the kernel. This drastically reduces overhead. Instead of packets being processed by iptables rules and then passed to kube-proxy for translation, eBPF programs handle it all in one go. This is particularly impactful for high-traffic clusters or latency-sensitive applications.

The cilium-cli tool is your primary interface for managing Cilium. You’ll use it to install Cilium, check its status, and diagnose issues. For instance, to check the status of your Cilium agents on each node, you’d run:

cilium status

This command will show you if the Cilium agent is running, if it’s connected to the Kubernetes API, and if its eBPF programs are loaded correctly on the node’s network interfaces. You can also inspect specific eBPF maps to understand how Cilium is translating Service IPs or enforcing policies:

# On a K3s node where the cilium agent is running
cilium bpf service list
cilium bpf policy list

The first command shows the Service-to-Pod mappings Cilium has established, and the second displays the network policies it’s actively enforcing. This direct inspection of the kernel’s eBPF state is invaluable for debugging.

A key insight into Cilium’s performance is how it manages the lifecycle of eBPF programs. Instead of recompiling and re-injecting entire programs for every minor change in network configuration, Cilium uses eBPF maps as dynamic data stores. These maps are accessible from both userspace (the Cilium agent) and kernel space (the eBPF programs). When a Service or NetworkPolicy changes, the Cilium agent updates the relevant eBPF map entries. The eBPF programs running in the kernel then read these updated values from the maps, allowing for near-instantaneous configuration changes without requiring the eBPF program itself to be reloaded or recompiled. This dynamic data-driven approach is fundamental to Cilium’s agility and performance.

Once you’ve got Cilium running and your basic pods communicating, the next logical step is to explore how to secure your cluster using Network Policies.

Want structured learning?

Take the full K3s course →