天天看點

【BPF入門系列-1】eBPF 技術簡介

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

【BPF入門系列-1】eBPF 技術簡介
由範老師和我一起翻譯的圖書 《Linux核心觀測技術BPF》 已經在 JD 上有現貨,歡迎感興趣 BPF 技術的同學選購。連結位址 https://item.jd.com/72110825905.html “eBPF 是我見過的 Linux 中最神奇的技術,沒有之一,已成為 Linux 核心中頂級子子產品,從 tcpdump 中用作網絡包過濾的經典 cbpf,到成為通用 Linux 核心技術的 eBPF,已經完成華麗蛻變,為應用與神奇的核心打造了一座橋梁,在系統跟蹤、觀測、性能調優、安全和網絡等領域發揮重要的角色。為 Service Mesh 打造了具備 API 感覺和安全高效的容器網絡方案 Cilium,其底層正是基于 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]:

【BPF入門系列-1】eBPF 技術簡介

圖 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 程式運作完成,而不會造成系統的崩潰。

【BPF入門系列-1】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 則可實作對于容器環境下更高次元和級别的網絡結構。

【BPF入門系列-1】eBPF 技術簡介

圖 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 工具,為性能分析和觀察提供了更加豐富的工具集:

圖檔來源
【BPF入門系列-1】eBPF 技術簡介

圖 2-3 Linux bcc/BPF 觀測工具

同時,IO Visor 的

bpf-docs

包含了日常的文檔,可以用于學習。

由于 eBPF 還在快速發展期,核心中的功能也日趨增強,一般推薦基于Linux 4.4+ (4.9 以上會更好) 核心的來使用 eBPF。部分 Linux Event 和 BPF 版本支援見下圖:
【BPF入門系列-1】eBPF 技術簡介
圖 2-4 Linux 事件和 BPF 版本支援

2.2 eBPF 架構(觀測)

基于 Linux 系統的觀測工具中,eBPF 有着得天獨厚的優勢,高效、生産安全且核心中内置,特别的可以在核心中完成資料分析聚合比如直方圖,與将資料發送到使用者空間分析聚合相比,能夠節省大量的資料複制傳遞帶來的 CPU 消耗。

eBPF 整體結構圖如下:

【BPF入門系列-1】eBPF 技術簡介

圖 2-5 eBPF 觀測架構

eBPF 分為使用者空間程式和核心程式兩部分:

  • 使用者空間程式負責加載 BPF 位元組碼至核心,如需要也會負責讀取核心回傳的統計資訊或者事件詳情;
  • 核心中的 BPF 位元組碼負責在核心中執行特定事件,如需要也會将執行的結果通過 maps 或者 perf-event 事件發送至使用者空間;

其中使用者空間程式與核心 BPF 位元組碼程式可以使用 map 結構實作雙向通信,這為核心中運作的 BPF 位元組碼程式提供了更加靈活的控制。

使用者空間程式與核心中的 BPF 位元組碼互動的流程主要如下:

  1. 我們可以使用 LLVM 或者 GCC 工具将編寫的 BPF 代碼程式編譯成 BPF 位元組碼;
  2. 然後使用加載程式 Loader 将位元組碼加載至核心;核心使用驗證器(verfier) 元件保證執行位元組碼的安全性,以避免對核心造成災難,在确認位元組碼安全後将其加載對應的核心子產品執行;BPF 觀測技術相關的程式程式類型可能是 kprobes/uprobes/tracepoint/perf_events 中的一個或多個,其中:
    • kprobes:實作核心中動态跟蹤。 kprobes 可以跟蹤到 Linux 核心中的函數入口或傳回點,但是不是穩定 ABI 接口,可能會因為核心版本變化導緻,導緻跟蹤失效。
    • uprobes:使用者級别的動态跟蹤。與 kprobes 類似,隻是跟蹤的函數為使用者程式中的函數。
    • tracepoints:核心中靜态跟蹤。tracepoints 是核心開發人員維護的跟蹤點,能夠提供穩定的 ABI 接口,但是由于是研發人員維護,數量和場景可能受限。
    • perf_events:定時采樣和 PMC。
  3. 核心中運作的 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.md

BCC 中每一個工具都有一個對應的使用樣例,比如

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 版本如下:

【BPF入門系列-1】eBPF 技術簡介

圖 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           
【BPF入門系列-1】eBPF 技術簡介

圖 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

【BPF入門系列-1】eBPF 技術簡介

圖 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 實踐經驗

6. 參考資料

  1. The BSD Packet Filter: A New Architecture for User-level Packet Capture
  2. [[譯] Cilium:BPF 和 XDP 參考指南(2019) ]( http://arthurchiao.art/blog/cilium-bpf-xdp-reference-guide-zh/) Cillum BPF and XDP Reference Guide
  3. Cloudflare架構以及BPF如何占據世界
  4. 關於 BPF 和 eBPF 的筆記
  5. Dive into BPF: a list of reading material 中文
  6. eBPF 簡史
  7. https://www.youtube.com/watch?v=znBGt7oHJyQ
  8. BPF Documentation HOWTO interact with BPF subsystem
  9. Linux 核心 BPF 文檔
  10. Linux Extended BPF (eBPF) Tracing Tools Brendan Gregg
  11. SDN handbook
  12. 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