共计 10807 个字符,预计需要花费 28 分钟才能阅读完成。
简介:coolbpf,能够酷玩的 BPF!来看看让 BPF 加了双翅膀的它到底有多硬核?
vcg_VCG211275017166_RF.jpg
文 / 零碎运维 SIG(Special Interest Group)
引言
BPF 是一个新的动静跟踪技术,目前这项技术正在粗浅的影响着咱们的生产和生存。BPF 在四大利用场景施展着巨大作用:
零碎故障诊断:它能够动静插桩透视内核。
网络性能优化:它能够对接管和发送的网络包做批改和转发。
系统安全:它能够监控文件关上和敞开从而做出平安决策等。
性能监控:它能够查看函数消耗工夫从而晓得性能瓶颈点。
BPF 技术也是随着 Linux 内核的倒退而倒退的,Linux 内核版本经验了 3.x 向 4.x 到 5.x 演进,eBPF 技术的反对也是从 4.x 开始更加欠缺起来,特地是 5.x 内核也减少了十分多的高级个性。然而云上服务器有大量的 3.10 内核版本是不反对 eBPF 的,为了让咱们现有的 eBPF 工具在这些存量机器得以运行,咱们移植了 BPF 到低版本内核,同时基于 libbpf 的 CO-RE 能力,保障一个工具可运行在 3.x/4.x/5.x 的低、中、高内核版本。
BPF 的开发方式有很多,以后比拟热门的有:
1)纯 libbpf 利用开发:借助 libbpf 库加载 BPF 程序到内核的形式:这种开发方式不仅效率低,没有根底库封装,所有必备步骤和根底函数都须要本人摸索。
2)借助 BCC 等开源我的项目:开发效率高、可移植性好,并且反对动静批改内核局部代码,非常灵活。但存在部署依赖 Clang/LLVM 等库;每次运行都要执行 Clang/LLVM 编译,重大耗费 CPU、内存等资源,容易与其它服务争抢。
coolbpf 我的项目,以 CO-RE(Compile Once-Run Everywhere)为根底实现,保留了资源占用低、可移植性强等长处,还交融了 BCC 动静编译的个性,适宜在生产环境批量部署所开发的利用。coolbpf 创始了一个新的思路,利用近程编译的思维,把用户的 BPF 程序推送到远端的服务器并返回给用户.o 或.so,提供高级语言如 Python/Rust/Go/C 等进行加载,而后在全量内核版本平安运行。用户只需专一本人的性能开发,不必关怀底层库(如 LLVM、python 等)装置、环境搭建,给宽广 BPF 爱好者提供一种新的摸索和实际。
一、BPF 开发方式比照
BPF 经验了传统的 setsockopt 形式的 sock filter 报文过滤,到现在应用 libbpf CO-RE 形式进行监控和诊断性能的开发,是和 eBPF 与硬件紧密结合的优良的指令集能力及 libbpf 通用库的开源凋谢分不开的,让咱们一起回顾一下 BPF 的开发方式,并在此基础上推出基于近程编译思维为外围的 coolbpf,它站在了伟人的肩膀上,进行了资源优化、简洁编程和效率晋升。
1、原始阶段
在 BPF 还叫伯克利报文过滤 (cBPF) 的时候,它通过 sock filter 将原始的 BPF 指令码,利用 setsockopt 加载到内核,通过 setsockopt 加载到内核,通过在 packet_rcv 调用 runfilter 运行这段程序来进行报文过滤。这种形式,BPF 字节码的生成十分原始,相似于手工编写汇编程序,过程是十分苦楚的。
static struct sock_filter filter[6] = {
{OP_LDH, 0, 0, 12}, // ldh [12]
{OP_JEQ, 0, 2, ETH_P_IP}, // jeq #0x800, L2, L5
{OP_LDB, 0, 0, 23}, // ldb [23]
{OP_JEQ, 0, 1, IPPROTO_TCP}, // jeq #0x6, L4, L5
{OP_RET, 0, 0, 0}, // ret #0x0
{OP_RET, 0, 0, -1,}, // ret #0xffffffff
};
int main(int argc, char **argv)
{
…
struct sock_fprog prog = {6, filter};
…
sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
…
if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog))) {
return 1;
}
…
}
2、激进阶段
例子为 samples/bpf 上面的 sockex1_kern.c 和 sockex1_user.c,代码分为两局部,通常命名为 xxx_kern.c 和 xxx_user.c,前者加载到内核空间中执行,后者在用户空间执行。BPF 程序编写实现后就通过 Clang/LLVM 进行编译,xxx_user.c 里显式的去加载生成的 xxx_kernel.o 文件。这种形式尽管应用了编译器反对主动生成了 BPF 字节码,但代码组织和 BPF 加载形式比拟激进,用户须要写十分多的反复代码。
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, u32);
__type(value, long);
__uint(max_entries, 256);
} my_map SEC(“.maps”);
SEC(“socket1”)
int bpf_prog1(struct __sk_buff *skb)
{
int index = load_byte(skb, ETH_HLEN + offsetof(struct iphdr, protocol));
long *value;
if (skb->pkt_type != PACKET_OUTGOING)
return 0;
value = bpf_map_lookup_elem(&my_map, &index);
if (value)
__sync_fetch_and_add(value, skb->len);
return 0;
}
char _license[] SEC(“license”) = “GPL”;
int main(int ac, char **argv)
{
struct bpf_object *obj;
struct bpf_program *prog;
int map_fd, prog_fd;
char filename[256];
int i, sock, err;
FILE *f;
snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
obj = bpf_object__open_file(filename, NULL);
if (libbpf_get_error(obj))
return 1;
prog = bpf_object__next_program(obj, NULL);
bpf_program__set_type(prog, BPF_PROG_TYPE_SOCKET_FILTER);
err = bpf_object__load(obj);
if (err)
return 1;
prog_fd = bpf_program__fd(prog);
map_fd = bpf_object__find_map_fd_by_name(obj, "my_map");
...
}
3、BCC 初始阶段
BCC 的呈现突破了激进的开发方式,杰出的运行时编译和根底库封装能力,极大的升高了开发难度,有了不少迷妹,而后开始攻城略地,相似资本的疾速扩张。用户只须要在 Python 程序里 attach 一段 prog,而后进行数据分析和解决,毛病是必须在生产环境上装置 Clang 和 python 库,运行时有 CPU 资源刹时冲高,导致呈现加载 BPF 程序后问题不复现的可能。
int trace_connect_v4_entry(struct pt_regs ctx, struct sock sk)
{
if (container_should_be_filtered()) {
return 0;
}
u64 pid = bpf_get_current_pid_tgid();
##FILTER_PID##
u16 family = sk->__sk_common.skc_family;
##FILTER_FAMILY##
// stash the sock ptr for lookup on return
connectsock.update(&pid, &sk);
return 0;
}
initialize BPF
b = BPF(text=bpf_text)
if args.ipv4:
b.attach_kprobe(event="tcp_v4_connect", fn_name="trace_connect_v4_entry")
b.attach_kretprobe(event="tcp_v4_connect", fn_name="trace_connect_v4_return")
b.attach_kprobe(event=”tcp_close”, fn_name=”trace_close_entry”)
b.attach_kretprobe(event=”inet_csk_accept”, fn_name=”trace_accept_return”)
4、BCC 高级阶段
BCC 风行一时,俘获了不少开发者。因为时代在提高,需要也在变。libbpf 横空出世及 CO-RE 思维流行,BCC 本人也在改革,开始借助 BTF 的形式反对重定位,心愿同一套程序在任何 Linux 零碎都能顺利运行。然而,有些构造体在不同内核版本上,或者成员名字变了、或者成员的含意变了(从微秒变成了毫秒),这种形式就须要程序处理。在 4.x 等中版本内核上,还须要通过 debuginfo 生成独立的 BTF 文件,过程还是相当简单。
SEC(“kprobe/inet_listen”)
int BPF_KPROBE(inet_listen_entry, struct socket *sock, int backlog)
{
__u64 pid_tgid = bpf_get_current_pid_tgid();
__u32 pid = pid_tgid >> 32;
__u32 tid = (__u32)pid_tgid;
struct event event = {};
if (target_pid && target_pid != pid)
return 0;
fill_event(&event, sock);
event.pid = pid;
event.backlog = backlog;
bpf_map_update_elem(&values, &tid, &event, BPF_ANY);
return 0;
}
include “solisten.skel.h”
…
int main(int argc, char **argv)
{
...
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
libbpf_set_print(libbpf_print_fn);
obj = solisten_bpf__open();
obj->rodata->target_pid = target_pid;
err = solisten_bpf__load(obj);
err = solisten_bpf__attach(obj);
pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
handle_event, handle_lost_events, NULL, NULL);
...
}
5、资源共享阶段
BCC 尽管也反对了 CO-RE,然而依然存在代码绝对固定,无奈动静配置的问题,同时还须要搭建编译工程。coolbpf 把编译资源放到一台服务器上,提供近程编译能力,大家共享近程服务器资源,只须要把 bpf.c 推送到远端服务器,这台服务器会开动马达,减速输入 .o 和 .so。不论用户应用 Python 还是 Go 语言、Rust 或 C 语言,只须要在程序 ini t 的时候加载这些 .o 或 .so 就能够把 BPF 程序 attach 到内核的 hook 点,而后专一于解决来自 BPF 程序输入的信息,进行性能开发。
coolbpf 把 BTF 制作、代码编译、数据处理、功能测试集一身,生产效率大幅晋升,使 BPF 开发进入一个更优雅境界:
开箱即用:内核侧仅提供 bpf.c 即可,齐全剥离出内核编译工程。
复用编译成绩:本地侧无编译过程,不存在库依赖和 CPU、内存等资源耗费问题。
自适应不同版本差别:更适宜在集群多个不同内核版本共存的场景。
pip 装置 coolbpf 命令,它会把 xx.bpf.c 发送到编译服务器编译。
pip install coolbpf
…
import time
from pylcc.lbcBase import ClbcBase
bpfPog = r”””
include “lbc.h”
SEC(“kprobe/wake_up_new_task”)
int j_wake_up_new_task(struct pt_regs *ctx)
{
struct task_struct parent = (struct task_struct )PT_REGS_PARM1(ctx);
bpf_printk(“hello lcc, parent: %d\n”, _(parent->tgid));
return 0;
}
char _license[] SEC(“license”) = “GPL”;
“””
class Chello(ClbcBase):
def __init__(self):
super(Chello, self).__init__("hello", bpf_str=bpfPog)
while True:
time.sleep(1)
if __name__ == "__main__":
hello = Chello()
pass
二、coolbpf 性能及架构
13.png
后面剖析了 BPF 的开发方式,coolbpf 借助近程编译把开发和编译这个过程进一步优化,总结一下它以后蕴含的 6 大性能:
1)本地编译服务,根底库封装:客户应用本地容器镜像编译程序,调用封装的通用函数库简化程序编写和数据处理。
本地编译服务,把同样的库和常用工具放在容器镜像里,编译时间接到容器外面编译。咱们应用如下镜像进行编译,用户也能够通过 docker 本人搭建容器镜像。
容器镜像:
registry.cn-hangzhou.aliyuncs.com/alinux/coolbpf:latest
用户能够 pull 这个镜像进行本地编译,一些罕用的库和工具,通过咱们提供的镜像就曾经蕴含在外面,省去了构建环境的繁冗。
2)近程编译服务:接管 bpf.c,生成 bpf.so 或 bpf.o,提供给高级语言进行加载,用户只专一本人的性能开发,不必关怀底层库装置、环境搭建。
近程编译服务,目前用户开发代码时只须要 pip install coolbpf,程序就会主动到咱们的编译服务器进行编译。你也能够参考 compile/remote-compile/lbc/ 本人搭建编译服务器(咱们前面会陆续开源这个编译服务器源码),过程可能会比较复杂。这样搭建好的服务器,你能够集体应用或者在公司提供给大家一起应用。
3)高版本个性通过 kernel module 形式补齐到低版本,如 ring buffer 个性,backport BPF 个性到 3.10 内核。
因为存量 3.10 内核的服务器仍然很多,为了让同一个 BPF 程序也能运行在低版本内核,为了保护不便且不必批改程序代码,只须要 install 一个 ko,就能够反对 BPF,让低版本也享受到了 BPF 的红利。
4)BTF 的主动生成和全网最新内核版本爬虫。主动发现最新的 CentOS、ubuntu、Anolis 等内核版本,主动生成对应的 BTF。
要具备一次编译多处运行 CO-RE 能力,没有 BTF 是行不通的。coolbpf 不仅提供一个制作 BTF 的工具,还会主动发现和制作最新内核版本的 BTF,以供大家下载和应用。
5)各内核版本功能测试自动化,工具编写后主动进行装置测试,保障用户性能在生产环境运行前预测试。
没有上线运行过的 BPF 程序和工具,肯定概率上是存在危险的。coolbpf 提供一套自动化测试流程,在大部分内核环境都事后进行根本的功能测试,保障工具真正运行在生产环境时不会出大问题。
6)Python、Rust、Go、C 等高级语言反对。
目前 coolbpf 我的项目反对应用 Python、Rust、Go 及 C 语言的用户程序开发,不同语言开发者都能在本人最善于的畛域施展最大的劣势。
总之,coolbpf 使得 BPF 程序和利用程序开发在一个平台上闭环解决了,无效晋升了生产力,笼罩了以后支流的开发语言,适宜更多的 BPF 爱好者入门学习,也适宜零碎运维人员高效开发监控和诊断程序。
下图为 coolbpf 的性能和工具反对状况,欢送更多优良 BPF 工具退出:
14.png
三、实际阐明
coolbpf 目前蕴含 pylcc、rlcc、golcc 和 clcc,以及 glcc 子目录,别离是高级语言 Python、Rust 和 Go 语言反对近程和本地编译的能力,glcc(g 代表 generic) 是通过将高版本的 BPF 个性移植到低版本,通过 kernel module 的形式在低版本上运行。上面咱们别离简略介绍它的应用。
1、pylcc(基于 Python 的 LCC)
pylcc 在 libbpf 根底上进行封装,将简单的编译工程交由容器执行。
15.png
代码编写十分简洁,只须要三步就能实现,pyLCC 技术关键点:
1)执行 pip install coolbpf 装置
2)xx.bpf.c 的编写:
bpfPog = r”””
include “lbc.h”
LBC_PERF_OUTPUT(e_out, struct data_t, 128);
LBC_HASH(pid_cnt, u32, u32, 1024);
LBC_STACK(call_stack,32);
3)xx.py 编写,只须要这一步,程序就能够运行起来。用户关注从内核收到的数据进行剖析就能够:
importtimefrompylcc.lbcBaseimportClbcBase
classPingtrace(ClbcBase):def__init__(self):super(Pingtrace, self).__init__(“pingtrace”)
bpf.c 里须要被动蕴含 lbc.h,它告知近程服务器的行为,本地不须要有这个文件。其内容如下:
include “vmlinux.h”
include <linux/types.h>
include <bpf/bpf_helpers.h>
include <bpf/bpf_core_read.h>
include <bpf/bpf_tracing.h>
2、rlcc(基于 Rust 的 LCC)
Rust 语言反对近程编译和本地编译的能力。通过在 makefile 中应用 coolbpf 的命令把 bpf.c 发送到服务端,服务端返回 .o,这个与 Python 和 C 返回 .so 有很大区别,Rust 本人解决通用的 load、attach 的过程。其余相似于 Python 的开发,不再赘述。
编译 example 流程:
SKEL_RS=1 cargo build –release 生成 rust skel 文件;
SKEL_RS=0 cargo build –release 无需在生成 rust skel 文件;
默认 SKEL_RS 为 1.
编译 rexample 流程:
rexample 应用了近程编译性能,具体编译流程如下:
运行命令 mkdir build & cd build 创立编译目录;
运行命令 cmake .. 生成 Makefile 文件;
运行命令 make rexample;
运行 example 程序: ../lcc/rlcc/rexample/target/release/rexample.
fn main() -> Result<()>{
let opts = Command::from_args();
let mut skel_builder = ExampleSkelBuilder::default();
if opts.verbose {skel_builder.obj_builder.debug(true);
}
bump_memlock_rlimit()?;
let mut open_skel = skel_builder.open()?;
let mut skel = open_skel.load()?;
skel.attach()?;
let perf = PerfBufferBuilder::new(skel.maps_mut().events())
.sample_cb(handle_event)
.lost_cb(handle_lost_events)
.build()?;
loop {perf.poll(Duration::from_millis(100))?;
}
}
3、glcc(generic LCC,高版本个性移植到低版本)
背景:
目前基于 eBPF 编写的程序只能在高版本内核(反对 eBPF 的内核)上运行,无奈在不反对 eBPF 性能的内核上运行。
线上有很多 Alios 或者 CentOS 低版本内核须要保护。
存量 BPF 工具或我的项目代码,心愿不做批改能跨内核运行。
为此咱们提出了一种在低版本内核运行 eBPF 程序的办法,使得二进制程序无需任何批改即可在不反对 BPF 的内核上运行。
上面从架构上梳理,低版本内核运行 BPF 的可能。
16.png
Hook 是一个动静库,因为低版本内核不反对 bpf() 的零碎调用,原来在用户态创立 map、创立 prog 以及很多 helper 函数(如 bpf_update_elem 等)将不能运行,Hook 提供一个动静机制,把这些零碎调用转成 ioctl 命令,设置到一个叫 ebpfdriver 的 kernel module,通过他进行创立一些数据结构模仿 map 和 prog,同时注册 kprobe 和 tracepoint 的 handler。这样有数据到来时,就会运行注册在 kprobe 和 tracepoint 的回调。
运行机制见下图:
17.png
利用 Hook 程序将 BPF 的 syscall 转换成 ioctl 模式,将零碎调用参数传递给 eBPF 驱动,蕴含以下性能:
define IOCTL_BPF_MAP_CREATE _IOW(‘;’, 0, union bpf_attr *)
define IOCTL_BPF_MAP_LOOKUP_ELEM _IOWR(‘;’, 1, union bpf_attr *)
define IOCTL_BPF_MAP_UPDATE_ELEM _IOW(‘;’, 2, union bpf_attr *)
define IOCTL_BPF_MAP_DELETE_ELEM _IOW(‘;’, 3, union bpf_attr *)
define IOCTL_BPF_MAP_GET_NEXT_KEY _IOW(‘;’, 4, union bpf_attr *)
define IOCTL_BPF_PROG_LOAD _IOW(‘;’, 5, union bpf_attr *)
define IOCTL_BPF_PROG_ATTACH _IOW(‘;’, 6, __u32)
define IOCTL_BPF_PROG_FUNCNAME _IOW(‘;’, 7, char *)
define IOCTL_BPF_OBJ_GET_INFO_BY_FD _IOWR(‘;’, 8, union bpf_attr *)
eBPF 驱动收到 Ioctl 申请,会依据 cmd 来进行相应的操作,如:
A. IOCTL_BPF_MAP_CREATE:创立 map。
B. IOCTL_BPF_PROG_LOAD:加载 eBPF 字节码,进行字节码的平安验证和 jit 生成机器码。
C. IOCTL_BPF_PROG_ATTACH:将该 eBPF 程序 attach 到指定的内核函数,利用 register_kprobe 和 tracepoint_probe_register 性能实现 eBPF 程序的 attach。
另外,高版本的一些个性,比方 ringbuff,也能够通过 ko 等形式用在低版本。像 clcc 和 golcc 的应用形式,请参考 coolbpf 的 github 链接(见文末),这里不在赘述。
四、总结
coolbpf 以后具备以上 6 大性能,其目标是简化开发和编译过程,让用户专一本人的性能开发,使得宽广 BPF 爱好者疾速入门,疾速编写本人的性能程序而不必放心环境问题。明天咱们把这套零碎开源,让它服务更多人,以晋升他们的生产力,促成社会提高,让更多人参加到这个我的项目建设中来,造成一股合力,冲破一项技术。
咱们的近程编译服务,解决的是生产力的效率问题;低版本的 BPF 反对,解决的是困扰各个开发者的同一个 bin 文件如何在多内核版本无差别运行的目标,同时也心愿更多人参加进来共同提高,让云计算产业和企业服务的兄弟姐妹们全面享受到 BPF 技术的红利。
龙蜥社区零碎运维 SIG(Special Interest Group)致力于打造一个集主机治理、配置部署、监控报警、异样诊断、平安审计等一系列性能的自动化运维平台,coolbpf 是社区的一个子项目,指标是提供一个编译和开发平台,解决 BPF 在不同零碎平台的运行和生产效率晋升问题。
欢送更多开发者退出零碎运维 SIG: 网址:https://openanolis.cn/sig/sysom 邮件列表:sysom@lists.openanolis.cncoolbpf 链接:git@github.com:aliyun/coolbpf.git
—— 完 ——
退出龙蜥社群
退出微信群:增加社区助理 - 龙蜥社区小龙(微信:openanolis_assis),备注【龙蜥】与你同在;退出钉钉群:扫描下方钉钉群二维码。欢送开发者 / 用户退出龙蜥社区(OpenAnolis)交换,独特推动龙蜥社区的倒退,一起打造一个沉闷的、衰弱的开源操作系统生态!
公众号 & 小龙交换群.png
对于龙蜥社区
龙蜥社区(OpenAnolis)由企事业单位、高等院校、科研单位、非营利性组织、集体等在被迫、平等、开源、合作的根底上组成的非盈利性开源社区。龙蜥社区成立于 2020 年 9 月,旨在构建一个开源、中立、凋谢的 Linux 上游发行版社区及翻新平台。
龙蜥社区成立的短期指标是开发龙蜥操作系统 (Anolis OS) 作为 CentOS 停服后的应答计划,构建一个兼容国内 Linux 支流厂商的社区发行版。中长期指标是摸索打造一个面向未来的操作系统,建设对立的开源操作系统生态,孵化翻新开源我的项目,凋敝开源生态。
目前,Anolis OS 8.6 已公布,更多龙蜥自研个性,反对 X86_64、RISC-V、Arm64、LoongArch 架构,欠缺适配 Intel、兆芯、鲲鹏、龙芯等芯片,并提供全栈国密反对。
欢送下载:
https://openanolis.cn/download
退出咱们,一起打造面向未来的开源操作系统!
https://openanolis.cn
原文链接:http://click.aliyun.com/m/100…
本文为阿里云原创内容,未经容许不得转载。