Flomesh 服務網格採用 eBPF 實現更高效的流量攔截和通信

在不久前發佈的 Flomesh 服務網格 1.3.3[1] 我們引入了 eBPF 功能,用以替代流量攔截方面的實現 iptables。由於 eBPF 對較新內核的依賴,iptables 的實現仍繼續提供。同時,得益於 eBPF 網絡方面的能力,我們也實現了同節點網絡通信的加速。

背景

在服務網格中,iptables 和 eBPF 是兩種比較常見的流量攔截方式。

iptables 是一種基於 Linux 內核的流量攔截工具,它可以通過過濾規則來對流量進行控制。它的優點包括:

然而,iptables 也存在一些缺點:

ebpf 是一種高級的流量攔截工具,它可以通過自定義的程序在 Linux 內核中進行流量攔截和分析。ebpf 的優點包括:

然而,ebpf 也存在一些缺點:

綜合來看,iptables 更適合簡單的流量過濾和管理,而 ebpf 更適合需要更高靈活性和性能的複雜流量攔截和分析場景。

架構

在 1.3.3 中爲了提供 eBPF 特性,Flomesh 服務網格提供了 CNI 實現 osm-cni 和運行於各個節點的 osm-interceptor,其中 osm-cni 可與主流的 CNI 插件兼容。

當 kubelet 在節點上創建 pod,會通過容器運行時 CRI 實現調用 CNI 的接口創建 Pod 的網絡命名空間,osm-cni 會在 pod 網絡命名空間創建完成後調用 osm-interceptor 的接口加載 BPF 程序並將其附加到 hook 點上。除此以外 osm-interceptor 還會在 eBPF Maps 中維護 pod 信息等內容。

實現原理

接下來介紹下引入 eBPF 後帶來的兩個特性的實現原理,注意這裏會忽略很多的處理細節。

流量攔截

出站流量

下面的圖中展示的是出站(outbound)流量的攔截。將 BPF 程序附加到 socket 操作 connect 上,在程序中判斷當前 pod 是否被網格納管,也就是是否注入 sidecar,然後將目標地址修改爲 127.0.0.1、目標端口修改爲 sidecar 的 outbound 端口 15003。只是修改還不夠,還要將原始目的地址和端口保存在 map 中,使用 socket 的 cookie 作爲 key。

當與 sidecar 的連接建立成功後,通過附加到掛載點 sock_ops 上的程序將原始目的地保存在另一個 map 中,使用 本地地址 + 端口和遠端地址 + 端口 作爲 key。後面 sidecar 訪問目標應用時,通過 socket 的 getsockopt 操作獲得原始目的地址。沒錯,getsockopt 上也附加了 BPF 程序,程序會從 map 中取出原地目的地址並返回。

入站流量

對於入站流量的攔截,主要是將原本訪問應用端口的流量,轉發到 sidecar 的 inbound 端口 15003。這裏有兩種情況:

網絡通信加速

網絡數據包在 Kubernetes 的網絡中不可避免的要經過多次內核網絡協議棧的處理,eBPF 對網絡通信的加速則是通過跳過不必要的內核網絡協議棧,由互爲對端的兩個 socket 直接交換數據。

在上面流量攔截的圖中有消息的發送和接收軌跡。當附加在 sock_ops 上的程序發現連接建立成功,會將 socket 保存 map 中,同樣使用 本地地址 + 端口和遠端地址 + 端口 作爲 key。而互爲對端的兩個 socket,本地和遠端信息正好 相反,因此一個 socket 發送消息時可以直接從 map 中尋址對端的 socket。

對於同一個節點上的兩個 pod 通信,該方案也同樣適用。

快速體驗

安裝 CNI 插件

在所有的節點上執行下面的命令,下載 CNI 插件。

sudo mkdir -p /opt/cni/bin
curl -sSL https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz | sudo tar -zxf - -C /opt/cni/bin

Master 節點

獲取 master 節點的 IP 地址。

export MASTER_IP=10.0.2.6

Kubernetes 集羣使用 k3s 發行版,但在安裝集羣的時候,需要禁用 k3s 集成的 flannel,使用獨立安裝的 flannel 進行驗證。這是因爲 k3s 的 CNI bin 目錄在 /var/lib/rancher/k3s/data/xxx/bin 下,而非 /opt/cni/bin

