性能优化实战|使用eBPF代替iptables优化服务网格数据面性能
目前以 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]
Inbound iptables 將入流量重定向到 15006 端口,也就是 envoy 的 VirtualInboundListener,envoy 會根據請求的原始目的地址轉發到應用程序的指定端口
Outbound iptables 將出流量重定向到 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 團隊將持續探索新的性能優化方向。
Reference
[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
- END -
看完一鍵三連在看,轉發,點贊
是對文章最大的贊賞,極客重生感謝你
推薦閱讀
定個目標|建立自己的技術知識體系
從C10K到C10M高性能網絡的探索與實踐
萬字長文|深入理解XDP全景指南
你好,這里是極客重生,我是阿榮,大家都叫我榮哥,從華為->外企->到互聯網大廠,目前是大廠資深工程師,多次獲得五星員工,多年職場經驗,技術扎實,專業后端開發和后臺架構設計,熱愛底層技術,豐富的實戰經驗,分享技術的本質原理,希望幫助更多人蛻變重生,拿BAT大廠offer,培養高級工程師能力,成為技術專家,實現高薪夢想,期待你的關注!點擊藍字查看我的成長之路。
校招/社招/簡歷/面試技巧/大廠技術棧分析/后端開發進階/優秀開源項目/直播分享/技術視野/實戰高手等,?極客星球希望成為最有技術價值星球,盡最大努力為星球的同學提供技術和成長幫助!詳情查看->極客星球
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 求點贊,在看,分享三連
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
以上是生活随笔為你收集整理的性能优化实战|使用eBPF代替iptables优化服务网格数据面性能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 定个目标|建立自己的技术知识体系
- 下一篇: Golang 并发编程指南