Install K3s without the defaults Cilium will replace
curl -sfL https://get.k3s.io | sh -s - \
--flannel-backend=none \
--disable-network-policy \
--disable-kube-proxy \
--cluster-init
Without a CNI, no pods can communicate — kubectl get pods -A shows the system pods as Pending. That's expected; Cilium provides the CNI.
Install Cilium
# Install the cilium CLI on your workstation
CILIUM_CLI_VER=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
curl -L --remote-name-all \
"https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VER}/cilium-linux-amd64.tar.gz"
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz
# Install Cilium into the cluster, replacing kube-proxy
cilium install \
--version v1.16.5 \
--set kubeProxyReplacement=true \
--set k8sServiceHost=<api-server-host> \
--set k8sServicePort=6443 \
--set hubble.enabled=true \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true
The --set k8sServiceHost matters because without kube-proxy, the cluster has no way to translate the in-cluster kubernetes.default.svc service to a real address — Cilium needs to know the API server's actual location for bootstrapping.
Wait for the rollout:
cilium status --wait
kubectl get pods -A
All system pods should now be Running, and a new kube-system/cilium-* DaemonSet plus cilium-operator Deployment plus hubble-relay + hubble-ui exist.
Verify the replacement
cilium connectivity test
# Runs ~80 tests across pod-to-pod, pod-to-service, pod-to-world, network policies.
# Takes 10-15 minutes; the output is verbose but each test reports pass/fail.
Hubble: the observability UI
Port-forward the Hubble UI:
cilium hubble ui
The browser opens to a real-time flow diagram of every connection in the cluster. Filter by namespace, source/destination pod, verdict (allowed/denied), or DNS query.
CLI alternative:
cilium hubble enable
hubble observe --namespace default --follow
hubble observe --pod default/web --to-port 5432 # narrow filters
hubble observe --verdict DENIED --follow # show only blocked flows
For "what is this app actually talking to right now," Hubble is the single most useful tool you can add to a Kubernetes cluster.
Network policies
Standard networking.k8s.io/v1 NetworkPolicy works, but Cilium's cilium.io/v2 CiliumNetworkPolicy extends it to L7 (HTTP methods, paths, headers, gRPC services, DNS hostnames, Kafka topics):
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: web-to-api
namespace: default
spec:
endpointSelector:
matchLabels:
app: web
egress:
- toEndpoints:
- matchLabels:
app: api
toPorts:
- ports: [ { port: "8080", protocol: TCP } ]
rules:
http:
- method: GET
path: "/v1/.*"
- method: POST
path: "/v1/events"
- toFQDNs:
- matchPattern: "*.googleapis.com"
toPorts:
- ports: [ { port: "443", protocol: TCP } ]
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports: [ { port: "53", protocol: UDP } ]
rules:
dns:
- matchPattern: "*"
This policy says: app: web can call app: api only via GET on /v1/* and POST on /v1/events; can reach any *.googleapis.com hostname over TLS; can use cluster DNS. Everything else is denied.
WireGuard between nodes
Cilium can encrypt all pod-to-pod traffic between nodes with WireGuard (kernel-native, fast):
cilium install --set encryption.enabled=true --set encryption.type=wireguard
# Or upgrade an existing install
cilium upgrade --set encryption.enabled=true --set encryption.type=wireguard
cilium status # shows "Encryption: Wireguard"
Per-node WireGuard tunnels appear; every pod's traffic to a different node is encrypted at the kernel level. CPU cost is modest on modern processors (~5-10% per gigabit of throughput).
Cluster Mesh
For multi-cluster setups, Cilium's Cluster Mesh stitches multiple clusters into one logical network with shared service discovery and cross-cluster network policies:
cilium clustermesh enable --service-type LoadBalancer
cilium clustermesh connect --context cluster-a --destination-context cluster-b
Services in cluster-a can be exported and consumed transparently by cluster-b. Critical for multi-region disaster recovery without standing up a service mesh on top.
Tetragon: runtime security
Cilium's sister project Tetragon uses the same eBPF base for runtime security: detect process executions, file modifications, network connections, and capability use. Define policies in TracingPolicy CRDs; observe events via kubectl logs -n kube-system -l app.kubernetes.io/name=tetragon or a SIEM forwarder (see Wazuh).
helm install tetragon cilium/tetragon -n kube-system
Performance numbers worth knowing
- Pod-to-pod TCP latency drops ~10-30% vs Flannel+iptables for small packets.
- Service load-balancing latency drops dramatically at high service counts — iptables service rules are O(n) per packet; eBPF maps are O(1).
- WireGuard encryption costs ~5-10% per Gb/s on AES-NI-capable CPUs (every server-class CPU made since 2015).
When Cilium isn't the right choice
- For very small clusters (a homelab with 2-3 nodes and a few pods), Flannel is simpler and Cilium's overhead isn't worth it.
- For older kernel versions (<5.10), Cilium's feature surface is reduced. K3s on modern distros isn't affected.
- For Linux-on-ARM-Cortex-A boards without recent kernel support, eBPF features may be limited — check the kernel BTF support before committing.
For any cluster where service count grows past tens, network policies become important, or you want to see what's actually flowing — Cilium is the most polished option in 2026, with Hubble as the killer add-on.