天天看點

手把手教你如何在 Linux 顯微鏡(LMP)項目中開啟 eBPF 之旅?

作者:linux上的碼農

eBPF 為 Linux 核心提供了可擴充性,使開發人員能夠對 Linux 核心進行程式設計,以便根據他們的業務需求快速建構智能的或豐富的功能。

我們的 LMP(Linux Microscope) 項目^[1]^ 是為了充分挖掘 ebpf 的可能性而建立的,項目以建構 eBPF 學習社群、成為 eBPF 工具集散地、孵化 eBPF 想法和項目為目标,正在大力建設中。之前我們在 LMP 其中的 eBPF 超市 中包含了大量由個人開發者編寫的 eBPF 工具,覆寫了網絡、性能分析、安全等多種功能,我們正在嘗試把其中的一些程式遷移到 eBPF Hub,一些規範化的 eBPF 程式庫,可以随時下載下傳運作,或嵌入大型應用程式中作為插件使用。

我們嘗試在 eBPF Hub 中,基于 eunomia-bpf 開發架構建立符合 OCI 标準的 WASM 和 eBPF 程式,并利用 ORAS 簡化擴充 LMP 的 eBPF 分發、加載、運作能力。

快速使用

如果您想快速開始 eBPF,可以使用我們開發的輕量級架構之上的指令行程式 lmp-cli。當使用腳本安裝好我們的架構之後,您隻需要一條指令,無需任何編譯,即可體會到 eBPF 的強大之處:

$ lmp run sigsnoop
download with curl: https://linuxkerneltravel.github.io/lmp/sigsnoop/package.json
running and waiting for the eBPF events from perf event...
time pid tpid sig ret comm
00:21:41 109955 112863 28 0 gnome-terminal-
00:21:41 109955 112862 28 0 gnome-terminal-
...
           

如果您使用過 bcc 等 eBPF 開發工具,您一定會驚喜于 LMP 的便捷性。LMP 中包含了各種各樣的 eBPF 程式,這種便捷的運作,離不開我們基于的底層架構 eunomia-bpf,它完全實作了“一次編譯,處處運作”的 eBPF 跨平台目标。在 eunomia-bpf 架構下,LMP 開發的 eBPF 應用不僅可以适配任意架構和不同核心版本,而且還具有輕量級、良好的隔離性等優點,可以作為插件到嵌入大型應用之中。

eunomia-bpf:結合 eBPF 和 WASM 的輕量級開發架構

作為一個 eBPF 程式的輕量級開發加載架構,eunomia-bpf 基于 WASM 運作時和 BTF 技術,包含了一個使用者态動态加載架構/運作時庫,以及一個簡單的編譯 WASM 和 eBPF 位元組碼的工具鍊容器。

Wasm 是為了一個可移植的目标而設計的,可作為 C/C+/RUST 等進階語言的編譯目标,使用戶端和伺服器應用程式能夠在 Web 上部署。目前已經發展成為一個輕量級、高性能、跨平台和多語種的軟體沙盒環境,被運用于雲原生軟體元件。eunomia-bpf 将 eBPF 使用者态的所有控制和資料處理邏輯全部移到 WASM 虛拟機中,通過 WASM module 打包和分發 eBPF 位元組碼,同時在 WASM 虛拟機内部控制整個 eBPF 程式的加載和執行,将二者的優勢結合了起來。

在 WASM 子產品中編寫 eBPF 代碼和通常熟悉的使用 libbpf 架構或 Coolbpf 開發 eBPF 程式的方式是基本一樣的,WASM 的複雜性會被隐藏在 eunomia-bpf 的編譯工具鍊和運作時庫中,開發者可以專注于 eBPF 程式的開發和調試,不需要了解 WASM 的背景知識,也不需要擔心 WASM 的編譯環境配置。

大緻來說,eunomia-bpf 在 WASM 運作時和使用者态的 libbpf 中間多加了一層抽象層,使得一次編譯、到處運作的 eBPF 代碼可以從 JSON 對象中加載動态。JSON 對象會在編譯時被包含在 WASM 子產品中,是以在運作時,我們可以通過解析 JSON 對象來擷取 eBPF 程式的資訊,然後動态加載 eBPF 程式。通過 WASM module 打包和分發 eBPF 位元組碼,同時在 WASM 虛拟機内部控制整個 eBPF 程式的加載和執行,eunomia-bpf 就可以将二者的優勢結合起來,讓任意 eBPF 程式能有如下特性:

  • 可移植:讓 eBPF 工具和應用不需要進行重新編譯即可以跨平台分發,省去了複雜的交叉編譯流程;
  • 隔離性:讓 eBPF 程式的加載和執行、以及使用者态的資料處理流程更加安全可靠。
  • 包管理:完成 eBPF 程式或工具的分發、管理、加載等工作。
  • 靈活性:使每個人都可以使用官方和未經修改的應用程式來加載自定義擴充,任何 eBPF 程式的錯誤修複和/或更新都可以在運作時推送和/或測試,而不需要更新和/或重新部署一個新的二進制。
  • 輕量級:與 Linux 容器應用相比,WASM 微服務冷啟動的時間是 1%,可以實作 eBPF as a service,讓 eBPF 程式的加載和執行變得更加輕量級、快速、簡便易行。