curl -sfL https://get.k3s.io | sh -s - --disable traefik --disable servicelb --flannel-backend=none --advertise-address $MASTER_IP --write-kubeconfig-mode 644 --write-kubeconfig ~/.kube/config

安裝 Flannel。這裏注意,Flannel 默認的 Pod CIDR 是 10.244.0.0/16,我們將其修改爲 k3s 默認的 10.42.0.0/16

curl -s https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml | sed 's|10.244.0.0/16|10.42.0.0/16|g' | kubectl apply -f -

獲取 apiserver 的訪問 token,用於初始化工作節點。

sudo cat /var/lib/rancher/k3s/server/node-token

工作節點

使用 master 節點的 IP 地址以及前面獲取的 token 初始化節點。

export INSTALL_K3S_VERSION=v1.23.8+k3s2
export NODE_TOKEN=K107c1890ae060d191d347504740566f9c506b95ea908ba4795a7a82ea2c816e5dc::server:2757787ec4f9975ab46b5beadda446b7
curl -sfL https://get.k3s.io | K3S_URL=https://${MASTER_IP}:6443 K3S_TOKEN=${NODE_TOKEN} sh -

下載 osm-edge CLI

system=$(uname -s | tr [:upper:] [:lower:])
arch=$(dpkg --print-architecture)
release=v1.3.3
curl -L https://github.com/flomesh-io/osm-edge/releases/download/${release}/osm-edge-${release}-${system}-${arch}.tar.gz | tar -vxzf -
./${system}-${arch}/osm version
sudo cp ./${system}-${arch}/osm /usr/local/bin/

安裝 osm-edge

export osm_namespace=osm-system 
export osm_mesh_name=osm 

osm install \
    --mesh-name "$osm_mesh_name" \
    --osm-namespace "$osm_namespace" \
    --set=osm.trafficInterceptionMode=ebpf \
    --set=osm.osmInterceptor.debug=true \
    --timeout=900s

部署示例應用

#模擬業務服務
kubectl create namespace ebpf
osm namespace add ebpf
kubectl apply -n ebpf -f https://raw.githubusercontent.com/cybwan/osm-edge-start-demo/main/demo/interceptor/curl.yaml
kubectl apply -n ebpf -f https://raw.githubusercontent.com/cybwan/osm-edge-start-demo/main/demo/interceptor/pipy-ok.yaml

#讓 Pod 調度到不同的 node 上
kubectl patch deployments curl -n ebpf -p '{"spec":{"template":{"spec":{"nodeName":"node1"}}}}'
kubectl patch deployments pipy-ok-v1 -n ebpf -p '{"spec":{"template":{"spec":{"nodeName":"node1"}}}}'
kubectl patch deployments pipy-ok-v2 -n ebpf -p '{"spec":{"template":{"spec":{"nodeName":"node2"}}}}'

sleep 5

#等待依賴的 POD 正常啓動
kubectl wait --for=condition=ready pod -n ebpf -l app=curl --timeout=180s
kubectl wait --for=condition=ready pod -n ebpf -l app=pipy-ok -l version=v1 --timeout=180s
kubectl wait --for=condition=ready pod -n ebpf -l app=pipy-ok -l version=v2 --timeout=180s

測試

測試時可通過命令查看工作節點上的內核 tracing 日誌查看 BPF 程序執行的 debug 日誌。爲了避免干擾 sidecar 與控制平面通信產生的干擾,先獲取控制面的 IP 地址。

kubectl get svc -n osm-system osm-controller -o jsonpath='{.spec.clusterIP}'
10.43.241.189

在兩個工作節點執行下面的命令。

sudo cat /sys/kernel/debug/tracing/trace_pipe | grep bpf_trace_printk | grep -v '10.43.241.189'

執行下面的命令發起請求。

curl_client="$(kubectl get pod -n ebpf -l app=curl -o jsonpath='{.items[0].metadata.name}')"
kubectl exec ${curl_client} -n ebpf -c curl -- curl -s pipy-ok:8080

正常會收到類似下面的結果,同時內核 tracing 日誌也會相應地輸出 BPF 程序的 debug 日誌(內容較多,這裏不做展示)。

Hi, I am pipy ok v1 !
Hi, I am pipy ok v2 !

引用鏈接

[1] Flomesh 服務網格 1.3.3: https://github.com/flomesh-io/osm-edge/releases/tag/v1.3.3

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/-yJa2W7acU56WYuW0njFMA