简介: 过来一年,ARMS基于eBPF技术打造了Kubernetes监控,提供多语言无侵入的利用性能,零碎性能,网络性能观测能力,验证了eBPF技术的有效性。eBPF技术和生态倒退很好,将来前景宽广,作为该技术的实践者,本文指标是通过答复7个外围问题介绍eBPF技术自身,为大家解开eBPF的面纱。
image.png

作者 | 炎寻
起源 | 阿里开发者公众号

过来一年,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成为可能。

image.png

安全控制
eBPF能够看到所有零碎调用,所有网络数据包和socket网络操作,一体化联合过程上下文跟踪,网络操作级别过滤,零碎调用过滤,能够更好地提供安全控制。

性能监控
相比于传统的系统监控组件比方sar,只能提供动态的counters和gauges,eBPF反对可编程地动静收集和边缘计算聚合自定义的指标和事件,极大地晋升了性能监控的效率和设想空间。

eBPF为什么会呈现?
eBPF的呈现实质上是为了解决内核迭代速度慢和零碎需要疾速变动的矛盾,在eBPF畛域罕用的一个例子是eBPF绝对于Linux Kernel相似于Javascript绝对于HTML,突出的是可编程性。一般来说可编程性的反对通常会带来一些新的问题,比方内核模块其实也是为了解决这个问题,然而他没有提供很好的边界,导致内核模块会影响内核自身的稳定性,在不同的内核版本须要做适配等。eBPF采纳以下策略,使得其成为一种平安高效地内核可编程技术:

平安
eBPF 程序必须被验证器校验通过后能力执行,且不能蕴含无奈达到的指令;eBPF 程序不能随便调用内核函数,只能调用在 API 中定义的辅助函数;eBPF 程序栈空间最多只有 512 字节,想要更大的存储,就必须要借助映射存储。

高效
借助即时编译器(JIT),且因为 eBPF 指令仍然运行在内核中,无需向用户态复制数据,大大提高了事件处理的效率。

规范
通过BPF Helpers,BTF,PERF MAP提供规范的接口和数据模型供开发者应用。

功能强大
eBPF 不仅扩大了寄存器的数量,引入了全新的 BPF 映射存储,还在 4.x 内核中将本来繁多的数据包过滤事件逐渐扩大到了内核态函数、用户态函数、跟踪点、性能事件(perf_events)以及安全控制等畛域。

eBPF怎么用?
image.png

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 available
eBPF program_type kprobe is available
eBPF program_type sched_cls is available
eBPF program_type sched_act is available
eBPF program_type tracepoint is available
eBPF program_type xdp is available
eBPF program_type perf_event is available
eBPF program_type cgroup_skb is available
eBPF program_type cgroup_sock is available
eBPF program_type lwt_in is available
eBPF program_type lwt_out is available
eBPF program_type lwt_xmit is available
eBPF program_type sock_ops is available
eBPF program_type sk_skb is available
eBPF program_type cgroup_device is available
eBPF program_type sk_msg is available
eBPF program_type raw_tracepoint is available
eBPF program_type cgroup_sock_addr is available
eBPF program_type lwt_seg6local is available
eBPF program_type lirc_mode2 is NOT available
eBPF program_type sk_reuseport is available
eBPF program_type flow_dissector is available
eBPF program_type cgroup_sysctl is available
eBPF program_type raw_tracepoint_writable is available
eBPF program_type cgroup_sockopt is available
eBPF program_type tracing is available
eBPF program_type struct_ops is available
eBPF program_type ext is available
eBPF program_type lsm is available
具体可参考https://elixir.bootlin.com/li...

次要是分为3大应用场景:

跟踪
tracepoint, kprobe, perf_event等,次要用于从零碎中提取跟踪信息,进而为监控、排错、性能优化等提供数据撑持。

网络
xdp, sock_ops, cgroup_sock_addr , sk_msg等,次要用于对网络数据包进行过滤和解决,进而实现网络的观测、过滤、流量管制以及性能优化等各种丰盛的性能,这里能够丢包,重定向。

image.png

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获取
sudo bpftrace -lv tracepoint:syscalls:sys_enter_openat
具体如何应用以上信息,请参考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

查问uprobe

bpftrace -l 'uprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:*'

查问USDT

bpftrace -l 'usdt:/usr/lib/x86_64-linux-gnu/libc.so.6:*'
uprobe 是基于文件的。当文件中的某个函数被跟踪时,除非对过程 PID 进行了过滤,默认所有应用到这个文件的过程都会被插桩。

下面说的是动态编译语言,他和内核的跟踪相似,应用程序的符号信息能够寄存在 ELF 二进制文件中,也能够以独自文件的模式,放到调试文件中;而内核的符号信息除了能够寄存到内核二进制文件中之外,还会以 /proc/kallsyms 和 /sys/kernel/debug 等模式裸露到用户空间。

对于非动态编译语言来说,次要是两种:

1、解释型语言

应用相似编译型语言应用程序的跟踪点查询方法,查问它们在解释器层面的 uprobe 和 USDT 跟踪点,如何将解释器层面的行为和利用行为关联须要相干语言的专家来剖析。

2、即时编译型语言

