KubeCon 2021|使用 eBPF 代替 iptables 優化服務網格資料面效能

語言: CN / TW / HK

作者簡介

  劉旭

騰訊雲高階工程師,專注容器雲原生領域,有多年大規模 Kubernetes 叢集管理及微服務治理經驗,現負責騰訊雲服務網格 TCM 資料面產品架構設計和研發工作。

引言

目前以 Istio[1] 為代表的服務網格普遍使用 Sidecar 架構,並使用 iptables 將流量劫持到 Sidecar 代理,優點是對應用程式無侵入,但是 Sidecar 代理會增加請求時延和資源佔用。

效能一直是使用者十分關心的一個點,也是使用者評估是否使用服務網格產品的關鍵因素,騰訊雲 TCM 團隊一直致力於優化服務網格效能,上週我們在 KubeCon 分享了 使用 eBPF 代替 iptables 優化服務網格資料面效能的方案

iptables 實現流量劫持

首先看一下當前社群使用的基於 iptables 的流量劫持方案,下圖是一個 Pod 的建立過程,sidecar injector 會向 Pod 中注入兩個容器,istio-init 和 istio-proxy。

  • istio-init 是一個 init container,負責建立流量劫持相關的 iptables 規則,在建立完成後會退出;

  • istio-proxy 中執行著 envoy,負責代理 Pod 的網路流量,iptables 會將請求劫持到 istio-proxy 處理。

下圖展示了 iptables 完成流量劫持的整個過程,這裡簡單說明下,感興趣的同學可以檢視[2]

  • Inboundiptables 將入流量重定向到 15006 埠,也就是 envoy 的 VirtualInboundListener,envoy 會根據請求的原始目的地址轉發到應用程式的指定埠;

  • Outboundiptables 將出流量重定向到 15001 埠,也就是 envoy 的 VirtualOutboundListener,envoy 會根據請求的原始目的地址以及 Host URL 等資訊路由到指定後端。

eBPF 實現流量劫持

eBPF(extended Berkeley Packet Filter) 是一種可以在 Linux 核心中執行使用者編寫的程式,而不需要修改核心程式碼或載入核心模組的技術,目前被廣泛用於網路、安全、監控等領域。在 Kubernetes 社群最早也是最有影響的基於 eBPF 專案是 Cilium[4],Cilium 使用 eBPF 代替 iptables 優化 Service 效能。

Inbound

首先來看一下對入流量的劫持,對入流量的劫持主要使用 eBPF 程式 hook bind 系統呼叫完成。

eBPF 程式會劫持 bind 系統呼叫並修改地址,例如應用程式 bind 0.0.0.0:80 會被修改為 127.0.0.1:80,應用程式還有可能 bind ipv6 的地址,所以這裡有兩個 eBPF 程式分別處理 ipv4 和 ipv6 的 bind。

和 iptables 不同,iptables 可以針對每個 netns 單獨設定規則,eBPF 程式 attach 到指定 hook 點後,會對整個系統都生效,例如 attach 到 bind 系統呼叫後,所有 Pod 內以及節點上程序呼叫 bind 都會觸發 eBPF 程式,我們需要區分哪些呼叫是來自需要由 eBPF 完成流量劫持的 Pod。

在 K8s 中,除了 hostnetwork 的情況,每個 Pod 都有獨立的 netns,而每個 netns 都有唯一的 cookie,因此我們將需要使用 eBPF 完成流量劫持的 Pod 對應的 netns cookie 儲存在 cookie_map 中,eBPF 程式通過判斷當前 socket 的 netns cookie 是否在 cookie_map 中來決定是否修改 bind 地址。

修改應用程式的 bind 地址後,還需要下發 pod_ip:80 listener 配置到 envoy,pod_ip:80 listener 會將請求轉發到 127.0.0.1:80 也就是應用程式監聽的地址,這樣就實現了對入流量的劫持。但是這裡有一個問題,由於 istio 使用 istio-proxy 使用者啟動 envoy,預設情況下非 root 使用者不能 bind 1024 以下的特權埠,我們通過 istio-init 修改核心引數 sysctl net.ipv4.ip_unprivileged_port_start=0 解決了這個問題。

對比 iptables 和 eBPF 對入流量的劫持,iptables 方案每個包都需要 conntrack 處理,而 eBPF 方案只有在應用程式呼叫 bind 時執行一次,之後不會再執行,減少了效能開銷。

Outbound

再來看一下對出流量的劫持,對出流量的劫持比較複雜, 根據協議分為 TCP 和 UDP 兩種情況。

TCP 流量劫持

對 TCP 的出流量劫持過程:

  • _coonect4 通過劫持 connect 系統呼叫將目的地址修改為127.0.0.1:15001,也就是 envoy 的 VirtualOutboundListerer,同時將連線的原始目的地址儲存在 sk_storage_map;

  • 在 TCP 連線建立完成後, sockops 會讀取 sk_storage_map 中的資料,並以四元組(源IP、目的IP、源埠、目的埠)為 key 將原始目的地址儲存在 origin_dst_map;

  • _getsockopt 通過劫持 getsockopt 系統呼叫,讀取 origin_dst_map 中的資料將原始目的地址返回給 envoy。