我們已經測試了在 x86、ARM 等不同架構不同核心版本的 Linux 系統上,eunomia-bpf 架構都可以使用同一個預編譯 eBPF 程式二進制,從雲端一行指令擷取到本地之後運作。之後 eunomia-bpf 還會添加 RISC-V 等更多架構的支援。

更多linux核心視訊教程文檔資料免費領取背景私信【核心】自行擷取.

手把手教你如何在 Linux 顯微鏡(LMP)項目中開啟 eBPF 之旅?
手把手教你如何在 Linux 顯微鏡(LMP)項目中開啟 eBPF 之旅?

使用 lmp-cli 建構一個 eBPF 項目

如果您是一個 eBPF 工具的使用者,您可以無需任何編譯流程,也不需要了解任何 eBPF 和 WASM 的相關知識,使用 就可以直接運作 LMP 倉庫的小程式,其中會調用指令從雲端從庫中下載下傳對應的小程式。lmp run <name>``lmp pull <name>

如果您是一個 eBPF 程式的開發者,讓我們開始建立、編譯并運作一個簡單的程式。在這裡,我們使用基簡單指令行工具 lmp-cli,概述如何從四個步驟開始建構。

1. 準備你的環境

eBPF 本身是一種 Linux 核心技術,是以任何實際的 BPF 程式都必須在 Linux 核心中運作。我建議您從核心 5.4 或更新的版本開始。從 SSH 終端,檢查核心版本,并确認您已經啟用了 CONFIG_DEBUG_INFO_BTF:

uname -r
cat /boot/config-$(uname -r) | grep CONFIG_DEBUG_INFO_BTF
           

你會看到類似這樣的輸出:

$ uname -r
5.15.0-48-generic

$ cat /boot/config-$(uname -r) | grep CONFIG_DEBUG_INFO_BTF
CONFIG_DEBUG_INFO_BTF=y
CONFIG_DEBUG_INFO_BTF_MODULES=y
           

安裝指令行工具 lmp-cli:

curl https://github.com/GorilaMond/lmp_cli/releases/download/lmp/install.sh | sh
           

2. 建立項目的核心部分

使用建立一個項目模闆,來初始化你的核心程式,快速地投入到代碼的編寫中:lmp init

lmp init hello
           

成功建立項目後,您将看到如下類似的輸出:

$ lmp init hello
Cloning into 'ebpm-template'...
           

它實際上建立了一個項目名對應的檔案夾,裡面有這些檔案:

$ cd hello/
$ ll
...
-rw-rw-r--  1 a a 2910 10月 17 23:18 bootstrap.bpf.c
-rw-rw-r--  1 a a  392 10月 17 23:18 bootstrap.bpf.h
-rw-rw-r--  1 a a  221 10月 17 23:18 config.json
drwxrwxr-x  8 a a 4096 10月 17 23:18 .git/
drwxrwxr-x  3 a a 4096 10月 17 23:18 .github/
-rw-rw-r--  1 a a   21 10月 17 23:18 .gitignore
-rw-rw-r--  1 a a 2400 10月 17 23:18 README.md
           

核心程式模闆 bootstrap.bpf.c 中預設的跟蹤點為 和,用來跟蹤新程式的執行和退出,這裡不做修改。tp/sched/sched_process_exec``tp/sched/sched_process_exit

建構核心項目,如下所示。儲存您的更改,使用 建構核心程式,這會建立一個名為 package.json 的對象檔案。sudo lmp build

$ sudo lmp build
make
  ...
  BINARY   client
  DUMP_LLVM_MEMORY_LAYOUT
  DUMP_EBPF_PROGRAM
  FIX_TYPE_INFO_IN_EBPF
  GENERATE_PACKAGE_JSON
           

3. 運作核心程式

