作者 |炎尋
?過去一年,ARMS基于eBPF技術打造了Kubernetes監控,提供多語言無侵入的應用性能,系統性能,網絡性能觀測能力,驗證了eBPF技術的有效性。eBPF技術和生態發展很好,未來前景廣大,作為該技術的實踐者,本文目標是通過回答7個核心問題介紹eBPF技術本身,為大家解開eBPF的面紗。
eBPF是什么?eBPF是一個能夠在內核運行沙箱程序的技術,提供了一種在內核事件和用戶程序事件發生時安全注入代碼的機制,使得非內核開發人員也可以對內核進行控制。隨著內核的發展,eBPF 逐步從最初的數據包過濾擴展到了網絡、內核、安全、跟蹤等,而且它的功能特性還在快速發展中,早期的 BPF 被稱為經典 BPF,簡稱cBPF,正是這種功能擴展,使得現在的BPF被稱為擴展BPF,簡稱eBPF。
eBPF的應用場景是什么?網絡優化eBPF兼具高性能和高可擴展特性,使得其成為網絡方案中網絡包處理的優選方案:
高性能JIT編譯器提供近乎內核本地代碼的執行效率。
高可擴展在內核的上下文里,可以快速地增加協議解析和路由策略。
故障診斷eBPF通過kprobe,tracepoints跟蹤機制兼具內核和用戶的跟蹤能力,這種端到端的跟蹤能力可以快速進行故障診斷,與此同時eBPF支持以更加高效的方式透出profiling的統計數據,而不需要像傳統系統需要將大量的采樣數據透出,使得持續地實時profiling成為可能。
?
安全控制eBPF可以看到所有系統調用,所有網絡數據包和socket網絡操作,一體化結合進程上下文跟蹤,網絡操作級別過濾,系統調用過濾,可以更好地提供安全控制。
性能監控相比于傳統的系統監控組件比如sar,只能提供靜態的counters和gauges,eBPF支持可編程地動態收集和邊緣計算聚合自定義的指標和事件,極大地提升了性能監控的效率和想象空間。
eBPF為什么會出現?eBPF的出現本質上是為了解決內核迭代速度慢和系統需求快速變化的矛盾,在eBPF領域常用的一個例子是eBPF相對于Linux Kernel類似于Javascript相對于HTML,突出的是可編程性。一般來說可編程性的支持通常會帶來一些新的問題,比如內核模塊其實也是為了解決這個問題,但是他沒有提供很好的邊界,導致內核模塊會影響內核本身的穩定性,在不同的內核版本需要做適配等。eBPF采用以下策略,使得其成為一種安全高效地內核可編程技術:
安全eBPF 程序必須被驗證器校驗通過后才能執行,且不能包含無法到達的指令;eBPF 程序不能隨意調用內核函數,只能調用在 API 中定義的輔助函數;eBPF 程序??臻g最多只有 512 字節,想要更大的存儲,就必須要借助映射存儲。
高效借助即時編譯器(JIT),且因為 eBPF 指令依然運行在內核中,無需向用戶態復制數據,大大提高了事件處理的效率。
標準通過BPF Helpers,BTF,PERF MAP提供標準的接口和數據模型供開發者使用。
功能強大eBPF 不僅擴展了寄存器的數量,引入了全新的 BPF 映射存儲,還在 4.x 內核中將原本單一的數據包過濾事件逐步擴展到了內核態函數、用戶態函數、跟蹤點、性能事件(perf_events)以及安全控制等領域。
eBPF怎么用?5個步驟1.使用 C 語言開發一個 eBPF 程序;
即插樁點觸發事件時要調用的eBPF沙箱程序,該程序會在內核態運行。
?2.借助 LLVM 把 eBPF 程序編譯成 BPF 字節碼;
eBPF 程序編譯成 BPF 字節碼,用于后續在eBPF虛擬機內驗證并運行。
?3.通過 bpf 系統調用,把 BPF 字節碼提交給內核;
在用戶態通過bpf系統,將BPF字節碼加載到內核。
?4.內核驗證并運行 BPF 字節碼,并把相應的狀態保存到 BPF 映射中;
內核驗證BPF字節碼安全,并且確保對應事件發生時調用正確的eBPF程序,如果有狀態需要保存,則寫入對應BPF映射中,比如監控數據就可以寫到BPF映射中。?
5.用戶程序通過 BPF 映射查詢 BPF 字節碼的運行狀態。
用戶態通過查詢BPF映射的內容,獲取字節碼運行的狀態,比如獲取抓取到的監控數據。
?一個完整的 eBPF 程序,通常包含用戶態和內核態兩部分:用戶態程序需要通過 BPF 系統調用跟內核進行交互,進而完成 eBPF 程序加載、事件掛載以及映射創建和更新等任務;而在內核態中,eBPF 程序也不能任意調用內核函數,而是需要通過 BPF 輔助函數完成所需的任務。尤其是在訪問內存地址的時候,必須要借助 bpf_probe_read 系列函數讀取內存數據,以確保內存的安全和高效訪問。在 eBPF 程序需要大塊存儲時,我們還需要根據應用場景,引入特定類型的 BPF 映射,并借助它向用戶空間的程序提供運行狀態的數據。?
eBPF程序分類和使用場景bpftool feature probe | grep program_type
以上命令可以查看系統支持的eBPF程序類型,一般有如下類型:
eBPF program_type socket_filter is availableeBPF program_type kprobe is availableeBPF program_type sched_cls is availableeBPF program_type sched_act is availableeBPF program_type tracepoint is availableeBPF program_type xdp is availableeBPF program_type perf_event is availableeBPF program_type cgroup_skb is availableeBPF program_type cgroup_sock is availableeBPF program_type lwt_in is availableeBPF program_type lwt_out is availableeBPF program_type lwt_xmit is availableeBPF program_type sock_ops is availableeBPF program_type sk_skb is availableeBPF program_type cgroup_device is availableeBPF program_type sk_msg is availableeBPF program_type raw_tracepoint is availableeBPF program_type cgroup_sock_addr is availableeBPF program_type lwt_seg6local is availableeBPF program_type lirc_mode2 is NOT availableeBPF program_type sk_reuseport is availableeBPF program_type flow_dissector is availableeBPF program_type cgroup_sysctl is availableeBPF program_type raw_tracepoint_writable is availableeBPF program_type cgroup_sockopt is availableeBPF program_type tracing is availableeBPF program_type struct_ops is availableeBPF program_type ext is availableeBPF program_type lsm is available
具體可參考https://elixir.bootlin.com/linux/v5.13/source/include/linux/bpf_types.h
主要是分為3大使用場景:
跟蹤tracepoint, kprobe, perf_event等,主要用于從系統中提取跟蹤信息,進而為監控、排錯、性能優化等提供數據支撐。
網絡xdp, sock_ops, cgroup_sock_addr , sk_msg等,主要用于對網絡數據包進行過濾和處理,進而實現網絡的觀測、過濾、流量控制以及性能優化等各種豐富的功能,這里可以丟包,重定向。
cilium基本用了所有的hook點。
安全和其他lsm,用于安全,其他還有flow_dissector, lwt_in都是一些不怎么常用的,不再贅述。
eBPF的最佳實踐是什么?尋找內核的插樁點從前面可以看出來eBPF程序本身并不困難,困難的是為其尋找合適的事件源來觸發運行。對于監控和診斷領域來說,跟蹤類eBPF程序的事件源包含3類:內核函數(kprobe)、內核跟蹤點(tracepoint)或性能事件(perf_event)。此時有2個問題需要回答:
1.內核中都有哪些內核函數、內核跟蹤點或性能事件?
使用調試信息獲取內核函數、內核跟蹤點sudo ls /sys/kernel/debug/tracing/events使用bpftrace獲取內核函數、內核跟蹤點
# 查詢所有內核插樁和跟蹤點sudo bpftrace -l# 使用通配符查詢所有的系統調用跟蹤點sudo bpftrace -l "tracepoint:syscalls:*"# 使用通配符查詢所有名字包含"open"的跟蹤點sudo bpftrace -l "*open*"使用perf list獲取性能事件
sudo perf list tracepoint
2.對于內核函數和內核跟蹤點,在需要跟蹤它們的傳入參數和返回值的時候,又該如何查詢這些數據結構的定義格式呢?
使用調試信息獲取sudo cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_openat/format使用bpftrace獲取
具體如何使用以上信息,請參考bcc。
尋找應用的插樁點1.如何查詢用戶進程的跟蹤點?
靜態編譯語言通過-g編譯選項保留調試信息,應用程序二進制會包含DWARF(Debugging With Attributed Record Format),有了調試信息,可以通過 readelf、objdump、nm 等工具,查詢可用于跟蹤的函數、變量等符號列表# 查詢符號表readelf -Ws /usr/lib/x86_64-linux-gnu/libc.so.6# 查詢USDT信息readelf -n /usr/lib/x86_64-linux-gnu/libc.so.6使用bpftrace
# 查詢uprobebpftrace -l "uprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:*"# 查詢USDTbpftrace -l "usdt:/usr/lib/x86_64-linux-gnu/libc.so.6:*"
uprobe 是基于文件的。當文件中的某個函數被跟蹤時,除非對進程 PID 進行了過濾,默認所有使用到這個文件的進程都會被插樁。
上面說的是靜態編譯語言,他和內核的跟蹤類似,應用程序的符號信息可以存放在 ELF 二進制文件中,也可以以單獨文件的形式,放到調試文件中;而內核的符號信息除了可以存放到內核二進制文件中之外,還會以 /proc/kallsyms 和 /sys/kernel/debug 等形式暴露到用戶空間。
對于非靜態編譯語言來說,主要是兩種:
解釋型語言使用類似編譯型語言應用程序的跟蹤點查詢方法,查詢它們在解釋器層面的 uprobe 和 USDT 跟蹤點,如何將解釋器層面的行為和應用行為關聯需要相關語言的專家來分析。
即時編譯型語言這類語言的應用源代碼會先編譯為字節碼,再由即時編譯器(JIT)編譯為機器碼執行,還會有大量的優化,跟蹤難度很大,同解釋型編程語言類似,uprobe 和 USDT 跟蹤只能用在即時編譯器上,從即時編譯器的跟蹤點參數里面獲取最終應用程序的函數信息。找出即時編譯器的跟蹤點同應用程序運行之間的關系需要相關語言的專家來分析。
可以參考BCC的應用程序跟蹤,用戶進程的跟蹤,本質上是通過斷點去執行 uprobe 處理程序。雖然內核社區已經對 BPF 做了很多的性能調優,跟蹤用戶態函數(特別是鎖爭用、內存分配之類的高頻函數)還是有可能帶來很大的性能開銷。因此,我們在使用 uprobe 時,應該盡量避免跟蹤高頻函數。
具體如何使用以上信息,請參考:https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#events--arguments
關聯問題與插樁點一個理想的狀態是所有問題都清楚應當觀察那些插樁點,但是這個要求技術人員對端到端的軟件棧細節都了解十分透徹,一個更加合理的方法是二八法則,將軟件棧數據流的最核心的80%脈絡抓住,保障出現問題一定會在這個脈絡被發現即可。此時再使用內核棧和用戶棧來查看具體的調用棧即可發現核心問題,比如說發現了網絡在丟包,但是不知道為什么丟,此時我們知道網絡丟包一定會調用kfree_skb內核函數,那么我們可以通過:
sudo bpftrace -e "kprobe:kfree_skb /comm==" 發現該函數的調用棧: kstack: kfree_skb+1 udpv6_destroy_sock+66 sk_common_release+34 udp_lib_close+9 inet_release+75 inet6_release+49 __sock_release+66 sock_close+21 __fput+159 ____fput+14 task_work_run+103 exit_to_user_mode_loop+411 exit_to_user_mode_prepare+187 syscall_exit_to_user_mode+23 do_syscall_64+110 entry_SYSCALL_64_after_hwframe+68 那么就可以回溯上面的函數,看看他們具體是哪一行在什么條件下調用的,就能夠定位到問題。這個方法不僅可以定位問題,也可以用于加深對內核調用的理解,比如: 可以查看所有網絡相關的跟蹤點及其調用棧。 eBPF在內核主要由5個模塊協作: 1.BPF Verifier(驗證器) 確保 eBPF 程序的安全。驗證器會將待執行的指令創建為一個有向無環圖(DAG),確保程序中不包含不可達指令;接著再模擬指令的執行過程,確保不會執行無效指令,這里通過和個別同學了解到,這里的驗證器并無法保證100%的安全,所以對于所有BPF程序,都還需要嚴格的監控和評審。 ?2.BPF JIT 將 eBPF 字節碼編譯成本地機器指令,以便更高效地在內核中執行。 ?3.多個 64 位寄存器、一個程序計數器和一個 512 字節的棧組成的存儲模塊 用于控制eBPF程序的運行,保存棧數據,入參與出參。 ?4.BPF Helpers(輔助函數) 提供了一系列用于 eBPF 程序與內核其他模塊進行交互的函數。這些函數并不是任意一個 eBPF 程序都可以調用的,具體可用的函數集由 BPF 程序類型決定。注意,eBPF里面所有對入參,出參的修改都必須符合BPF規范,除了本地變量的變更,其他變化都應當使用BPF Helpers完成,如果BPF Helpers不支持,則無法修改。 通過以上命令可以看到不同類型的eBPF程序可以運行哪些BPF Helpers。 ?5.BPF Map & context 用于提供大塊的存儲,這些存儲可被用戶空間程序用來進行訪問,進而控制 eBPF 程序的運行狀態。 bpftool feature probe 通過以上命令可以看到系統支持哪些類型的map。 先說下重要的系統調用bpf: int bpf(int cmd, union bpf_attr *attr, unsigned int size); 這里cmd是關鍵,attr是cmd的參數,size是參數大小,所以關鍵是看cmd有哪些 // 5.11內核enum bpf_cmd {BPF_MAP_CREATE, BPF_MAP_LOOKUP_ELEM, BPF_MAP_UPDATE_ELEM, BPF_MAP_DELETE_ELEM, BPF_MAP_GET_NEXT_KEY, BPF_PROG_LOAD,BPF_OBJ_PIN,BPF_OBJ_GET, BPF_PROG_ATTACH, BPF_PROG_DETACH, BPF_PROG_TEST_RUN,BPF_PROG_GET_NEXT_ID, BPF_MAP_GET_NEXT_ID, BPF_PROG_GET_FD_BY_ID, BPF_MAP_GET_FD_BY_ID,BPF_OBJ_GET_INFO_BY_FD, BPF_PROG_QUERY, BPF_RAW_TRACEPOINT_OPEN, BPF_BTF_LOAD, BPF_BTF_GET_FD_BY_ID, BPF_TASK_FD_QUERY, BPF_MAP_LOOKUP_AND_DELETE_ELEM, BPF_MAP_FREEZE, BPF_BTF_GET_NEXT_ID, BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH, BPF_LINK_CREATE,BPF_LINK_UPDATE, BPF_LINK_GET_FD_BY_ID,BPF_LINK_GET_NEXT_ID, BPF_ENABLE_STATS, BPF_ITER_CREATE,BPF_LINK_DETACH,BPF_PROG_BIND_MAP,}; 最核心的就是PROG,MAP相關的cmd,就是程序加載和映射處理。 ?1.程序加載 調用BPF_PROG_LOAD cmd,會將BPF程序加載到內核,但eBPF 程序并不像常規的線程那樣,啟動后就一直運行在那里,它需要事件觸發后才會執行。這些事件包括系統調用、內核跟蹤點、內核函數和用戶態函數的調用退出、網絡事件,等等,所以需要第2個動作。 ?2.綁定事件 b.attach_kprobe(event="xxx", fn_name="yyy") 以上就是將特定的事件綁定到特定的BPF函數,實際實現原理如下: (1)借助 bpf 系統調用,加載 BPF 程序之后,會記住返回的文件描述符; (2)通過attach操作知道對應函數類型的事件編號; (3)根據attach的返回值調用 perf_event_open 創建性能監控事件; (4)通過 ioctl 的 PERF_EVENT_IOC_SET_BPF 命令,將 BPF 程序綁定到性能監控事件。 ?3.映射操作 通過MAP相關的cmd,控制MAP增刪,然后用戶態基于該MAP與內核狀態進行交互。 建議>=4.14 eBPF的生態自下而上的情況如下: 1.基礎設施 支持eBPF基礎能力的發展。 2.開發工具集 主要是用于加載,編譯,調試eBPF程序,不同語言有不同的開發工具集: https://github.com/cilium/ebpf https://github.com/aquasecurity/libbpfgo https://github.com/libbpf/libbpf ?3、eBPF應用 提供一套開發工具和腳本。 基于bcc,提供一個腳本語言。 網絡優化和安全 網絡安全 高性能4層負載均衡 可觀測 可觀測 可觀測 調度bpftrace腳本 分布式環境下啟動和管理eBPF程序的平臺 動態linux trace Linux運行時安全監測 4、跟蹤生態的網站 通過上面的介紹,相信大家對eBPF已經有了足夠的理解,eBPF提供的只是一個框架和機制,核心還是需要用eBPF的人對軟件棧的理解,找到合適的插樁點,能夠和應用問題進行關聯。 1.全覆蓋 內核,應用程序插樁點全覆蓋。 ?2.無侵入 不需要修改任何被hook的代碼。 3.可編程 動態下發eBPF程序,邊緣動態執行指令,動態聚合分析。
X 關閉
X 關閉