Merbridge - 使用 eBPF 加速您的網格
用 eBPF 取代 iptables 規則,可以直接將資料從入站 socket 傳輸到出站 socket,縮短 sidecar 和服務之間的資料路徑。
Istio 在流量管理、安全性、可觀察性和策略方面的能力秘密都藏在 Envoy 代理中。Istio 使用 Envoy 作為「sidecar」來攔截服務流量,並使用 iptables 配置核心的 netfilter
封包過濾功能。
使用 iptables 執行此攔截存在一些缺點。由於 netfilter 是一個高度通用的封包過濾工具,因此在到達目標 socket 之前,會應用多個路由規則和資料過濾程序。例如,從網路層到傳輸層,netfilter 會根據預定義的規則(如 pre_routing
、post_routing
等)多次用於處理。當封包變成 TCP 封包或 UDP 封包,並轉發到使用者空間時,會執行一些額外的步驟,如封包驗證、協定策略處理和目標 socket 搜尋。當 sidecar 配置為攔截流量時,原始資料路徑可能會變得非常長,因為重複的步驟會執行多次。
在過去的兩年中,eBPF 已成為一種趨勢技術,並且社群中已發佈許多基於 eBPF 的專案。Cilium 和 Pixie 等工具展示了 eBPF 在可觀察性和網路封包處理方面的出色用例。借助 eBPF 的 sockops
和 redir
功能,可以透過直接將資料封包從入站 socket 傳輸到出站 socket 來有效率地處理資料封包。在 Istio 網格中,可以使用 eBPF 取代 iptables 規則,並透過縮短資料路徑來加速資料平面。
我們建立了一個名為 Merbridge 的開源專案,透過將以下命令應用到您的 Istio 管理叢集,您可以使用 eBPF 來實現這種網路加速。
$ kubectl apply -f https://raw.githubusercontent.com/merbridge/merbridge/main/deploy/all-in-one.yaml
使用 Merbridge,封包資料路徑可以直接從一個 socket 縮短到另一個目標 socket,以下是其運作方式。
使用 eBPF sockops
進行效能最佳化
網路連線本質上是 socket 通訊。eBPF 提供了一個函數 bpf_msg_redirect_hash
,可以直接將應用程式在入站 socket 中傳送的封包轉發到出站 socket。透過輸入前面提到的函數,開發人員可以執行任何邏輯來決定封包目的地。根據此特性,可以在核心中顯著最佳化封包的資料路徑。
sock_map
是記錄封包轉發資訊的關鍵部分。當封包到達時,會從 sock_map
中選取現有的 socket 來將封包轉發到。因此,我們需要儲存所有封包的 socket 資訊,以使傳輸過程正常運作。當有新的 socket 操作(如建立新的 socket)時,會執行 sock_ops
函數。socket 中繼資料會被取得並儲存在 sock_map
中,以便在處理封包時使用。sock_map
中的常見金鑰類型是來源和目的地位址及埠的「四元組」。有了金鑰和儲存在 map 中的規則,當新的封包到達時,將會找到目標 socket。
Merbridge 方法
讓我們逐步介紹 Merbridge 的詳細設計和實作原則,並提供一個真實的場景。
基於 iptables 的 Istio sidecar 流量攔截
當外部流量命中您應用程式的埠時,它將被 iptables 中的 PREROUTING
規則攔截,轉發到 sidecar 容器的 15006 埠,並移交給 Envoy 進行處理。如上圖紅色路徑中的步驟 1-4 所示。
Envoy 使用 Istio 控制平面發出的策略來處理流量。如果允許,流量將會傳送到應用程式容器的實際容器埠。
當應用程式嘗試存取其他服務時,它將會被 iptables 中的 OUTPUT
規則攔截,然後轉發到 sidecar 容器的 15001 埠,Envoy 在這裡監聽。這是在紅色路徑上的步驟 9-12,類似於入站流量處理。
到應用程式埠的流量需要轉發到 sidecar,然後從 sidecar 埠傳送到容器埠,這會產生額外負擔。此外,iptables 的多功能性決定了其效能並不總是理想,因為它不可避免地會因為應用不同的過濾規則而增加整個資料路徑的延遲。雖然 iptables 是進行封包過濾的常見方法,但在 Envoy 代理案例中,較長的資料路徑會放大核心中封包過濾程序的瓶頸。
如果我們使用 sockops
直接將 sidecar 的 socket 連接到應用程式的 socket,則流量將不需要經過 iptables 規則,因此可以提高效能。
處理出站流量
如上所述,我們希望使用 eBPF 的 sockops
來繞過 iptables 以加速網路請求。同時,我們也不想修改 Istio 的任何部分,使 Merbridge 完全適用於社群版本。因此,我們需要在 eBPF 中模擬 iptables 的作用。
iptables 中的流量重新導向利用其 DNAT
功能。當嘗試使用 eBPF 模擬 iptables 的功能時,我們需要做兩件事
- 在連線啟動時修改目標位址,以便將流量傳送到新的介面。
- 使 Envoy 能夠識別原始目標位址,以便能夠識別流量。
對於第一部分,我們可以透過修改 user_ip
和 user_port
來使用 eBPF 的 connect
程式來處理它。
對於第二部分,我們需要瞭解 ORIGINAL_DST
的概念,它屬於核心中的 netfilter
模組。
當應用程式(包括 Envoy)收到連線時,它會呼叫 get_sockopt
函數以取得 ORIGINAL_DST
。如果經過 iptables DNAT
程序,iptables 會將此參數設定為目前 socket 的「原始 IP + 埠」值。因此,應用程式可以根據連線取得原始目標位址。
我們必須透過 eBPF 的 get_sockopts
函數修改此呼叫程序。(這裡不使用 bpf_setsockopt
,因為此參數目前不支援 SO_ORIGINAL_DST
的 optname)。
參考下圖,當應用程式啟動請求時,它將經過以下步驟
- 當應用程式啟動連線時,
connect
程式會將目標位址修改為127.x.y.z:15001
,並使用cookie_original_dst
來儲存原始目標位址。 - 在
sockops
程式中,目前 socket 資訊和四元組會儲存在sock_pair_map
中。同時,相同的四元組及其對應的原始目標位址會寫入pair_original_dest
。(這裡不使用 Cookie,因為它無法在get_sockopt
程式中取得)。 - 在 Envoy 收到連線後,它會呼叫
get_sockopt
函數來讀取目前連線的目標位址。get_sockopt
將根據四元組資訊從pair_original_dst
中提取並傳回原始目標位址。因此,連線已完全建立。 - 在資料傳輸步驟中,
redir
程式會根據四元組資訊從sock_pair_map
中讀取 socket 資訊,然後透過bpf_msg_redirect_hash
直接轉發以加快請求。
為什麼我們將目標位址設定為 127.x.y.z
而不是 127.0.0.1
?當存在不同的 pod 時,可能會有衝突的四元組,這樣可以優雅地避免衝突。(Pod 的 IP 位址不同,它們在任何時候都不會處於衝突狀態。)
入站流量處理
入站流量的處理方式基本上與出站流量相似,唯一的區別是:修改目的地埠為 15006。
應注意的是,由於 eBPF 無法像 iptables 那樣在指定的命名空間中生效,因此變更將會是全域性的,這意味著如果我們使用原本不由 Istio 管理的 Pod 或外部 IP 位址,將會遇到嚴重的問題 — 就像連線根本無法建立一樣。
因此,我們設計了一個微型控制平面(部署為 DaemonSet),它會監看所有 pod — 類似於 kubelet 監看節點上的 pod — 以便將已注入 sidecar 的 pod IP 位址寫入 local_pod_ips
map。
在處理入站流量時,如果目標位址不在 map 中,我們不會對流量執行任何操作。
否則,步驟與出站流量相同。
相同節點加速
理論上,可以直接透過入站流量處理來實現同一節點上 Envoy sidecar 之間的加速。但是,在這種情況下存取目前 pod 的應用程式時,Envoy 會引發錯誤。
在 Istio 中,Envoy 使用目前的 pod IP 和埠號來存取應用程式。在上述場景中,我們意識到 pod IP 也存在於 local_pod_ips
map 中,並且流量將再次重新導向到埠 15006 上的 pod IP,因為它是入站流量的相同位址。重新導向到相同的入站位址會導致無限迴圈。
這裡出現一個問題:是否有任何方法可以使用 eBPF 取得目前命名空間中的 IP 位址?答案是肯定的!
我們設計了一個回饋機制:當 Envoy 嘗試建立連線時,我們會將其重新導向到埠 15006。但是,在 sockops
步驟中,我們將判斷來源 IP 和目標 IP 是否相同。如果是,則表示傳送了錯誤的請求,我們將在 sockops
程序中捨棄此連線。同時,目前的 ProcessID
和 IP
資訊將會寫入 process_ip
map,以允許 eBPF 支援處理程序和 IP 之間的對應。
當下一次請求發送時,不需要再次執行相同的流程。我們會直接從 process_ip
映射表中檢查目標位址是否與目前的 IP 位址相同。
連線關係
在使用 Merbridge 應用 eBPF 之前,Pod 之間的資料路徑如下:
應用 Merbridge 之後,出站流量將會跳過許多篩選步驟,以提升效能。
如果兩個 Pod 在同一部機器上,連線速度甚至可以更快。
效能結果
讓我們看看使用 eBPF 取代 iptables 對整體延遲的影響(越低越好)。
我們也可以看到使用 eBPF 後的整體 QPS(越高越好)。測試結果使用 wrk
生成。
總結
在這篇文章中,我們介紹了 Merbridge 的核心概念。透過使用 eBPF 取代 iptables,可以在網格場景中加速資料傳輸過程。同時,Istio 本身完全不會改變。這表示如果你不想再使用 eBPF,只需刪除 DaemonSet,資料路徑就會恢復到傳統的基於 iptables 的路由,而不會有任何問題。
Merbridge 是一個完全獨立的開源專案。它仍處於早期階段,我們期待有更多使用者和開發者參與其中。如果您能嘗試這項新技術來加速您的網格,並向我們提供一些回饋,我們將不勝感激!
另請參閱
- GitHub 上的 Merbridge
- 使用 eBPF 取代 iptables 來優化服務網格資料平面的效能,作者:騰訊的 Liu Xu
- Istio 中 Sidecar 注入和透明流量劫持過程的詳細說明,作者:Tetrate 的 Jimmy Song
- 使用 eBPF 加速 Istio 資料平面,作者:Intel 的 Yizhou Xu
- Envoy 的原始目的地篩選器