原文位址: 【BPF入門系列-1】eBPF 技術簡介

1. BPF
BPF(Berkeley Packet Filter ),中文翻譯為伯克利包過濾器,是類 Unix 系統上資料鍊路層的一種原始接口,提供原始鍊路層封包的收發。1992 年,Steven McCanne 和 Van Jacobson 寫了一篇名為《BSD資料包過濾:一種新的使用者級包捕獲架構》的論文。在文中,作者描述了他們如何在 Unix 核心實作網絡資料包過濾,這種新的技術比當時最先進的資料包過濾技術快 20 倍。BPF 在資料包過濾上引入了兩大革新:
- 一個新的虛拟機 (VM) 設計,可以有效地工作在基于寄存器結構的 CPU 之上;
- 應用程式使用緩存隻複制與過濾資料包相關的資料,不會複制資料包的所有資訊。這樣可以最大程度地減少BPF 處理的資料;
由于這些巨大的改進,所有的 Unix 系統都選擇采用 BPF 作為網絡資料包過濾技術,直到今天,許多 Unix 核心的派生系統中(包括 Linux 核心)仍使用該實作。
tcpdump 的底層采用 BPF 作為底層包過濾技術,我們可以在指令後面增加 ”-d“ 來檢視 tcpdump 過濾條件的底層彙編指令。
$ tcpdump -d 'ip and tcp port 8080'
(000) ldh [12]
(001) jeq #0x800 jt 2 jf 12
(002) ldb [23]
(003) jeq #0x6 jt 4 jf 12
(004) ldh [20]
(005) jset #0x1fff jt 12 jf 6
(006) ldxb 4*([14]&0xf)
(007) ldh [x + 14]
(008) jeq #0x1f90 jt 11 jf 9
(009) ldh [x + 16]
(010) jeq #0x1f90 jt 11 jf 12
(011) ret #262144
(012) ret #0
圖 1-1 tcpdump 底層彙編指令
BPF 工作在核心層,BPF 的架構圖如下 [來自于bpf-usenix93]:
圖 1-2 tcpdump 運作架構
2. eBPF
2.1 eBPF 介紹
2014 年初,Alexei Starovoitov 實作了 eBPF(extended Berkeley Packet Filter)。經過重新設計,eBPF 演進為一個通用執行引擎,可基于此開發性能分析工具、軟體定義網絡等諸多場景。eBPF 最早出現在 3.18 核心中,此後原來的 BPF 就被稱為經典 BPF,縮寫 cBPF(classic BPF),cBPF 現在已經基本廢棄。現在,Linux 核心隻運作 eBPF,核心會将加載的 cBPF 位元組碼透明地轉換成 eBPF 再執行。
eBPF 新的設計針對現代硬體進行了優化,是以 eBPF 生成的指令集比舊的 BPF 解釋器生成的機器碼執行得更快。擴充版本也增加了虛拟機中的寄存器數量,将原有的 2 個 32 位寄存器增加到 10 個 64 位寄存器。由于寄存器數量和寬度的增加,開發人員可以使用函數參數自由交換更多的資訊,編寫更複雜的程式。總之,這些改進使 eBPF 版本的速度比原來的 BPF 提高了 4 倍。
次元 | cBPF | eBPF |
---|---|---|
核心版本 | Linux 2.1.75(1997年) | Linux 3.18(2014年)[4.x for kprobe/uprobe/tracepoint/perf-event] |
寄存器數目 | 2個:A, X | 10個: R0–R9, 另外 R10 是一個隻讀的幀指針 |
寄存器寬度 | 32位 | 64位 |
存儲 | 16 個記憶體位: M[0–15] | 512 位元組堆棧,無限制大小的 “map” 存儲 |
限制的核心調用 | 非常有限,僅限于 JIT 特定 | 有限,通過 bpf_call 指令調用 |
目标事件 | 資料包、 seccomp-BPF | 資料包、核心函數、使用者函數、跟蹤點 PMCs 等 |
表格 1 cBPF 與 eBPF 對比
eBPF 在 Linux 3.18 版本以後引入,并不代表隻能在核心 3.18+ 版本上運作,低版本的核心更新到最新也可以使用 eBPF 能力,隻是可能部分功能受限,比如我就是在 Linux 發行版本 CentOS Linux release 7.7.1908 核心版本 3.10.0-1062.9.1.el7.x86_64 上運作 eBPF 在生産環境上搜集和排查網絡問題。
eBPF 實作的最初目标是優化處理網絡過濾器的内部 BPF 指令集。當時,BPF 程式仍然限于核心空間使用,隻有少數使用者空間程式可以編寫核心處理的 BPF 過濾器,例如:tcpdump和 seccomp。時至今日,這些程式仍基于舊的 BPF 解釋器生成位元組碼,但核心中會将這些指令轉換為高性能的内部表示。
2014 年 6 月,eBPF 擴充到使用者空間,這也成為了 BPF 技術的轉折點。 正如 Alexei 在送出更新檔的注釋中寫到:“這個更新檔展示了 eBPF 的潛力”。目前,eBPF 不再局限于網絡棧,已經成為核心頂級的子系統。eBPF 程式架構強調安全性和穩定性,看上去更像核心子產品,但與核心子產品不同,eBPF 程式不需要重新編譯核心,并且可以確定 eBPF 程式運作完成,而不會造成系統的崩潰。
圖 2-1 BPF 架構圖
簡述概括, eBPF 是一套通用執行引擎,提供了可基于系統或程式事件高效安全執行特定代碼的通用能力,通用能力的使用者不再局限于核心開發者;eBPF 可由執行位元組碼指令、存儲對象和 Helper 幫助函數組成,位元組碼指令在核心執行前必須通過 BPF 驗證器 Verfier 的驗證,同時在啟用 BPF JIT 模式的核心中,會直接将位元組碼指令轉成核心可執行的本地指令運作。
同時,eBPF 也逐漸在觀測(跟蹤、性能調優等)、安全和網絡等領域發揮重要的角色。Facebook、NetFlix 、CloudFlare 等知名網際網路公司内部廣泛采用基于 eBPF 技術的各種程式用于性能分析、排查問題、負載均衡、防範 DDoS 攻擊,據相關資訊顯示在 Facebook 的機器上内置一系列 eBPF 的相關工具。
相對于系統的性能分析和觀測,eBPF 技術在網絡技術中的表現,更是讓人眼前一亮,BPF 技術與 XDP(eXpress Data Path) 和 TC(Traffic Control) 組合可以實作功能更加強大的網絡功能,更可為 SDN 軟體定義網絡提供基礎支撐。XDP 隻作用與網絡包的 Ingress 層面,BPF 鈎子位于網絡驅動中盡可能早的位置,無需進行原始包的複制就可以實作最佳的資料包處理性能,挂載的 BPF 程式是運作過濾的理想選擇,可用于丢棄惡意或非預期的流量、進行 DDOS 攻擊保護等場景;而 TC Ingress 比 XDP 技術處于更高層次的位置,BPF 程式在 L3 層之前運作,可以通路到與資料包相關的大部分中繼資料,是本地節點處理的理想的地方,可以用于流量監控或者 L3/L4 的端點政策控制,同時配合 TC egress 則可實作對于容器環境下更高次元和級别的網絡結構。
圖 2-2 XDP 技術架構
eBPF 相關的知名的開源項目包括但不限于以下:
- Facebook 高性能 4 層負載均衡器 Katran ;
- Cilium 為下一代微服務 ServiceMesh 打造了具備API感覺和安全高效的容器網絡方案;底層主要使用 XDP 和 TC 等相關技術;
- IO Visor 項目開源的 BCC 、 BPFTrace 和 Kubectl-Trace : 提供了更高階的抽象,可以讓使用者采用 Python、C++ 和 Lua 等進階語言快速開發 BPF 程式; 采用類似于 awk 語言快速編寫 eBPF 程式; 則提供了在 kubernetes 叢集中使用 BPF 程式調試的友善操作;
- CloudFlare 公司開源的 eBPF Exporter bpf-tools 将 eBPF 技術與監控 Prometheus 緊密結合起來; 可用于網絡問題分析和排查;
越來越多的基于 eBPF 的項目如雨後脆筍一樣開始蓬勃發展,而且逐漸在社群中異軍突起,成為一道風景線。比如 IO Visor 項目的 BCC 工具,為性能分析和觀察提供了更加豐富的工具集:
圖檔來源圖 2-3 Linux bcc/BPF 觀測工具
同時,IO Visor 的
bpf-docs包含了日常的文檔,可以用于學習。
由于 eBPF 還在快速發展期,核心中的功能也日趨增強,一般推薦基于Linux 4.4+ (4.9 以上會更好) 核心的來使用 eBPF。部分 Linux Event 和 BPF 版本支援見下圖:圖 2-4 Linux 事件和 BPF 版本支援![]()
【BPF入門系列-1】eBPF 技術簡介
2.2 eBPF 架構(觀測)
基于 Linux 系統的觀測工具中,eBPF 有着得天獨厚的優勢,高效、生産安全且核心中内置,特别的可以在核心中完成資料分析聚合比如直方圖,與将資料發送到使用者空間分析聚合相比,能夠節省大量的資料複制傳遞帶來的 CPU 消耗。
eBPF 整體結構圖如下:
圖 2-5 eBPF 觀測架構
eBPF 分為使用者空間程式和核心程式兩部分:
- 使用者空間程式負責加載 BPF 位元組碼至核心,如需要也會負責讀取核心回傳的統計資訊或者事件詳情;
- 核心中的 BPF 位元組碼負責在核心中執行特定事件,如需要也會将執行的結果通過 maps 或者 perf-event 事件發送至使用者空間;
其中使用者空間程式與核心 BPF 位元組碼程式可以使用 map 結構實作雙向通信,這為核心中運作的 BPF 位元組碼程式提供了更加靈活的控制。
使用者空間程式與核心中的 BPF 位元組碼互動的流程主要如下:
- 我們可以使用 LLVM 或者 GCC 工具将編寫的 BPF 代碼程式編譯成 BPF 位元組碼;
- 然後使用加載程式 Loader 将位元組碼加載至核心;核心使用驗證器(verfier) 元件保證執行位元組碼的安全性,以避免對核心造成災難,在确認位元組碼安全後将其加載對應的核心子產品執行;BPF 觀測技術相關的程式程式類型可能是 kprobes/uprobes/tracepoint/perf_events 中的一個或多個,其中:
- kprobes:實作核心中動态跟蹤。 kprobes 可以跟蹤到 Linux 核心中的函數入口或傳回點,但是不是穩定 ABI 接口,可能會因為核心版本變化導緻,導緻跟蹤失效。
- uprobes:使用者級别的動态跟蹤。與 kprobes 類似,隻是跟蹤的函數為使用者程式中的函數。
- tracepoints:核心中靜态跟蹤。tracepoints 是核心開發人員維護的跟蹤點,能夠提供穩定的 ABI 接口,但是由于是研發人員維護,數量和場景可能受限。
- perf_events:定時采樣和 PMC。
- 核心中運作的 BPF 位元組碼程式可以使用兩種方式将測量資料回傳至使用者空間
- maps 方式可用于将核心中實作的統計摘要資訊(比如測量延遲、堆棧資訊)等回傳至使用者空間;
- perf-event 用于将核心采集的事件實時發送至使用者空間,使用者空間程式實時讀取分析;
如無特殊說明,本文中所說的 BPF 都是泛指 BPF 技術。
2.3 eBPF 的限制
eBPF 技術雖然強大,但是為了保證核心的處理安全和及時響應,核心中的 eBPF 技術也給予了諸多限制,當然随着技術的發展和演進,限制也在逐漸放寬或者提供了對應的解決方案。
- eBPF 程式不能調用任意的核心參數,隻限于核心子產品中列出的 BPF Helper 函數,函數支援清單也随着核心的演進在不斷增加。(todo 添加個數說明)
- eBPF 程式不允許包含無法到達的指令,防止加載無效代碼,延遲程式的終止。
- eBPF 程式中循環次數限制且必須在有限時間内結束,這主要是用來防止在 kprobes 中插入任意的循環,導緻鎖住整個系統;解決辦法包括展開循環,并為需要循環的常見用途添加輔助函數。Linux 5.3 在 BPF 中包含了對有界循環的支援,它有一個可驗證的運作時間上限。
- eBPF 堆棧大小被限制在 MAX_BPF_STACK,截止到核心 Linux 5.8 版本,被設定為 512;參見 include/linux/filter.h ,這個限制特别是在棧上存儲多個字元串緩沖區時:一個char[256]緩沖區會消耗這個棧的一半。目前沒有計劃增加這個限制,解決方法是改用 bpf 映射存儲,它實際上是無限的。
/* BPF program can access up to 512 bytes of stack space. */ #define MAX_BPF_STACK 512
- eBPF 位元組碼大小最初被限制為 4096 條指令,截止到核心 Linux 5.8 版本, 目前已将放寬至 100 萬指令( BPF_COMPLEXITY_LIMIT_INSNS),參見: include/linux/bpf.h ,對于無權限的BPF程式,仍然保留4096條限制 ( BPF_MAXINSNS );新版本的 eBPF 也支援了多個 eBPF 程式級聯調用,雖然傳遞資訊存在某些限制,但是可以通過組合實作更加強大的功能。
#define BPF_COMPLEXITY_LIMIT_INSNS 1000000 /* yes. 1M insns */
2.4 eBPF 與核心子產品對比
在 Linux 觀測方面,eBPF 總是會拿來與 kernel 子產品方式進行對比,eBPF 在安全性、入門門檻上比核心子產品都有優勢,這兩點在觀測場景下對于使用者來講尤其重要。
Linux 核心子產品 | ||
---|---|---|
kprobes/tracepoints | 支援 | |
安全性 | 可能引入安全漏洞或導緻核心 Panic | 通過驗證器進行檢查,可以保障核心安全 |
核心函數 | 可以調用核心函數 | 隻能通過 BPF Helper 函數調用 |
編譯性 | 需要編譯核心 | 不需要編譯核心,引入頭檔案即可 |
運作 | 基于相同核心運作 | 基于穩定 ABI 的 BPF 程式可以編譯一次,各處運作 |
與應用程式互動 | 列印日志或檔案 | 通過 perf_event 或 map 結構 |
資料結構豐富性 | 一般 | 豐富 |
入門門檻 | 高 | 低 |
更新 | 需要解除安裝和加載,可能導緻處理流程中斷 | 原子替換更新,不會造成處理流程中斷 |
核心内置 | 視情況而定 | 核心内置支援 |
表格 2 eBPF 與 Linux 核心子產品方式對比
3. 應用案例
大名鼎鼎的性能分析大師 Brendan Gregg 等編寫了諸多的 BCC 或 BPFTrace 的工具集可以拿來直接使用,完全可以滿足我們日常問題分析和排查。
BCC 在 CentOS 7 系統中可以通過 yum 快速安裝
# yum install bcc -y
Resolving Dependencies
--> Running transaction check
---> Package bcc.x86_64 0:0.8.0-1.el7 will be updated
--> Processing Dependency: bcc(x86-64) = 0.8.0-1.el7 for package: python-bcc-0.8.0-1.el7.x86_64
---> Package bcc.x86_64 0:0.10.0-1.el7 will be an update
--> Processing Dependency: bcc-tools = 0.10.0-1.el7 for package: bcc-0.10.0-1.el7.x86_64
--> Running transaction check
---> Package bcc-tools.x86_64 0:0.8.0-1.el7 will be updated
---> Package bcc-tools.x86_64 0:0.10.0-1.el7 will be an update
---> Package python-bcc.x86_64 0:0.8.0-1.el7 will be updated
---> Package python-bcc.x86_64 0:0.10.0-1.el7 will be an update
--> Finished Dependency Resolution
...
其他系統的安裝方式參見:
INSTALL.mdBCC 中每一個工具都有一個對應的使用樣例,比如
execsnoop.py execsnoop_example.txt,在使用樣例中有詳細的使用說明,而且 BCC 中的工具使用的幫助文檔格式基本類似,上手非常友善。
BCC 的程式一般情況下都需要 root 使用者來運作。
3.1 Linux 性能分析 60 秒 (BPF版本)
英文原文
Linux Performance Analysis in 60,000 Milliseconds,
視訊位址uptime
dmesg | tail
vmstat 1
mpstat -P ALL 1
pidstat 1
iostat -xz 1
free -m
sar -n DEV 1
sar -n TCP,ETCP 1
top
60s 系列 BPF 版本如下:
圖 3-1 60s 排查之 BPF 版本
對于在系統中運作的 "閃電俠" 程式,運作周期非常短,但是可能會帶來系統的抖動延時,我們采用
top
指令檢視一般情況下難以發現,我們可以使用 BCC 提供的工具
execsnoop
來進行排查:
# Trace file opens with process and filename: opensnoop
#/usr/share/bcc/tools/execsnoop
PCOMM PID PPID RET ARGS
sleep 3334 21029 0 /usr/bin/sleep 3
sleep 3339 21029 0 /usr/bin/sleep 3
conntrack 3341 1112 0 /usr/sbin/conntrack --stats
conntrack 3342 1112 0 /usr/sbin/conntrack --count
sleep 3344 21029 0 /usr/bin/sleep 3
iptables-save 3347 9211 0 /sbin/iptables-save -t filter
iptables-save 3348 9211 0 /sbin/iptables-save -t nat
3.2 slab dentry 過大導緻的網絡抖動排查
現象
網絡 ping 的延時間歇性有規律出現抖動
問題排查
采用
execsnoop
分析發現,某個運作指令
cat /proc/slabinfo
的運作時間間隔與抖動的頻率完全吻合,順着這個的線索定位,我們發現雲廠商提供的 Java 版本的雲監控會定期調用
cat /proc/slabinfo
來擷取核心緩存的資訊;
通過指令
slabtop
發現系統中的
dentry
項的記憶體占用非常大,系統記憶體 128G,
dentry
占用 70G 以上,是以問題很快就定位到是系統在打開檔案方面可能有相關問題;
根因分析
我們使用對于打開檔案跟蹤的 BCC 工具
opensnoop
很快就定位到是某個程式頻繁建立和删除臨時檔案,最終定位為某個 PHP 程式設定的調用方式存在問題,導緻每次請求會建立和删除臨時檔案;代碼中将 http 調用中的
contentType
設定成了
Http::CONTENT_TYPE_UPLOAD
,導緻每次請求都會生成臨時檔案,修改成
application/x-www-form-urlencoded
問題解決。
問題的原理可參考
記一次對網絡抖動經典案例的分析 systemtap腳本分析系統中dentry SLAB占用過高問題3.3 生成火焰圖
火焰圖是幫助我們對系統耗時進行可視化的圖表,能夠對程式中那些代碼經常被執行給出一個清晰的展現。Brendan Gregg 是火焰圖的建立者,他在
GitHub上維護了一組腳本可以輕松生成需要的可視化格式資料。使用 BCC 中的工具
profile
可很方面地收集道 CPU 路徑的資料,基于資料采用工具可以輕松地生成火焰圖,查找到程式的性能瓶頸。
使用 profile
搜集火焰圖的程式沒有任何限制和改造
profile
工具可以讓我們輕松對于系統或者程式的 CPU 性能路徑進行可視化分析:
/usr/share/bcc/tools/profile -h
usage: profile [-h] [-p PID | -L TID] [-U | -K] [-F FREQUENCY | -c COUNT] [-d]
[-a] [-I] [-f] [--stack-storage-size STACK_STORAGE_SIZE]
[-C CPU]
[duration]
Profile CPU stack traces at a timed interval
positional arguments:
duration duration of trace, in seconds
optional arguments:
-h, --help show this help message and exit
-p PID, --pid PID profile process with this PID only
-L TID, --tid TID profile thread with this TID only
-U, --user-stacks-only
show stacks from user space only (no kernel space
stacks)
-K, --kernel-stacks-only
show stacks from kernel space only (no user space
stacks)
-F FREQUENCY, --frequency FREQUENCY
sample frequency, Hertz
-c COUNT, --count COUNT
sample period, number of events
-d, --delimited insert delimiter between kernel/user stacks
-a, --annotations add _[k] annotations to kernel frames
-I, --include-idle include CPU idle stacks
-f, --folded output folded format, one line per stack (for flame
graphs)
--stack-storage-size STACK_STORAGE_SIZE
the number of unique stack traces that can be stored
and displayed (default 16384)
-C CPU, --cpu CPU cpu number to run profile on
examples:
./profile # profile stack traces at 49 Hertz until Ctrl-C
./profile -F 99 # profile stack traces at 99 Hertz
./profile -c 1000000 # profile stack traces every 1 in a million events
./profile 5 # profile at 49 Hertz for 5 seconds only
./profile -f 5 # output in folded format for flame graphs
./profile -p 185 # only profile process with PID 185
./profile -L 185 # only profile thread with TID 185
./profile -U # only show user space stacks (no kernel)
./profile -K # only show kernel space stacks (no user)
profile
配合
FlameGraph
可以輕松幫我們繪制出 CPU 使用的火焰圖。
$ profile -af 30 > out.stacks01
$ git clone https://github.com/brendangregg/FlameGraph
$ cd FlameGraph
$ ./flamegraph.pl --color=java < ../out.stacks01 > out.svg
圖 3-2 火焰圖
3.3 排查網絡調用來源
在生産場景下,會有些特定場景需要抓取連接配接到外網特定位址的程式,這時候我們可以采用 BCC 工具集中的
tcplife
來定位。
/usr/share/bcc/tools/tcplife -h
usage: tcplife [-h] [-T] [-t] [-w] [-s] [-p PID] [-L LOCALPORT]
[-D REMOTEPORT]
Trace the lifespan of TCP sessions and summarize
optional arguments:
-h, --help show this help message and exit
-T, --time include time column on output (HH:MM:SS)
-t, --timestamp include timestamp on output (seconds)
-w, --wide wide column output (fits IPv6 addresses)
-s, --csv comma separated values output
-p PID, --pid PID trace this PID only
-L LOCALPORT, --localport LOCALPORT
comma-separated list of local ports to trace.
-D REMOTEPORT, --remoteport REMOTEPORT
comma-separated list of remote ports to trace.
examples:
./tcplife # trace all TCP connect()s
./tcplife -t # include time column (HH:MM:SS)
./tcplife -w # wider colums (fit IPv6)
./tcplife -stT # csv output, with times & timestamps
./tcplife -p 181 # only trace PID 181
./tcplife -L 80 # only trace local port 80
./tcplife -L 80,81 # only trace local ports 80 and 81
./tcplife -D 80 # only trace remote port 80
通過在機器上使用
tcplife
來擷取的網絡連接配接資訊,我們可以看到包括了 PID、COMM、本地 IP 位址、本地端口、遠端 IP 位址和遠端端口,通過這些資訊非常友善排查到連接配接到特定 IP 位址的程式,尤其是連接配接的過程非常短暫,通過
netstat
等其他工具不容易排查的場景。
# /usr/share/bcc/tools/tcplife
PID COMM IP LADDR LPORT RADDR RPORT TX_KB RX_KB MS
1776 blackbox_export 4 169.254.20.10 35830 169.254.20.10 53 0 0 0.36
27150 node-cache 4 169.254.20.10 53 169.254.20.10 35830 0 0 0.36
12511 coredns 4 127.0.0.1 58492 127.0.0.1 8080 0 0 0.32
...
如果我們想知道更加詳細的 TCP 狀态情況,那麼
tcptracer
可展示更加詳細的 TCP 狀态,其中 C 代表 Connect X 表示關閉, A 代表 Accept。
# /usr/share/bcc/tools/tcptracer
Tracing TCP established connections. Ctrl-C to end.
T PID COMM IP SADDR DADDR SPORT DPORT
C 21066 ilogtail 4 10.81.128.12 100.100.49.128 40906 80
X 21066 ilogtail 4 10.81.128.12 100.100.49.128 40906 80
C 21066 ilogtail 4 10.81.128.12 100.100.49.128 40908 80
X 21066 ilogtail 4 10.81.128.12 100.100.49.128 40908 80
tcpstates
還能夠展示出來 TCP 狀态機的流轉情況:
# /usr/share/bcc/tools/tcpstates
SKADDR C-PID C-COMM LADDR LPORT RADDR RPORT OLDSTATE -> NEWSTATE MS
ffff9fd7e8192000 22384 curl 100.66.100.185 0 52.33.159.26 80 CLOSE -> SYN_SENT 0.000
ffff9fd7e8192000 0 swapper/5 100.66.100.185 63446 52.33.159.26 80 SYN_SENT -> ESTABLISHED 1.373
ffff9fd7e8192000 22384 curl 100.66.100.185 63446 52.33.159.26 80 ESTABLISHED -> FIN_WAIT1 176.042
同樣,我們也可以實時擷取到 TCP 連接配接逾時或者重連的網絡連接配接;也可以通過抓取 UDP包相關的連接配接資訊,用于定位諸如 DNS 請求逾時或者 DNS 請求的發起程序。
4. 編寫 BPF 程式
對于大多數開發者而言,更多的是基于 BPF 技術之上編寫解決我們日常遇到的各種問題,目前 BCC 和 BPFTrace 兩個項目在觀測和性能分析上已經有了諸多靈活且功能強大的工具箱,完全可以滿足我們日常使用。
更早期的工具則是使用 C 語言來編寫 BPF 程式,使用 LLVM clang 編譯成 BPF 代碼,這對于普通使用者上手有不少門檻目前僅限于對于 eBPF 技術更加深入的學習場景。
4.1 BCC 版本 HelloWorld
圖 4-1 BCC 整體架構
使用 BCC 前端綁定語言 Python 編寫的 Hello World 版本:
#!/usr/bin/python3
from bcc import BPF
# This may not work for 4.17 on x64, you need replace kprobe__sys_clone with kprobe____x64_sys_clone
prog = """
int kprobe__sys_clone(void *ctx) {
bpf_trace_printk("Hello, World!\\n");
return 0;
}
"""
b = BPF(text=prog, debug=0x04)
b.trace_print()
運作程式前需要安裝過 bcc 相關工具包,當運作正常的時候我們發現每當
sys_clone
系統調用時,運作的控制台上就會列印 “Hello, World!”,在列印文字前面還包含了調用程式的程序名稱,程序 ID 等資訊;
如果運作報錯,可能是缺少頭檔案,一般安裝 kernel-devel 包即可。
# python ./hello.py
kubelet-8349 [006] d... 33637334.829981: : Hello, World!
kubelet-8349 [006] d... 33637334.838594: : Hello, World!
kubelet-8349 [006] d... 33637334.843788: : Hello, World!
4.3 BPFTrace
BPFTrace 是基于 BPF 和 BCC 的開源項目,與 BCC 不同的是其提供了更高層次的抽象,可以使用類似 AWK 腳本語言來編寫基于 BPF 的跟蹤或者性能排查工具,更加易于入門和編寫,該工具的主要靈感來自于 Solaris 的 D 語言。BPFTrace 更友善與編寫單行的程式。BPFTrace 與 BCC 一樣也是 IO Visor 組織下的項目,倉庫參見
bpftrace。更加深入的學習資料參見:
Reference Guide One-Liner Tutorial。
BPFTrace 使用 LLVM 将腳本編譯成 BPF 二進制碼,後續使用 BCC 與 Linux 核心進行互動。從功能層面上講,BPFTrace 的定制性和靈活性不如 BCC,但是比 BCC 工具更加易于了解和使用,降低了 BPF 技術的使用門檻。
使用樣例:
# 統計程序調用 sys_enter 的次數
#bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
Attaching 1 probe...
^C
@[bpftrace]: 6
@[systemd]: 24
@[snmp-pass]: 96
@[sshd]: 125
# 統計核心中函數堆棧的次數
# bpftrace -e 'profile:hz:99 { @[kstack] = count(); }'
Attaching 1 probe...
^C
[...]
@[
filemap_map_pages+181
__handle_mm_fault+2905
handle_mm_fault+250
__do_page_fault+599
async_page_fault+69
]: 12
[...]
@[
cpuidle_enter_state+164
do_idle+390
cpu_startup_entry+111
start_secondary+423
secondary_startup_64+165
]: 22122
4.3 C 語言原生方式
采用 LLVM Clang 的方式編譯會涉及到核心編譯環境搭建,而且還需要自己編譯 Makefile 等操作,屬于進階使用者使用:
bpf_program.c
#include <linux/bpf.h>
#define SEC(NAME) __attribute__((section(NAME), used))
static int (*bpf_trace_printk)(const char *fmt, int fmt_size,
...) = (void *)BPF_FUNC_trace_printk;
SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(void *ctx) {
char msg[] = "Hello, BPF World!";
bpf_trace_printk(msg, sizeof(msg));
return 0;
}
char _license[] SEC("license") = "GPL";
loader.c
#include "bpf_load.h"
#include <stdio.h>
int main(int argc, char **argv) {
if (load_bpf_file("bpf_program.o") != 0) {
printf("The kernel didn't load the BPF program\n");
return -1;
}
read_trace_pipe();
return 0;
}
Makefile 檔案(部分)
build: ${BPFCODE.c} ${BPFLOADER}
$(CLANG) -O2 -target bpf -c $(BPFCODE:=.c) $(CCINCLUDE) -o ${BPFCODE:=.o}
其中 clang 編譯中的選型
-target bpf
表明我們将代碼編譯成 bpf 的位元組碼。
完整的程式參見:
hello_world;更多的樣例代碼可以參見對應核心中
kernel-src/samples/bpf/
下的樣例代碼。
5. 國内大廠 eBPF 實踐經驗
- eBPF 在網易輕舟雲原生的應用實踐
- 性能提升40%: 騰訊 TKE 用 eBPF繞過 conntrack 優化K8s Service
- 位元組跳動:eBPF 技術實踐:高性能 ACL
- 阿裡:eBPF Internal:Instructions and Runtime
- 使用 ebpf 深入分析容器網絡 dup 包問題
- eBay 雲計算“網”事:網絡逾時篇 eBay雲計算“網”事|網絡丢包篇
- 位元組跳動容器化場景下的性能優化實踐
6. 參考資料
- The BSD Packet Filter: A New Architecture for User-level Packet Capture
- [[譯] Cilium:BPF 和 XDP 參考指南(2019) ]( http://arthurchiao.art/blog/cilium-bpf-xdp-reference-guide-zh/) Cillum BPF and XDP Reference Guide
- Cloudflare架構以及BPF如何占據世界
- 關於 BPF 和 eBPF 的筆記
- Dive into BPF: a list of reading material 中文
- eBPF 簡史
- https://www.youtube.com/watch?v=znBGt7oHJyQ
- BPF Documentation HOWTO interact with BPF subsystem
- Linux 核心 BPF 文檔
- Linux Extended BPF (eBPF) Tracing Tools Brendan Gregg
- SDN handbook
- Linux BPF 幫助文檔 bpf(2) bpf-helpers(7) tc-bpf(8)
> 1. user commands > > 2. system calls > > 3. library functions > > 4. special files > > 5. file formats and filesystems > > 6. games > > 7. overview and miscellany section > > 8. administration and privileged commands > > 參考:https://man7.org/linux/man-pages/index.html