可以使用運作核心程式,沒有使用者端程式對資料的處理的情況下,該架構下核心程式将會輸出所有被 output 的資料:lmp run package.json

$ sudo lmp run ./package.json
running and waiting for the ebpf events from ring buffer...
time pid ppid exit_code duration_ns comm filename exit_event
           

一開始您不會看到任何資料,隻有當核心的跟蹤點被觸發時,這裡是新的程序被建立或退出時,才會輸出資料。這裡建立了一個虛拟終端,輸出了如下資料:

23:31:31 111788 109955 0 0 bash /bin/bash 0
23:31:31 111790 111788 0 0 lesspipe /usr/bin/lesspipe 0
...
           

4. 添加使用者态程式

我們提供的是 demo 是 C 語言版本的 WASM 開發架構,在建構好的核心項目檔案夾内,使用 生成一個 WASM 使用者态項目模闆,app.c、eunomia-include、ewasm-skel.h 這些檔案會被生成。ewasm-skel.h 是被打包為頭檔案的核心程式,app.c 是使用者态程式的模闆檔案,我們可以修改它來進行自定義的資料處理,這裡不做修改。sudo lmp gen-wasm-skel

$ sudo lmp gen-wasm-skel
make
  BPF      .output/client.bpf.o
...
           

使用建構使用者态程式,生成 app.wasm 檔案sudo lmp build-wasm

$ sudo lmp build-wasm
make
  BPF      .output/client.bpf.o
...
           

使用運作使用者态程式,json 格式的輸出為通用的資料處理做好了準備:lmp run app.wasm

$ lmp run app.wasm
running and waiting for the ebpf events from ring buffer...
{"pid":112665,"ppid":109955,"exit_code":0,"duration_ns":0,"comm":"bash","filename":"/bin/bash","exit_event":0}
{"pid":112667,"ppid":112665,"exit_code":0,"duration_ns":0,"comm":"lesspipe","filename":"/usr/bin/lesspipe","exit_event":0}
{"pid":112668,"ppid":112667,"exit_code":0,"duration_ns":0,"comm":"basename","filename":"/usr/bin/basename","exit_event":0}
...
           

另一個例子:使用 eBPF 列印程序記憶體使用狀況

可以将 bootstrap.bpf.c 重命名為 procstat.bpf.c,将 bootstrap.bpf.h 重命名為 procstat.bpf.h,然後編譯運作。對應的源代碼如下:

procstat.bpf.c

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "procstat.h"

char LICENSE[] SEC("license") = "Dual BSD/GPL";

struct {
 __uint(type, BPF_MAP_TYPE_RINGBUF);
 __uint(max_entries, 256 * 1024);
} rb SEC(".maps");


SEC("kprobe/finish_task_switch")
int BPF_KPROBE(finish_task_switch, struct task_struct *prev)
{
 struct event *e;
 struct mm_rss_stat rss = {};
 struct mm_struct *mms;
 long long *t;

 e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
 if (!e)
  return 0;

 e->pid = BPF_CORE_READ(prev, pid);
 e->vsize = BPF_CORE_READ(prev, mm, total_vm);
 e->Vdata = BPF_CORE_READ(prev, mm, data_vm);
 e->Vstk = BPF_CORE_READ(prev, mm, stack_vm);
 e->nvcsw = BPF_CORE_READ(prev, nvcsw);
 e->nivcsw = BPF_CORE_READ(prev, nivcsw);

 rss = BPF_CORE_READ(prev, mm, rss_stat);
 t = (long long *)(rss.count);
 e->rssfile = *t;
 e->rssanon = *(t + 1);
 e->vswap = *(t + 2);
 e->rssshmem = *(t + 3);
 e->size = *t + *(t + 1) + *(t + 3);

 bpf_ringbuf_submit(e, 0);
 return 0;
}
           

過程

#ifndef __BOOTSTRAP_H
#define __BOOTSTRAP_H

#define TASK_COMM_LEN 16
#define MAX_FILENAME_LEN 127

struct event {
/*程序記憶體狀态報告*/
    pid_t pid;
    long nvcsw;
    long nivcsw;
    long vsize;              //虛拟記憶體
    long size;               //實體記憶體
    long long rssanon;       //匿名頁面
    long long rssfile;       //檔案頁面
    long long rssshmem;      //共享頁面
    long long vswap;         //交換頁面
    long long Hpages;        //hugetlbPages
    long Vdata;              //Private data segments
    long Vstk;               //User stack
    long long VPTE;
};
#endif /* __BOOTSTRAP_H */
           

具體的上報事件資訊在 event 結構體中定義:

