Target environment: RamNode KVM VPS instances, Ubuntu 24.04 LTS, kubeadm-provisioned cluster. Cilium is a CNI, so this guide assumes you're standing up (or already have) a kubeadm cluster on RamNode VPS nodes and want Cilium as the pod networking / network policy / observability layer, replacing kube-proxy via eBPF.
If your target is actually bare-VPS eBPF filtering with no Kubernetes involved, the CNI-specific sections (kubeadm init, Hubble, kube-proxy replacement) don't apply — let me know and I'll rewrite this as a standalone Cilium/eBPF host-firewall guide instead.
1. Prerequisites
- One or more RamNode KVM VPS instances (2 vCPU / 4GB RAM minimum for control plane; workers sized to workload). Virtio-net NICs (RamNode's default KVM NIC model) work fine with Cilium's native routing and VXLAN/Geneve overlay modes.
- Ubuntu 24.04 LTS (kernel 6.8 by default — comfortably clears Cilium's eBPF feature baseline of 5.10+; you get full kube-proxy replacement, BPF masquerading, and Hubble support with no backports).
- Root/sudo access via your jump host to each node.
- Outbound access to
pypi.org-style package mirrors won't matter here — you needdownload.docker.com,pkgs.k8s.io, andhelm.sh/GitHub reachable from each node. Check your security group / firewall egress rules before you start; RamNode VPS firewalls default-deny inbound but egress is normally open. - A private network path between nodes. If your nodes span RamNode regions (e.g., ATL and SEA), you need routed connectivity between them (a WireGuard mesh or RamNode's private VLAN if provisioned) — Cilium's overlay mode will work across the WAN but latency-sensitive workloads should stay same-DC.
2. Base OS prep (run on every node)
# Disable swap (kubelet requirement)
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
# Kernel modules Cilium needs
cat <<EOF | sudo tee /etc/modules-load.d/cilium.conf
br_netfilter
overlay
EOF
sudo modprobe br_netfilter
sudo modprobe overlay
# sysctl for bridged traffic + ip forwarding
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cilium.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system3. Install containerd
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y containerd.io
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl enable containerd4. Install kubeadm, kubelet, kubectl
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | \
sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] \
https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' | \
sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl5. Init the control plane — WITHOUT kube-proxy, WITHOUT a default CNI
Cilium replaces kube-proxy entirely (better performance, fewer iptables chains). Skip proxy installation at init time:
sudo kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--skip-phases=addon/kube-proxy \
--apiserver-advertise-address=<CONTROL_PLANE_PRIVATE_IP>
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/configJoin workers with the kubeadm join command printed at the end of init (or regenerate via
kubeadm token create --print-join-command on the control plane).
Nodes will show NotReady until Cilium is installed — that's expected, there's no CNI yet.
6. Install Cilium
# cilium-cli
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
curl -L --fail --remote-name-all \
https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-amd64.tar.gz
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz
cilium install \
--set kubeProxyReplacement=true \
--set k8sServiceHost=<CONTROL_PLANE_PRIVATE_IP> \
--set k8sServicePort=6443 \
--set ipam.mode=kubernetes \
--set routingMode=native \
--set ipv4NativeRoutingCIDR=10.244.0.0/16 \
--set autoDirectNodeRoutes=true \
--set bpf.masquerade=trueRouting mode note: native routing works cleanly when all nodes are on the same L2/L3 segment
(same RamNode DC, same private VLAN). If your nodes are spread across RamNode regions without a
routed private network between them, switch to an overlay:
cilium install --set routingMode=tunnel --set tunnelProtocol=vxlan --set kubeProxyReplacement=trueVerify:
cilium status --wait
kubectl get nodes -o wide # should all be Ready now
cilium connectivity test # full e2e validation, takes a few minutes7. Enable Hubble (observability)
cilium hubble enable --ui
cilium hubble port-forward &
hubble statusFor a persistent UI reachable outside the cluster, front the Hubble UI service with an nginx
reverse proxy + Let's Encrypt cert to terminate TLS — issue a
cert on a dedicated hostname (e.g., hubble.internal.ramnode.example) and restrict access via
firewall/VPN rather than exposing it publicly, since Hubble shows full flow-level traffic data.
8. RamNode-specific firewall notes
- Cilium manages pod-to-pod traffic via eBPF and largely bypasses the host's iptables for that path
— but the node-level firewall (ufw/iptables/RamNode security groups) still governs traffic
reaching the VPS itself. Open:
6443/tcp(API server) between nodes needing to reach the control plane4240/tcp(Cilium health checks) between all nodes8472/udp(VXLAN) between all nodes if using tunnel routing mode2379-2380/tcpon control-plane nodes only, from other control-plane nodes (etcd)
- If nodes span RamNode regions, make sure the WAN path allows these ports specifically — don't rely on "private network" assumptions across DCs; RamNode's private VLANs are typically per-datacenter, not global.
9. Common issues
| Symptom | Likely cause | Fix |
|---|---|---|
Nodes stuck NotReady after Cilium install | kube-proxy still running, conflicting with Cilium's replacement | kubectl -n kube-system delete ds kube-proxy if it wasn't skipped at init |
cilium status shows bpf-lb: disabled | Kernel too old or bpf.masquerade unsupported | Confirm kernel ≥5.10 with uname -r; on legacy CentOS 7 nodes this will not work — Cilium needs the modern kernel path |
| Cross-DC pod traffic drops | Native routing mode without an actual routed path between DCs | Switch to tunnel/vxlan mode, or set up a WireGuard mesh between regions first |
cilium connectivity test hangs on NetworkPolicy tests | Security-group/VPS firewall blocking node-to-node on Cilium's ports | Re-check port list in section 8 |
10. Upgrades
cilium upgrade
cilium status --waitAlways run cilium connectivity test after upgrading before considering it done — Cilium version
skew between minor releases occasionally changes default behavior around kube-proxy replacement.
