eBPF 介紹
Tcpdump 是Linux 平台常用的網絡數據包抓取及分析工具,tcpdump 主要通過libpcap 實現,而libpcap 就是基於eBPF。
先介紹BPF(Berkeley Packet Filter),BPF 是基於寄存器虛擬機實現的,支持 JIT(Just-In-Time),比基於棧實現的性能高很多。它能載入用戶態代碼並且在內核環境下運行,內核提供 BPF 相關的接口,用戶可以將代碼編譯成字節碼,通過 BPF 接口加載到 BPF 虛擬機中,當然用戶代碼跑在內核環境中是有風險的,如有處理不當,可能會導致內核崩潰。因此在用戶代碼跑在內核環境之前,內核會先做一層嚴格的檢驗,確保沒問題才會被成功加載到內核環境中。
eBPF
(extended Berkeley Packet Filter
)起源於BPF
,它提供了內核的數據包過濾機制。其擴充了 BPF
的功能,豐富了指令集。
最初,eBPF 僅在內核內部使用,並且 cBPF 程序在幕后無縫轉換。但是隨着2014 年的 daedfb22451d提交,eBPF 虛擬機直接暴露給用戶空間。
eBPF分用戶空間和內核空間,用戶空間和內核空間的交互有2種方式:
- BPF map:統計摘要數據
- perf-event:用戶空間獲取實時監測數據
如上,一般eBPF 的工作邏輯是:
- BPF Program 通過LLVM/Clang 編譯成eBPF定義的字節碼prog.bpf。
- 通過系統調用bpf() 將bpf 字節碼指令傳入內核中。
- 經過verifier 檢驗字節碼的安全性、合規性。
- 在確認字節碼安全后將其加載對應的內核模塊執行,通過Helper/hook機制,eBPF與內核可以交換數據/邏輯。BPF 觀測技術相關的程序程序類型可能是 kprobes/uprobes/tracepoint/perf_events 中的一個或多個,其中:
-
-
- kprobes:實現內核中動態跟蹤。 kprobes 可以跟蹤到 Linux 內核中的函數入口或返回點,但是不是穩定 ABI 接口,可能會因為內核版本變化導致,導致跟蹤失效。理論上可以跟蹤到所有導出的符號 /proc/kallsyms。
-
-
-
- uprobes:用戶級別的動態跟蹤。與 kprobes 類似,只是跟蹤的函數為用戶程序中的函數。
-
-
-
-
tracepoints:內核中靜態跟蹤。tracepoints 是內核開發人員維護的跟蹤點,能夠提供穩定的 ABI 接口,但是由於是研發人員維護,數量和場景可能受限。
-
-
-
-
- perf_events:定時采樣和 PMC。
-
5. 用戶空間通過BPF map 與內核通信。
eBPF 可以做什么
eBPF 主要功能列表
特性
|
引入版本
|
功能介紹
|
應用場景
|
---|---|---|---|
Tc-bpf | 4.1 | eBPF重構內核流分類 | 網絡 |
XDP | 4.8 | 網絡數據面編程技術(主要面向L2/L3層業務) | 網絡 |
Cgroup socket | 4.10 | Cgroup內socket支持eBPF擴展邏輯 | 容器 |
AF_XDP | 4.18 | 網絡原始報文直送用戶態(類似DPDK) | 網絡 |
Sockmap | 4.20 | 支持socket短接 | 容器 |
Device JIT | 4.20 | JIT/ISA解耦,host可以編譯指定device形態的ISA指令 | 異構編程 |
Cgroup sysctl | 5.2 | Cgroup內支持控制系統調用權限 | 容器 |
Struct ops Prog ext | 5.3 | 內核邏輯可動態替換 eBPF Prog可動態替換 | 框架基礎 |
Bpf trampoline | 5.5 | 三種用途: 1.內核中代替K(ret)probe,性能更優 2.eBPF Prog中使用,解決eBPF Prog調試問題 3.實現eBPF Prog動態鏈接功能(未來功能) | 性能跟蹤 |
KRSI(lsm + eBPF) | 5.7 | 內核運行時安全策略可定制 | 安全 |
Ring buffer | 5.8 | 提供CPU間共享的環形buffer,並能實現跨CPU的事件保序記錄。用以代替perf/ftrace等buffer。 | 跟蹤/性能分析 |
eBPF 在 Linux 3.18 版本以后引入,並不代表只能在內核 3.18+ 版本上運行,低版本的內核升級到最新也可以使用 eBPF 能力,只是可能部分功能受限,比如我就是在 Linux 發行版本 CentOS Linux release 7.7.1908 內核版本 3.10.0-1062.9.1.el7.x86_64 上運行 eBPF 在生產環境上搜集和排查網絡問題。
查看kernel 對BPF 的支持情況,確保CONFIG_BPF、CONFIG_BPFSYSCALL 是yes的。
[root@dev ~]# grep BPF /usr/src/kernels/$(uname -r)/.config
和內核模塊對比
維度
|
Linux 內核模塊
|
eBPF
|
---|---|---|
kprobes/tracepoints | 支持 | 支持 |
安全性 | 可能引入安全漏洞或導致內核 Panic | 通過驗證器進行檢查,可以保障內核安全 |
內核函數 | 可以調用內核函數 | 只能通過 BPF Helper 函數調用 |
編譯性 | 需要編譯內核 | 不需要編譯內核,引入頭文件即可 |
運行 | 基於相同內核運行 | 基於穩定 ABI 的 BPF 程序可以編譯一次,各處運行 |
與應用程序交互 | 打印日志或文件 | 通過 perf_event 或 map 結構 |
數據結構豐富性 | 一般 | 豐富 |
入門門檻 | 高 | 低 |
升級 | 需要卸載和加載,可能導致處理流程中斷 | 原子替換升級,不會造成處理流程中斷 |
內核內置 | 視情況而定 | 內核內置支持 |
eBPF 的使用場景
**網絡場景**
在網絡加速場景中,DPDK技術大行其道,在某些場景DPDK成了唯一選擇。XDP的出現為廠商提供了一種新的選擇,借助於kernel eBPF社區的蓬勃發展,為網絡加速場景注入了一股清流。下面我們總結下兩種差異:
- DPDK優勢/價值:優勢(性能、生態)、價值(帶動硬件銷售)
- 性能:總體上XDP性能全面弱於DPDK(但是差距不大),注意:只是比較DPDK/XDP自身性能
- 生態:DPDK歷經多年發展,生態體現在:驅動支持豐富、基礎庫豐富(無鎖隊列、大頁內存、多核調度、性能分析工具等)、協議支持豐富(社區強大,例如VPP,支持眾多協議ARP/VLAN/IP/MPLS等)
- 價值:將網絡類專有硬件的工作轉嫁給軟件實現,進而拓展硬件廠商市場范圍。
- XDP優勢:可編程、內核協同工作
- 可編程:在網絡硬件智能化趨勢下,可編程可以適用多種場景。
- 內核協同:XDP並不是完全bypass kernel,所以在必要的時候可以與內核協同工作,利於網絡統一管理、部署。
- DPDK一些固有缺陷:
- 獨占Device:設備利用率低。
- 部署復雜:由於獨占Device,網絡部署需要與OS協議棧協同部署。
- 開發困難:DPDK定位就是網絡數據面開發包,所以它對使用者要求具備專業網絡知識、專業硬件知識,所以入門門檻高。
- 端到端性能不高:DPDK只是提供數據包從NIC到用戶態軟件的零拷貝,但是用戶態傳輸協議依然需要CPU參與。所以端到端性能不高。
進階閱讀Polycube 項目。
容器場景
背景:雲原生場景中容器比虛擬化技術有着更好的低底噪、輕便、易管理等優點,基本已經成為雲原生應用的事實標准。容器場景對網絡需求實際是應用對網絡的需求,即面向應用的網絡服務。
- 雲原生應用特點以及對網絡的訴求:
- 生命周期短:要求提供基於PoD靜態身份信息實施的網絡安全策略。
- (不能基於IP/Port) 租戶間隔離:要求提供API級別的網絡隔離策略。
- ServiceMesh拓撲管理:要求提供side-car加速。
- 服務入口位置透明:要求提供跨集群Ingress服務能力。
- 安全策略跨集群:要求網絡安全策略能夠在集群間共享、繼承。
- 服務實例冗余保證高可用性:要求提供L3/4層LB能力。
進階閱讀Cilium項目。
安全場景
背景:Linux系統的運行安全始終是在動態平衡中,系統安全性通常要評估兩方面的契合度:signals(系統中一些異常活動跡象)、mitigation(針對signals的一些補救措施)。
內核中的signal/mitigation設置散布在多個地方,配置時費時費力。
解決方案:引入eBPF,提供一些eBPF Helper實現“unified policy API”,由API來統一配置signal和mitigation。
eBPF 的使用
eBPF 提供多種使用方式:BCC、BPFTrace、libbpf C/C++ Library、eBPF GO library等
更早期的工具使用 C 語言來編寫 BPF 程序,使用 LLVM clang 編譯成 BPF 代碼,這對於普通使用者上手有不少門檻當前僅限於對於 eBPF 技術更加深入的學習場景。
對於大多數開發者而言,更多的是基於 BPF 技術之上編寫解決我們日常遇到的各種問題。
BCC 和 BPFTrace 作為BPF的兩個前端,當前這兩個項目在觀測和性能分析上已經有了諸多靈活且功能強大的工具箱,完全可以滿足我們日常使用。
libbpf C/C++ Library
基於libbpf C/C++ library 的開發架構如下:
獲取libbpf:
1) git clone https://github.com/libbpf/libbpf
2) cd libbpf/src
3) make -j8 && make install
原生C Hello world
參考:https://github.com/bpftools/linux-observability-with-bpf/tree/master/code/chapter-2/hello_world
[root@dev ~]# git clone https://github.com/bpftools/linux-observability-with-bpf
[root@dev ~]# cd linux-observability-with-bpf/code/chapter-2/hello_world
獲取內核源碼,將Makefile 中kenel-src 路徑替換為實際內核源碼路徑
[root@dev ~]# make
make 后會創建BPF ELF bpf-program.o
及 Loader monitor-exec
這時執行
[root@dev ~]# ./monitor-exec
將bpf 指令加載至內核。
之后,執行任意的execve 系統調用都會打印:Hello, BPF World!
如執行ls:
此時可以看到BPF 程序打印出Hello, BPF World!
注意:
- centos 默認yum 安裝的clang 版本是3.4,不支持tagert bpf,需要升級clang 至3.9
BCC 的安裝及使用
bcc 即BPF Compiler Collection,bcc 是一個關於BPF 技術的工具集。
以CentOS 7 為例
安裝
Linux 3.15 開始引入 eBPF,而又因為bcc 在5以上的內核版本中存在bug(https://github.com/iovisor/bcc/issues/2329),建議將內核升級至4+,如lt 版本4.19.
升級Linux 內核
因為多數elrepo 中的kernel 版本默認是最新的5.4 或5.12 等,可以直接下載4.19 的kernel rpm 包本地安裝;
rpm 包參考:https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190918210642/4.19.72-300.el7.x86_64/
下載rpm 包至本地:
kernel-4.19.72-300.el7.x86_64.rpm kernel-core-4.19.72-300.el7.x86_64.rpm |
本地安裝:
# yum localinstall kernel-core-4.19.72-300.el7.x86_64.rpm kernel-4.19.72-300.el7.x86_64.rpm kernel-modules-4.19.72-300.el7.x86_64.rpm kernel-headers-4.19.72-300.el7.x86_64.rpm
更新 Grub 后重啟:
[root@dev ~]# grub2-mkconfig -o /boot/grub2/grub.cfg [root@dev ~]# awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg 0 : CentOS Linux (5.2.8-1.el7.elrepo.x86_64) 7 (Core) 1 : CentOS Linux (3.10.0-862.14.4.el7.x86_64) 7 (Core) [root@dev ~]# grub2-set-default 0 [root@dev ~]# reboot
重新登錄后確認當前內核版本
[root@dev ~]# grub2-editenv list uname -r
安裝bcc-tools
[root@dev ~]# yum install -y bcc-tools
[root@dev ~]# export PATH=$PATH:/usr/share/bcc/tools
使用bcc-tools
如對於一些生命周期很短的進程很難通過top 工具去監測,這是可以通過execsnoop 去監測:
[root@dev ~]# /usr/share/bcc/tools/execsnoop
BCC 的程序一般情況下都需要 root 用戶或sudo 來運行。
BCC hello world
BCC 前端綁定語言Python
1 #!/usr/bin/python3 2 3 from bcc import BPF 4 5 # This may not work for 4.17 on x64, you need replace kprobe__sys_clone with kprobe____x64_sys_clone 6 prog = """ 7 int kprobe__sys_clone(void *ctx) { 8 bpf_trace_printk("Hello, World!\\n"); 9 return 0; 10 } 11 """ 12 13 b = BPF(text=prog, debug=0x04) 14 b.trace_print()
其中,
-
text='...':自定義的
C 代碼BPF 程序。 -
kprobe__sys_clone()
:通過kprobes 執行內核動態追蹤的捷徑。以kprobe__為前綴的C函數,被當作內核函數名使用,本文是
sys_clone()。
-
void *ctx
: ctx 傳遞參數,當前不傳遞參數則使用void *。
-
bpf_trace_printk():
一個簡單的內核工具,用於printf 輸出至trace_pipe (/sys/kernel/debug/tracing/trace_pipe)。對於一些簡單的用法是沒問題的,不過有三個限制:最多3個參數、只有1%s、trace_pipe 全局共享,所以當前程序的輸出會有不清晰的情況。更好的接口是利用BPF_PERF_OUTPUT(),而后覆蓋。 -
return 0;
: 必要的步驟 (參考 #139)。 -
.trace_print()
: 常規的bcc 代碼,讀取 trace_pipe 並打印輸出。
輸出:bash-21720 是ls,11789 是執行C BPF 程序 ./monitor-exec
BPFTrace
BPFTrace 使用 LLVM 將腳本編譯成 BPF 二進制碼,后續使用 BCC 與 Linux 內核進行交互。
從功能層面上講,BPFTrace 的定制性和靈活性不如 BCC,但是比 BCC 工具更加易於理解和使用,降低了 BPF 技術的使用門檻。
- 獲取bpftrace 源碼:git clone https://github.com/iovisor/bpftrace
- cd bpftrace
-
mkdir build; cd build; cmake -DCMAKE_BUILD_TYPE=Release ..
-
make -j8 && make install
# 統計內核中函數堆棧的次數
# bpftrace -e 'profile:hz:99 { @[kstack] = count(); }'
參考
- https://ebpf.io/
- https://cloudnative.to/blog/bpf-intro/
- https://github.com/iovisor/bcc
- https://openeuler.org/zh/blog/MrRlu/2021-01-04-openEuler%20eBPF%20introduce.html
Further Reading
eBPF 發展歷程
- 1992年:BPF全稱Berkeley Packet Filter,誕生初衷提供一種內核中自定義報文過濾的手段(類匯編),提升抓包效率。(tcpdump)
- 2011年:linux kernel 3.2版本對BPF進行重大改進,引入BPF JIT,使其性能得到大幅提升。
- 2014年: linux kernel 3.15版本,BPF擴展成eBPF,其功能范疇擴展至:內核跟蹤、性能調優、協議棧QoS等方面。與之配套改進包括:擴展BPF ISA指令集、提供高級語言(C)編程手段、提供MAP機制、提供Help機制、引入Verifier機制等。
- 2016年:linux kernel 4.8版本,eBPF支持XDP,進一步拓展該技術在網絡領域的應用。隨后Netronome公司提出eBPF硬件卸載方案。
- 2018年:linux kernel 4.18版本,引入BTF,將內核中BPF對象(Prog/Map)由字節碼轉換成統一結構對象,這有利於eBPF對象與Kernel版本的配套管理,為eBPF的發展奠定基礎。
- 2018年:從kernel 4.20版本開始,eBPF成為內核最活躍的項目之一,新增特性包括:sysctrl hook、flow dissector、struct_ops、lsm hook、ring buffer等。場景范圍覆蓋容器、安全、網絡、跟蹤等。