UDP 流量劫持

istio 在 1.8 版本支援了智慧 DNS 代理[5],開啟後 iptables 會將 DNS 請求劫持到 Sidecar 處理,我們也需要用 eBPF 實現相同邏輯,對於 TCP DNS 的劫持和上面類似, 對 UDP DNS 的劫持見下圖:

對 UDP 的出流量劫持過程:

  • _connect4 _sendmsg4 都是負責修改 UDP 的目的地址為 127.0.0.1:15053 並儲存原始的目的地址到 sk_storage_map ,因為 Linux 提供兩種傳送 UDP 資料的方式
    • 先呼叫 connect 再呼叫 send,這種情況由 _connect4 處理
    • 直接呼叫 sendto,這種情況由 _sendmsg4 處理
  • recvmsg4 通過讀取 sk_storage_map 將回包的源地址改為原始的目的地址,這是因為有些應用程式,例如 nslookup 會校驗回包的源地址。

對於 TCP 和 connected UDP,iptables 方案每個包都需要 conntrack 處理,而eBPF 方案的開銷是一次性的,只需要在 socket 建立時執行一次,降低了效能開銷。

Sockmap

使用 sockmap 優化服務網格效能的方案最早由 cilium 提出,我們的方案也參考了 cilium,這裡 借用 cilium 的兩張圖來說明下優化效果。

優化前 Sidecar 代理與應用程式間的網路通訊都需要經過 TCP/IP 協議棧處理。

優化後 Sidecar 代理與應用程式間的網路通訊繞過了 TCP/IP 協議棧,如果兩個 Pod 在同一節點上,兩個 Pod 間的網路通訊也可以被優化。這裡簡單說明下 sockmap 的優化原理,感興趣的同學可以檢視[6][7]。

  • sock_hash 是一個儲存 socket 資訊的 eBPF map,key 是四元組(源IP、目的IP、源埠、目的埠);
  • _sockops 負責監聽 socket 事件,並將 socket 資訊儲存在 sock_hash;
  • _sk_msg 會攔截 sendmsg 系統呼叫,然後到 sock_hash 中查詢對端 socket,如果找到會呼叫 bpf_msg_redirect_hash 直接將資料傳送給對端 socket。

問題

但是用四元組做為 key 可能會存在衝突的問題,例如在同一節點上的兩個 Pod 中,envoy 使用同一源埠 50000 請求應用程式的 80 埠。

為了解決這個問題,我們在 key 中添加了 netns cookie,同時對於非 localhost 的請求將 cookie 設定為 0,這樣既保證了 key 不會衝突,又可以加速同一節點上兩個 Pod 間的網路通訊。

但是之前版本的核心不支援在 sockops sk_msg 這兩種 eBPF 程式中獲取 netns cookie 資訊,因此我們提交了兩個 patch [8 ][9]到核心社群,目前已合入 5.15 版本。

架構

整個方案的架構如圖所示,istio-ebpf 以 DaemonSet 的形式執行在節點上,負責 load/attach eBPF 程式和建立 eBPF map。istio-init 容器仍然保留,但是不再建立 iptables 規則,而是更新 eBPF map,istio-init 會將 Pod 的 netns cookie 儲存在 cookie_map 中。同時我們也修改了 istiod,istiod 會根據 Pod 的流量劫持模式(iptables/eBPF)下發不同的 xDS 配置。

效能對比

測試環境:Ubuntu 21.04 5.15.7

  • 同等條件下,使用 eBPF 可減少 20% 的 System CPU 佔用;

  • 同等條件下,使用 eBPF 可提高 20% QPS;

  • 同等條件下,使用 eBPF 可降低請求時延。

總結

服務網格的 Sidecar 架構不可避免的會增加請求時延和資源佔用,我們通過使用 eBPF 代替 iptables 實現流量劫持,同時使用 sockmap 加速 Sidecar 代理和應用程式間的網路通訊,在 一定程度上降低了請求時延和資源開銷 ,由於核心版本等限制這一方案預計會在明年初上線,TCM 團隊將持續探索新的效能優化方向。

參考資料

[1] https://istio.io

[2] https://jimmysong.io/blog/sidecar-injection-iptables-and-traffic-routing

[3] https://ebpf.io

[4] https://cilium.io

[5] https://istio.io/latest/blog/2020/dns-proxy

[6] https://arthurchiao.art/blog/socket-acceleration-with-ebpf-zh

[7] https://github.com/cilium/cilium/tree/v1.11.0/bpf/sockops

[8] https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/commit/?id=6cf1770d

[9] https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/commit/?id=fab60e29f