您的浏览器禁用了JavaScript(一种计算机语言,用以实现您与网页的交互),请解除该禁用,或者联系我们。[龙蜥社区&浪潮信息]:2023年eBPF技术实践白皮书 - 发现报告
当前位置:首页/行业研究/报告详情/

2023年eBPF技术实践白皮书

AI智能总结
查看更多
2023年eBPF技术实践白皮书

编写说明 编写单位:浪潮电子信息产业股份有限公司、阿里云计算有限公司参编组织:龙蜥社区 编写组成员:苏志远、吴栋、方浩、王传国、梁媛、黄吉旺、毛文安、程书意、甄鹏、李宏伟、彭彬彬 前言 eBPF的诞生是BPF技术的一个转折点,使得BPF不再仅限于网络栈,而是成为内核的一个顶级子系统。在内核发展的同时,eBPF繁荣的生态也进一步促进了eBPF技术的蓬勃发展。随着内核的复杂性不断增加,eBPF以安全、稳定、零侵入、方便的内核可编程性等特点成为实现内核定制与功能多样性的最佳选择,为内核提供了应对未来挑战的基础。 本白皮书重点介绍eBPF技术的概念、技术实践以及发展趋势。本书首先梳理了eBPF的架构和重要技术原理,然后分析了eBPF在多种典型应用场景的使用方案,并进一步对eBPF技术的发展趋势做了探讨。 目录 eBPF简介6 eBPF技术介绍7 2.1eBPF架构7 2.1.1eBPF加载过程8 2.1.2JIT编译9 2.1.3挂载与执行10 2.2eBPF常见的开发框架17 2.2.1BCC17 2.2.2bpfTrace18 2.2.3libbpf18 2.2.4libbpf-bootstrap19 2.2.5cilium-ebpf19 2.2.6Coolbpf19 eBPF的应用场景与实践23 3.1基于eBPF的系统诊断23 3.1.1系统诊断面临挑战23 3.1.2基于eBPF的系统诊断方案28 3.2基于eBPF的虚拟化IO全链路时延监测34 3.2.1虚拟化IO全路径分析主要面临的挑战34 3.2.2基于bpftrace虚拟化IO路径追踪解决方案36 3.3基于eBPF的网络性能优化41 3.3.1Linux网络性能优化面临的技术挑战41 3.3.2基于eBPF的Linux内核网络性能优化解决方案44 3.4基于eBPF的主机安全53 3.4.1传统解决方案面临挑战53 3.4.2基于eBPF的新一代主机安全解决方案54 挑战与展望63 eBPF简介 eBPF是一项起源于Linux内核的革命性技术,可以在特权上下文中(如操作系统内核)运行沙盒程序。它可以安全有效地扩展内核的功能,并且不需要更改内核源代码或加载内核模块。 内核因具有监督和控制整个系统的特权,一直是实现可观察性、安全性和网络功能的理想场所。同时,由于对稳定性和安全性的高要求,内核发展相对缓慢,与内核之外实现的功能相比,创新速度较慢。 eBPF从根本上改变了上述情况,它允许在内核中运行沙箱程序,即通过运行eBPF程序向正在运行中的操作系统添加额外的功能,并通过验证引擎和即时编译器保证安全性和执行效率,由此衍生出了一系列的产品和项目,涉及下一代网络、可观察性和安全技术。 如今,eBPF在多种应用场景中起到重要作用:在现代数据中心和云原生环境中提供高性能网络和负载均衡,以低开销提取细粒度的安全可观察性数据,帮助应用程序开发人员跟踪应用程序的运行状态,高效进行性能故障定位,保证应用程序和容器运行时安全等。 eBPF引领的创新才刚刚开始,一切皆有可能。 eBPF技术介绍 2.1eBPF架构 eBPF包括用户空间程序和内核程序两部分,用户空间程序负责加载BPF字节码至内核,内核中的BPF程序负责在内核中执行特定事件,用户空间程序与内核BPF程序可以使用map结构实现双向通信,这为内核中运行的BPF程序提供了更加灵活的控制。eBPF的工作逻辑如下: 1、eBPFProgram通过LLVM/Clang编译成eBPF定义的字节码;2、通过系统调用bpf()将字节码指令传入内核中; 3、由Verifier检验字节码的安全性、合规性; 4、在确认字节码程序的安全性后,JITCompiler会将其转换成可以在当前系统运行的机器码; 5、根据程序类型的不同,把可运行机器码挂载到内核不同的位置/HOOK点; 6、等待触发执行,其中不同的程序类型、不同的HOOK点的触发时机是不相同的;并且在eBPF程序运行过程中,用户空间可通过eBPFmap与内核进行双向通信; 图2-1-1eBPF基本架构与使用示意图 2.1.1eBPF加载过程 编译得到的BPF字节码文件,经过字节码读取、重定位和Verifier等过程,加载到内核中。 1、字节码读取 通常eBPF字节码文件都是ELF格式,各section中保存着字节码所有的信息,包括字节码指令、map定义、BTF信息,以及需要重定位的变量和函数等;eBPF加载时,依照ELF格式读取字节码文件,把各种信息按照一定的格式保存起来。 2、重定位 重定位是指在编译、加载的过程中把字节码中一些临时数据以更准确的信息进行替换,比如用map句柄替换map索引,用map地址替换map句柄。经过多轮重定位,可以确保eBPF程序在内核中运行所需数据的正确性和完整性。需要重定位的对象有:map、函数调用、Helper函数调用、字段、Extern内核符号和kconfig。 重定位操作主要分为2类: 1)BPF基础设施提供了一组有限的“稳定接口”,通过convert_ctx_access对CTX进行转换,实现内核版本升级时的稳定性和兼容性。 2)CO-RE采用(BTF)非硬编码的形式对成员在结构中的偏移位置进行描述,解决不同版本之间的差异性问题。 3、Verifier Verifier是一个静态代码安全检查器,用于检查eBPF程序的安全性,保证eBPF程序不会破坏内核,影响内核的稳定性。 安全检查主要分成两个阶段。 第一个阶段检查函数的深度和指令数,通过使用深度优先遍历,来检查是否为有向无环图(DAG)。 第二个阶段检查每条bytecode指令,根据指令的分类(class),检查其操作寄存器的读写属性、内存访问是否越界、BPF_CALL是否符合接口协议等。 指针的安全性检查在第二个阶段实现,每次把指针加载到寄存器时都会进行指针类型的判定,根据指针类型定义确定读写属性和大小,实现针对指针操作的安全性检查;未识别的指针类型不允许解引用。 2.1.2JIT编译 执行字节码是一个模拟CPU执行机器码的过程,在运行时需要先把指令依次翻译成机器码之后才能运行,所以比机器码的执行效率低很多。JIT(JustInTime)的 中文意思是即时编译,主要为了解决虚拟机运行中间码时效率不高的问题。 JIT编译在执行前先把中间码编译成对应的机器码并缓存起来,从而运行时能够直接执行机器码,这样就解决了每次执行都需要进行中间码解析的问题,如下图所示: 图2-1-2JIT的作用示意图 2.1.3挂载与执行 eBPF在内核提供了大量的挂载点,算上kprobe挂载点,几乎可以在内核代码的任意函数挂载一段eBPF程序。不同eBPF程序类型的挂载点各不相同,挂载点是根据设计与需求提前在内核指定位置通过提前嵌入代码实现的,初始时函数指针是空,挂载eBPF程序后,对指针赋值,指向eBPF内核程序。 为了安全性,挂载eBPF程序需要root权限或CAP_BPFcapability,不过目前也有设计允许非root权限帐号载入eBPF程序,比如将kernel.unprivileged_bpf_disabledsysctl设置为false的情况下,非root帐号能够使用bpf()系统调用。 userid:532115,docid:145752,date:2023-11-14,sgpjbg.com eBPF内核程序基于事件触发,当内核执行到对应的挂载点时就会执行挂载在此处的eBPF程序。eBPF执行过程中会用到一个很重要的数据结构map,map的主要功能是用来实现BPF程序执行过程中用户空间程序与内核BPF程序之间的双向通信,这也为内核中运行的BPF程序提供了更加灵活的控制。 1、map的实现 map的定义信息在编译后会保存到字节码文件中名为maps的section中,bpf加载器读取到map信息后调用系统调用创建eBPFmap,系统调用返回由anon_inodefs文件系统生成的fd。 加载器将内核返回的map的fd,替换到使用map的eBPF指令的常量字段中,相当于直接修改编译后的BPF指令。 加载器在指令替换mapfd后,才会调用cmd为BPF_PROG_LOAD的bpf系统调用,将程序加载到内核。 内核在加载eBPF程序系统调用过程中,会根据eBPF指令中常量字段存储的mapfd,找到内核态中的map,然后将map内存地址替换到对应的BPF指令。 最终,BPF程序在内核执行阶段能根据指令存储的内存地址访问到map。 在eBPF数据面中,使用eBPFmap只需要按照规范定义map的结构,然后使用bpf_map_lookup_elem、bpf_map_update_elem、bpf_map_delete_elem等helperfunction就可以对map进行查询、更新、删除等操作。 2、map的性能 eBPFmap有多种不同类型,支持不同的数据结构,最常见的例如Array、PercpuArray、Hash、PercpuHash、LruHash、PercpulruHash、Lpm等等。 常用map的查询性能比较如下: Array>PercpuArray>Hash>PercpuHash>LruHash>Lpm 需要着重说明的是,Array的查询性能比PercpuArray更好,Hash的查询性能也比PercpuHash更好,这是由于Array和Hash的lookuphelper层面在内核有更多的优化。对于读多写少的情况下,使用Array比PercpuArray更优(Hash、PercpuHash同理),而对于读少写多的情况(比如统计计数)使用Percpu更优。 2.1.3.1eBPF常见程序类型 eBPF作为一个通用执行引擎,可用于性能分析工具、软件定义网络等诸多场景的开发工作。根据应用场景的不同,eBPF程序类型大致可以分为三类: 表2-1-1eBPF程序类型分类 分类 用途 包含的程序类型 跟踪 主要用于从系统中提取跟踪信息,进而为监控、排错、性能优化等提供数据支撑 tracepoint,kprobe,perf_event等 网络 主要用于对网络数据包进行过滤和处理,进而实现网络的观测、过滤、流量 xdp,sock_ops,sk_msg,sk_skb,sk_reuseport,socket_filter, 控制以及性能优化等各种丰富的功能 cgroup_sock_addr等 其他 主要用于安全和其他功能 LSM,flow_dissector,lwt_in,lwt_out,lwt_xmit等 1、kprobe kprobe是linux系统的一个动态调试机制,使用它可以向内核添加探针(Probe),在代码执行前或执行后触发一个回调函数。这个机制通常用于调试内核代码,跟踪应用程序执行或收集性能统计信息。通过使用kprobe,开发人员可以在不影响系统运行逻辑的情况下,对操作系统进行深入的分析和调试。 kprobe机制提供了两种形式的探测点, 一种最基本的kprobe:用于在指定代码执行前、执行后进行探测,挂载点可以是内核代码的任何指令处; 一种是kretprobe:用于完成指定函数返回值的探测功能,挂载点是内核函数的返回位置; 当内核执行到指定的探测函数时,会调用回调函数,用户便可收集所需的信息,在完成回调函数后,内核会继续回到原来的位置正常执行。如果用户已经收集足够的信息,不再需要继续探测,则同样可以动态的移除探测点。 eBPF可以在内核态高效的分析kprobe采集到的数据,仅把结果反馈到用户空间,而不需要像传统系统一样必须将大量的采样数据全部传输到用户空间再进行分析,极大地减轻了内核态与用户态的通信压力,使得持续地实时分析成为可能。 2、XDP XDP全称eXpressDataPath,即快速数据路径,XDP是Linux网络处理流程中的一个eBPF钩子,能够挂载eB