手把手教你如何在 Linux 顯微鏡(LMP)項目中開啟 eBPF 之旅?

挂載點與挂載原因分析:

  • 首先,擷取程序級别記憶體使用資訊需要擷取到程序的 task_struct 結構體,其中在 mm_struct 成員中存在一個儲存程序目前記憶體使用狀态的數組結構,是以有關程序的大部分記憶體使用資訊都可以通過這個數組獲得。
  • 其次,需要注意函數的插入點,插入點的選取關系到資料準确性是否得到保證,而在程序的記憶體申請,釋放,規整等代碼路徑上都存在頁面狀态改變,但是數量資訊還沒有更新的相關結構中的情況,如果插入點這兩者中間,資料就會和實際情況存在差異,所有在確定可以擷取到程序 PCB 的前提下,選擇在程序排程代碼路徑上考慮。而 finish_task_switch 函數是新一個程序第一個執行的函數,做的事卻是給上一個被排程出去的程序做收尾工作,是以這個函數的參數是上一個程序的 PCB,從這塊獲得上一個程序的記憶體就可以確定在它沒有再次被排程上 CPU 執行的這段時間内的記憶體穩定性資料。
  • 是以最後選擇将程式挂載到 finish_task_switch 函數上。資料來源有兩部分,一個是 mm_struc 結構本身存在的狀态資訊,另一個是在 mm_rss_stat 結構中。

也可以在 bolipi 的平台中線上編譯,線上體驗運作 eBPF 程式:https://bolipi.com/ebpf/home/online

完整的代碼、文檔和運作結果可以在 LMP 中 eBPF_Supermarket 處找到:eBPF_Supermarket/Memory_Subsystem/memstat/procstat^[2]^

相關背景

LMP 項目的成立初衷是:

  • 面向 eBPF 初學者和愛好者,提供 eBPF 學習資料、程式/項目案例,建構 eBPF 學習社群
  • 成為 eBPF 工具集散地,我們相信每一位 eBPF 初學者和愛好者都有無限的創造力
  • 孵化 eBPF 想法、相關工具、項目

LMP 目前分為四個子項目:

  • eBPF_Supermarket 中包含了大量由個人開發者編寫的 eBPF 工具,覆寫了網絡、性能分析、安全等多種功能;
  • eBPF_Hub 是規範化的 eBPF 程式庫,可以随時下載下傳運作;
  • eBPF_Visualization 是為 eBPF 程式管理而開發的 web 管理系統,聚焦 eBPF 資料可視化和核心可視化;
  • eBPF_Documentation 為社群收集、梳理和原創的 eBPF 相關資料和文檔。

目前 LMP 項目也存在一些問題,例如對于 eBPF 工具的開發者,存在非常多而且複雜的使用者态可視化、展示方案,有許多套系統提供可視化的實作并且有多種語言混合,缺乏展示标準、也難以進行可視化的整合等。是以,我們希望嘗試借助 eunomia-bpf 提供的符合 OCI 标準的 WASM 和 eBPF 程式,提供标準化、高可擴充性的基于 eBPF 的可視化、資料展示、分析平台,利用 ORAS 簡化擴充 eBPF 的分發、加載、運作能力,為 eBPF 工具的開發者和使用者提供更加簡單、高效的體驗。

網絡裝配

WebAssembly 是一種新的編碼方式,可以在現代的網絡浏覽器中運作 - 它是一種低級的類彙編語言,具有緊湊的二進制格式,可以接近原生的性能運作,并為諸如 c\c++ 等語言提供一個編譯目标,以便它們可以在 Web 上運作。它也被設計為可以與 JavaScript 共存,允許兩者一起工作。而且,更棒的是,這是通過 W3C WebAssembly Community Group 開發的一項網絡标準,并得到了來自各大主要浏覽器廠商的積極參與。

盡管 WebAssembly 是為運作在 Web 上設計的,它也可以在其它的環境中良好地運作。包括從用作測試的最小化 shell ,到完全的應用環境 —— 例如:在資料中心的伺服器、物聯網(IoT)裝置或者是移動/桌面應用程式。甚至,運作嵌入在較大程式裡的 WebAssembly 也是可行的。通常,通過維持不需要 Web API 的非 Web 路徑,WebAssembly 能夠在許多平台上用作便攜式的二進制格式,為移植性、工具和語言無關性帶來巨大的好處。(因為它支援 c\c++ 級語義)

WASM 的編譯和部署流程如下:

手把手教你如何在 Linux 顯微鏡(LMP)項目中開啟 eBPF 之旅?

