eBPF 基本架構及使用
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 在生產環境上搜集和排查網路問題。
和核心模組對比
維度 | 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 clonehttps://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 ELFbpf-program.o
及 Loadermonitor-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 localinstallkernel-core-4.19.72-300.el7.x86_64.rpmkernel-4.19.72-300.el7.x86_64.rpmkernel-modules-4.19.72-300.el7.x86_64.rpmkernel-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
#!/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()
|
其中,
-
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 clonehttps://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等。場景範圍覆蓋容器、安全、網路、跟蹤等。