初探eBPF技術的強大
隨著G行應用上全棧云越來越多,云上應用的需求也越發變得多樣,對網絡、安全、可觀測性等各類需求也逐漸從傳統面向"點、線"的場景轉向了"面、分布式"的場景,對云上很多領域而言,eBPF這種新技術能提供簡單、便捷、快速的手段來實現各類工具、服務。本文對eBPF的概念、主要使用場景、使用方式進行了簡單介紹,并結合全棧云實際運維場景對eBPF技術進行了實踐,拋磚引玉。
一、eBPF基本概念
傳統BPF工作方式: 基于事件驅動的框架,用戶使用BPF虛擬機指令集(RISC精簡指令集)定義過濾規則,然后傳遞給kernel再進行JIT即時編譯成CPU原生指令,在事件被觸發時執行。通過這種在kernel層實行過濾的方式,降低用戶層定義的過濾成本,提升包過濾性能。傳統的tcpdump就是這種過濾方式。
eBPF是在研究軟件定義網絡方案時擴展出來的技術,使BPF擴展成為了一個更通用的虛擬機,仍然是基于事件驅動的框架。eBPF patch在2014年3月開始合入kernel主分支中,JIT組件在2014年發布的Linux 3.15版本中被合入,應用層控制BPF程序的bpf系統調用在Linux 3.18中被合入,接下來的Linux 4.x版本系列中添加了eBPF支持kprobes,uprobes,tracepoints和perf_events等事件類型。
eBPF相比傳統BPF技術,寄存器從32位擴展到64位,寄存器數量也擴展到10個以上,擴展了map技術實現內核態與用戶態共享存儲空間用于高效讀取數據,并從包過濾事件類型擴展到動態插樁內核函數、靜態插樁內核函數、用戶函數插樁、性能監控、安全等領域。當前BPF名稱默認即指eBPF。
二、eBPF內部工作機制示意圖
圖1 eBPF工作示意圖
主要過程:
- 用戶編寫eBPF代碼,并使用LLVM、GCC等把代碼編譯成eBPF字節碼;
- 用戶態程序或工具通過bpf系統調用加載eBPF字節碼到kernel中;
- 內核驗證器驗證字節碼是否合規、安全,確保不會造成kernel異常,驗證通過后,內核中的JIT即時編譯器把字節碼翻譯成CPU原生指令并加載到對應的事件接口;
當內核中對應事件被觸發時,執行被加載的CPU原生指令,分析數據放入map共享區供用戶態程序使用。
三、eBPF重要應用場景
1. eBPF定義網絡
在與Linux Kernel解耦的同時,通過eBPF可編程性及高效處理可以在Linux內核包處理上下文中動態添加處理邏輯,包括過濾、流量控制、轉發、執行路徑優化、協議解析等幾乎任意操作,同時以近似于本地編譯的內核代碼效率執行。比如Linux內核XDP(快速數據路徑)框架,通過在框架中掛載eBPF程序后,可實現三層路由轉發、四層負載均衡、分布式防火墻、訪問控制ACL等功能定制,可以編寫eBPF程序掛載到網卡驅動層直接處理網絡流量,繞過Linux Kernel,進而可以使用專用的網絡處理器(NPU)進行網絡流量處理,釋放CPU資源。開源社區比較典型的有facebook開源的Katran四層負載均衡器等。騰訊使用Cilium作為TKE底層引擎,阿里云使用eBPF技術實現CNI網卡。G行全棧云使用的DeepFlow流量采集和分析技術也使用了eBPF技術。
2. eBPF定義安全
除了早期基于bpf技術實現的內核運行時安全計算模型Seccomp和LSM Linux安全模塊之外,業界有很多基于eBPF技術來高效靈活實現網絡安全策略,比如Flacon異常行為檢測工具;容器網絡領域的開源項目Cilium,重度使用eBPF技術來實現云原生場景下的三層/四層/七層網絡安全策略等,在不更改應用程序代碼或容器配置的情況下能夠發布和更新 Cilium 安全策略;用于Linux的運行時安全和取證工具Tracee,使用Linux eBPF 技術在運行時跟蹤系統和應用程序,收集事件并分析檢測可疑行為模式。
3. eBPF可觀測性和實時跟蹤
Netflix公司基于eBPF實現生產環境tracing, AWS公司使用eBPF作為RPC觀測工具,國內互聯網巨頭字節跳動使用eBPF技術實現主機可觀測性和ACL訪問控制等。
網絡包全鏈路排查開源工具pwru(package where r u)是基于 eBPF 開發的網絡數據包排查工具,提供了完整的細粒度網絡數據包排查方案 (kernel版本需大于5.5)。
四、 eBPF主要使用方式
1. 使用C語言、Go語言等編程語言編寫原始eBPF程序,實現邏輯控制、觀測跟蹤等功能,具體可參考社區教程。
2. 使用高階封裝工具 BCC編寫eBPF觀測跟蹤程序。為了降低BPF程序開發門檻,社區發起了BCC項目,提供簡單易用的編寫、加載和運行eBPF程序的一個框架,并可以通過Python、Lua等腳本語言來編寫。除此之外還提供了很多現成的用于對內核、CPU、內存、調度、網段等子系統的觀測跟蹤,參考https://github.com/iovisor/bcc。
3. 使用高階封裝工具bpftrace編寫eBPF觀測跟蹤程序。通過命令行就能實現eBPF性能觀測工具,更加簡化eBPF使用,用于追蹤、調試Linux kernel、了解kernel運行機制非常有用,缺點是不能調用內核函數或者自定義函數(此類場景需要使用BCC或C、GO語言開發),可參考https://github.com/iovisor/bpftrace
五、 G行全棧云Caas環境下eBPF技術初體驗案例一:全棧云hyper主機ping延時高
在全棧云某些hyper物理機上,發現ping 127.0.0.1延時高(圖2)
圖2 ping延時高
通過perf性能分析工具分別對正常ping、有ping延時進程分別進行trace采樣,制作成火焰圖,分析出耗時部分。
正常ping(圖3):
圖3 正常ping火焰圖
異常ping(延時大,圖4):
圖4 異常ping火焰圖
在火焰圖里可以看到,相比正常ping,延時高ping過程在try_to_wakeup_up()調用過程中耗費較大。
為了進一步搞清ping延時過程中try_to_wakeup_up具體是什么情況,編寫bpftrace kprobe類型程序掛載到try_to_wakeup_up內核函數:
#include <linux/sched.h>
kprobe:try_to_wake_up
/ pid == $1 /
{
$task = ((struct task_struct *) arg0);
$pid = $task->pid;
printf("from %s -> wakeup comm %s pid %d\n", comm, $task->comm, $pid);
}
用bpftrace執行此eBPF程序,監控ping進程被try_to_wakeup_up的詳細過程(圖5):
圖5 eBPF tracing數據
從圖中我們可以看到,在ping的過程中,try_to_wakeup_up頻繁喚醒isc-socket進程,經分析,此為dhcpd相關進程,是IaaS層分布式虛擬路由dvr master與client之間的處理邏輯。把dhcpd相關進程遷移到別的機器后,本機器上的ping 127.0.0.1延時恢復正常。
從以上案例可以看出,eBPF具備強大的可觀測性和實時跟蹤能力,可以很容易根據場景定制出合適的trace能力,對于觀測定位kernel、進程的運行邏輯十分便利。
案例二:觀測收發包主要過程耗時
在全棧云CaaS環境下,各業務以pod的形式運行在自己的namespace中,如果不同pod之間通信偶爾抖動變慢,如何判斷是網卡、協議棧、應用層等哪個環節出現了問題?傳統的tcpdump抓包工具(底層基于了classic bpf庫)抓包位置在軟中斷從網卡隊列(ring buffer)中讀取數據后發送給協議棧的時候,只能從tcpdump看到sequence數據包在網卡接口處收發的時間,在正常情況下無法直觀看到更深層次的延時原因,比如是內核處理延時還是用戶態延時?
如果我們知道veth驅動收發包關鍵kernel函數,以及協議棧處理與veth驅動的銜接點,就可以編寫eBPF程序掛載到這些關鍵函數入口或出口處,在可通過kprobe或者tracepoint在協議棧各層的關鍵函數中添加hook點,當數據包經過該函數時,打印出seq、network namespace、時間戳等關鍵信息,幫助我們快速定位或者縮小問題范圍。
本文模擬node節點某塊虛擬網卡延時(圖6),此時node節點上與pod節點(與延時虛擬網卡配對)如何判斷是網卡慢還是協議棧處理慢?
圖6 測試環境
首先需要分析出此場景下eBPF程序合適的kernel掛載點,基于bpftrace工具編寫eBPF程序并進行觀測跟蹤體驗:
1. veth發送關鍵內核函數:
__dev_queue_xmit(將數據發送到驅動層)
在該掛載點,獲取tcp四元組信息,獲取tcp sequence,并使用全局變量保存接收時間(納秒)@rcvpkg[$seq] = nsecs;
2. veth接收關鍵內核函數:
__netif_receive_skb(將報文收到協議棧)
tcp_rcv_state_process(tcp狀態機處理函數)
tcp_rcv_established(tcp establish過程處理)
選取上面3個示例掛載點,獲取tcp四元組信息,獲取tcp sequence與當前時間,減去__dev_queue_xmit記錄的起始時間,就可以得到發送到接收、協議棧主要處理函數耗時,對于超過一定時間的可以進行告警打印。
if( ($seq) == @sequence ){
$delta = ((nsecs - @rcvpkg[$seq]) / 1000000) % 1000;
if( $delta >= $1 ){
time("\n%H:%M:%S ");
printf("%-19u %-5s %d,%s,%s,%-10d ", $nsid, $netif, pid, comm, func, cpu);
printf("flags:%s, seq:%-u, ack:%u, win:%-25u ", $pkgflag, $seq, $ack, $win);
printf("%s:%-15d %s:%-15d %d ms\n", $srcip, $sport, $dstip, $dport, (nsecs / 1000000) % 1000);
printf("Slow pkg: duratinotallow=%u ms, seq=%-u\n", $delta, $seq);
}
}
模擬網絡延時:
tc qdisc add dev vnice9657d91c32 root netem delay 10ms
tc qdisc add dev vnicb8898168feb root netem delay 20ms
在node節點上執行eBPF程序:
bpftrace netpod.bt 5 > tt
在node節點上執行測試命令:
curl 30.254.10.7:8099;
nsenter -n -t 20720 telnet 30.254.10.6 22627
eBPF捕獲數據如下,可以看出tc設置的延時是在xmit發送時候產生的,接收方及tcp協議棧處理耗時正常(圖7-1,圖7-2)。
圖7-1 eBPF tracing網絡延時數據
圖7-2 eBPF tracing網絡延時數據
六、eBPF演進趨勢展望
eBPF技術像是一個任意門,可以隨意穿梭到你想去的地方進行探索甚至改造,在云時代大顯身手。Linux kernel面臨不斷增長的復雜度、性能、可擴展、向后兼容性等需求,需要保持kernel漸進式發展,更多新功能、新特性無法及時合并到kernel中。eBPF的內核可編程性,既能保證安全,又能在不改變kernel代碼的情況下實現新功能、新特性的快速應用,可為kernel的發展提供tick-tock迭代新方案,可以想象未來kernel的發展極有可能在eBPF技術基礎上實現軟件定義kernel。對于全棧云平臺而言,可以跟進eBPF技術發展,研究eBPF適用的應用場景,更好支持云上應用。