OCI(開放容器倡議)

開放容器協定(OCI)是一個輕量級,開放的治理結構,為容器技術定義了規範和标準。在 Linux 基金會的支援下成立,由各大軟體企業構成,緻力于圍繞容器格式和運作時建立開放的行業标準。其中包括了使用 Container Registries 進行工作的 API,正式名稱為 OCI 分發規範(又名“distribution-spec”)。這個釋出規範是基于 Docker 公司最初釋出的開源注冊伺服器編寫的,它存在于 GitHub 的distribution/distribution^[3]^ (現在是CNCF^[4]^ 項目)上。

OCI 目前提出的規範有如下這些:

手把手教你如何在 Linux 顯微鏡(LMP)項目中開啟 eBPF 之旅?

其中 runtime 和 image 的規範都已經正式釋出,而 distribution 的還在工作之中。runtime 規範中介紹了如何運作解壓縮到磁盤上的 'Filesystem Bundle'[8]。在 OCI 标準下,運作一個容器的過程就是下載下傳一個 OCI 的鏡像,将其解壓到某個 中,然後某個 OCI Runtime 就會運作這個 Bundle。Filesystem Bundle

伴随着 image spec 與分發 spec 的演化,人們開始逐漸認識到除了 Container Images 之外,Registries 還能夠用來分發 Kubernetes Deployment Files, Helm Charts, docker-compose, CNAB^[9]^ 等産物。它們可以共用同一套 API,同一套存儲,将 Registries 作為一個雲存儲系統。這就為帶來了 OCI Artifacts 的概念,使用者能夠把所有的産物都存儲在 OCI 相容的 Registiry 當中并進行分發。為此,Microsoft 将 oras^[10]^ 作為一個 client 端實作捐贈給了社群,包括 Harbor 在内的多個項目都在積極的參與。

ORAS(OCI 系統資料庫作為存儲)

Registries 正在逐漸演變為通用的元件存儲庫。為了實作這一目标,ORAS 項目提供了一種将 OCI Artifacts 從 OCI Registries 送出和拉取的方法。正在尋求通用 Registries 用戶端的使用者可以從ORAS CLI^[11]^ 中得到幫助,而開發人員可以在ORAS 用戶端的開發庫^[12]^ 之上建構自己的用戶端。

ORAS 的工作原理與您可能已經熟悉的工具(如 docker)類似。它允許您向 OCI Registries 推送(上傳)和提取(下載下傳)内容,并處理登入(身份驗證)和令牌流(授權)。ORAS 的不同之處在于将焦點從容器映像轉移到其他類型的元件上。

是以,鼓勵新的 OCI Artifacts 的作者定義他們自己的元件媒體類型,以使得他們的使用者知道如何對其進行操作。

如果您希望立即開始釋出 OCI Artifacts,請檢視ORAS CLI^[13]^ 。希望提供給自己使用者體驗的開發人員應該使用一個 ORAS 用戶端開發庫。

未來的發展方向

未來 LMP 會專注于更多的基于 eBPF 的應用工具和實踐的開發:

  1. 進一步完善 ORAS 和 OCI 鏡像相關的支援;
  2. 重構并遷移現有的 eBPF 工具,提供完整的、開箱即用的分析工具元件,例如性能工程等方面;
  3. 探索和孵化更多的 eBPF 想法、相關工具、項目;

我們所基于的 eunomia-bpf 項目也會繼續完善,專注于提供一個底層的 eBPF 開發平台和運作時基礎設施,力求帶來更好的開發和移植體驗:

  1. 測試更多平台和核心版本的支援,目前已經在 和 上成功移植并運作,接下來會對低核心版本、Android、RISC-V 等平台,以及嵌入式、邊緣計算相關的裝置進行更進一步的測試;也許在未來,我們還可以提供 Windows 上的 eBPF 程式支援和類似的開發體驗;ARM64``x86_64
  2. 提供标準化、穩定的 JSON 和 WASM 接口協定規範以及 OCI 鏡像規範,不和任何的供應商或雲服務綁定。如果不使用 eunomia-bpf 相關的底層運作時,或使用自定義的 WASM 運作時,也可以通過标準化的接口來使用 LMP 中已經有的大量 eBPF 程式生态。
  3. 提供更友好的使用者态開發接口,以及更多的使用者态開發語言 SDK,例如 Go、Rust、Python 等;
  4. 進行更多關于 WASM 和 eBPF 結合的探索;
原文作者:技術簡說
手把手教你如何在 Linux 顯微鏡(LMP)項目中開啟 eBPF 之旅?