eBPF CNI
    Hubble

    Deploy Cilium CNI on a Kubernetes Cluster

    Install Cilium as the CNI on a kubeadm cluster running on RamNode VPS nodes — eBPF kube-proxy replacement, Hubble observability, and network policies.

    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 need download.docker.com, pkgs.k8s.io, and helm.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)

    shell
    # 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 --system

    3. Install containerd

    shell
    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 containerd

    4. Install kubeadm, kubelet, kubectl

    shell
    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 kubectl

    5. 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:

    shell
    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/config

    Join 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

    shell
    # 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=true

    Routing 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:

    shell
    cilium install --set routingMode=tunnel --set tunnelProtocol=vxlan --set kubeProxyReplacement=true

    Verify:

    shell
    cilium status --wait
    kubectl get nodes -o wide     # should all be Ready now
    cilium connectivity test      # full e2e validation, takes a few minutes

    7. Enable Hubble (observability)

    shell
    cilium hubble enable --ui
    cilium hubble port-forward &
    hubble status

    For 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 plane
      • 4240/tcp (Cilium health checks) between all nodes
      • 8472/udp (VXLAN) between all nodes if using tunnel routing mode
      • 2379-2380/tcp on 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

    SymptomLikely causeFix
    Nodes stuck NotReady after Cilium installkube-proxy still running, conflicting with Cilium's replacementkubectl -n kube-system delete ds kube-proxy if it wasn't skipped at init
    cilium status shows bpf-lb: disabledKernel too old or bpf.masquerade unsupportedConfirm 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 dropsNative routing mode without an actual routed path between DCsSwitch to tunnel/vxlan mode, or set up a WireGuard mesh between regions first
    cilium connectivity test hangs on NetworkPolicy testsSecurity-group/VPS firewall blocking node-to-node on Cilium's portsRe-check port list in section 8

    10. Upgrades

    shell
    cilium upgrade
    cilium status --wait

    Always run cilium connectivity test after upgrading before considering it done — Cilium version skew between minor releases occasionally changes default behavior around kube-proxy replacement.