这类语言的利用源代码会先编译为字节码,再由即时编译器(JIT)编译为机器码执行,还会有大量的优化,跟踪难度很大,同解释型编程语言相似,uprobe 和 USDT 跟踪只能用在即时编译器上,从即时编译器的跟踪点参数外面获取最终应用程序的函数信息。找出即时编译器的跟踪点同利用程序运行之间的关系须要相干语言的专家来剖析。

能够参考BCC的应用程序跟踪,用户过程的跟踪,实质上是通过断点去执行 uprobe 处理程序。尽管内核社区曾经对 BPF 做了很多的性能调优,跟踪用户态函数(特地是锁争用、内存调配之类的高频函数)还是有可能带来很大的性能开销。因而,咱们在应用 uprobe 时,应该尽量避免跟踪高频函数。

具体如何应用以上信息,请参考:https://github.com/iovisor/bc...

关联问题与插桩点
一个现实的状态是所有问题都分明该当察看那些插桩点,然而这个要求技术人员对端到端的软件栈细节都理解非常透彻,一个更加正当的办法是二八法令,将软件栈数据流的最外围的80%脉络抓住,保障呈现问题肯定会在这个脉络被发现即可。此时再应用内核栈和用户栈来查看具体的调用栈即可发现外围问题,比如说发现了网络在丢包,然而不晓得为什么丢,此时咱们晓得网络丢包肯定会调用kfree_skb内核函数,那么咱们能够通过:

sudo bpftrace -e 'kprobe:kfree_skb /comm=="<your comm>"/ {printf("kstack: %s\n", kstack);}'
发现该函数的调用栈:

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
那么就能够回溯下面的函数,看看他们具体是哪一行在什么条件下调用的,就可能定位到问题。这个办法不仅能够定位问题,也能够用于加深对内核调用的了解,比方:

bpftrace -e 'tracepoint:net:* { printf("%s(%d): %s %s\n", comm, pid, probe, kstack()); }'
能够查看所有网络相干的跟踪点及其调用栈。

eBPF的实现原理是什么?

image.png

5个模块
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不反对,则无奈批改。

bpftool feature probe

通过以上命令能够看到不同类型的eBPF程序能够运行哪些BPF Helpers。

5、BPF Map & context

用于提供大块的存储,这些存储可被用户空间程序用来进行拜访,进而管制 eBPF 程序的运行状态。

bpftool feature probe | grep map_type
通过以上命令能够看到零碎反对哪些类型的map。

3个动作
先说下重要的零碎调用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与内核状态进行交互。

eBPF的倒退现状?
内核反对
倡议>=4.14

生态
eBPF的生态自下而上的状况如下:

1、基础设施

反对eBPF根底能力的倒退。

Linux Kernal
LLVM
2、开发工具集

次要是用于加载,编译,调试eBPF程序,不同语言有不同的开发工具集:

Go

https://github.com/cilium/ebpf
https://github.com/aquasecuri...
C/C++

https://github.com/libbpf/libbpf
3、eBPF利用

bcc(https://github.com/iovisor/bcc)
提供一套开发工具和脚本。

bpftrace(https://github.com/iovisor/bp...)
基于bcc,提供一个脚本语言。

cilium(https://github.com/cilium/cilium)
网络优化和平安

Falco(https://github.com/falcosecur...)
网络安全

Katran(https://github.com/facebookin...)
高性能4层负载平衡

Hubble(https://github.com/cilium/hubble)
可观测

Kindling(https://github.com/CloudDecte...)
可观测

Pixie(https://github.com/pixie-io/p...)
可观测

kubectl trace(https://github.com/iovisor/ku...)
调度bpftrace脚本

L3AF(https://github.com/l3af-proje...)
分布式环境下启动和治理eBPF程序的平台

ply(https://github.com/iovisor/ply)
动静linux trace

Tracee(https://github.com/aquasecuri...)
Linux运行时平安监测

4、跟踪生态的网站

https://ebpf.io/projects
https://github.com/zoidbergwi...
写在最初
用好eBPF的前提是对软件栈的了解
通过下面的介绍,置信大家对eBPF曾经有了足够的了解,eBPF提供的只是一个框架和机制,外围还是须要用eBPF的人对软件栈的了解,找到适合的插桩点,可能和利用问题进行关联。

eBPF的杀手锏是全笼罩,无侵入,可编程
1、全笼罩

内核,应用程序插桩点全笼罩。

2、无侵入

不须要批改任何被hook的代码。

3、可编程

动静下发eBPF程序,边缘动静执行指令,动静聚合剖析。

团队信息

阿里云可观测团队,笼罩前端监控、利用监控、容器监控、Prometheus、链路追踪、智能告警、运维可视化等多个技术畛域及产品,积淀阿里云可观测在不同行业、不同技术场景的可观测解决方案与最佳实际。

阿里云Kubernetes监控是一套基于eBPF技术,针对Kubernetes集群开发的一站式无侵入式可观测性产品,基于Kubernetes集群下的指标、利用链路、日志和事件,旨在为IT开发运维人员提供整体的可观测性计划。

介绍:https://help.aliyun.com/docum...

接入:https://help.aliyun.com/docum...

云原生环境下基于 Serverless 架构的翻新利用

云原生环境下基于 Serverless 架构的翻新利用

点击这里,查看详情。

原文链接:http://click.aliyun.com/m/100...

本文为阿里云原创内容,未经容许不得转载。