《Linux超能力BPF技术介绍及学习资料.docx》由会员分享,可在线阅读,更多相关《Linux超能力BPF技术介绍及学习资料.docx(11页珍藏版)》请在课桌文档上搜索。
1、BPF是什么?需要回答BPF是什么?就得先回答为什么需要BPF?多年前很多程序,例如网络监控器,都是作为用户级进程运行的。为了分析只在内核空间运行的数据,它们必须将这些数据从内核空间复制到用户空间的内存中去,并进行上下文切换。这与直接在内核空间分析这些数据相比,导致了巨大的性能开销。而随着近年来网络速度和流量井喷式增长,一些应用程序必须处理大量的数据(如音频、视频流媒体数据)。要在用户空间监控分析那么多的流量数据已经不可行了,因而BPF应运而生种在内核空间执行高效安全的程序的机制。BPF全称是BerkeleyPacketFilteid,翻译过来是伯克利包过滤器,顾名思义,它是在伯克利大学诞生的
2、,1992年StevenMcCanne和VanJacobson写了一篇论文:TheBSDPacketFilter:ANeWArehiteCtUrefOrUSer-IeVeIPaCketCaPture,第一次提出了BPF技术,在文中,作者描述了他们如何在UniX内核实现网络数据包过滤,这种新的技术比当时最先进的数据包过滤技术快20倍。下图为BPF概览,来自上面的论文:一个新的虚拟机(VM)设计,可以有效地工作在基于寄存器结构的CPU之上;应用程序使用缓存只复制与过滤数据包相关的数据,不会复制数据包的所有信息,最大程度地减少BPF处理的数据,提高处理效率。我们熟悉的Mpdump就是基于BPF技术,
3、成为了站在神器肩膀上的神器。发展到现在名称升级为eBPF:extendedBerkeleyPacketFiltero演进成一套通用执行引擎,提供了可基于系统或程序事件高效安全执行特定代码的通用能力,通用能力的使用者不再局限于内核开发者。其使用场景不再仅仅是网络分析,可以基于eBPF开发性能分析、系统追踪、网络优化等多种类型的工具和平台。eBPF由执行字节码指令、存储对象和帮助函数组成,字节码指令在内核执行前必须通过BPF验证器的验证,同时在启用BPFJlT模式的内核中,会直接将字节码指令转成内核可执行的本地指令运行,具有很高的执行效率。下图是eBPF工作原理演示:Controller向54TC
4、omPiIerEIXnUsendmsg()recvmsg()SocketsTCP/IPNetworkDeviceProcess原来的BPF就被称为CBPF(ClaSSiCBPF),目前已基本废弃。当前1.inUX内核只运行eBPF,内核会将CBPF透明转换成eBPF再执行。下文提到的BPF字样没有特别说明的话,是泛指CBPF和eBPF。BPF技术发展史从1992年发布以来,BPF技术快速发展,除了技术本身得到了升级,基于它的工具和平台也如雨后春笋一般层出不穷,下面是我整理的BPF技术发展史,里面罗列几个重要的eBPF发展里程碑和基于eBPF技术的工具和平台的诞生事件,其中包括BCC、Ciliu
5、mFalco、bpftracekubecti-trace,还有最近非常热门的腾讯云独创的IPVS-BPF模式。强安全,即不能允许不可信的代码运行在内核中,这是头等重要的事情高性能,作为承载千百万服务的操作系统内核,如果没有高性能的保障,互联网蓬勃发展将收到严重影响持续交付,在越来越多应用进入到云原生时代的今天,持续交付这个命题,一点都不陌生,而在内核开发领域,这点也至关重要,每次功能的升级,都需要你重新安装新的系统,大多数人都不会买账。我们希望做到跟ChrOme浏览器升级一样,用户都不会注意到升级完成了(除非有一些视觉上的变化),实现真正的无缝升级。想要实现上面的目标,没有想象中那么简单。我们
6、来看进行1.inux内核开发的一般解决方案以及它们的缺陷。直接修改内核代码进行开发,通过APl暴露能力,可能要等上n年用户才能更新到这个版本来使用,而且每次的功能更新都可能需要重新编译打包内核代码。开发新的可即时加载的内核模块,用户可以在运行时加载到1.inUX内核中,从而实现扩展内核功能的目的。然而每次内核版本的官方更新,可能会引起内核API的变化,因此你编写的内核模块可能会随着每一个内核版本的发布而不可用,这样就必须得为每次的内核版本更新调整你的模块代码,你得非常小心,不然就会让内核直接崩溃。来看BPF带来的解决方案,它是如何实现上面3个目标的:强安全:BPF验证器(verifier)会保
7、证每个程序能够安全运行,它会去检查将要运行到内核空间的程序的每一行是否安全可靠,如果检查不通过,它将拒绝这个程序被加载到内核中去,从而保证内核本身不会崩溃,这是不同于开发内核模块的。比如以下几种情况是无法通过的BPF验证器的:BPF验证机制很像Chrome浏览器对于JaVaSCriPt脚本的沙盒机制。没有实际加载BPF程序所需的权限访问任意内核空间的内存数据将任意内核空间的内存数据暴露给用户空间高性能:一旦通过了BPF验证器,那么它就会进入Jrr编译阶段,利用Just-In-Time编译器,编译生成的是通用的字节码,它是完全可移植的,可以在x86和ARM等任意球CPU架构上加载这个字节码,这样
8、我们能获得本地编译后的程序运行速度,而且是安全可靠的。持续交付:通过JlT编译后,就会把编译后的程序附加到内核中各种系统调用的钩子(hook)上,而且可以在不影响系统运行的情况下,实时在线地替换这些运行在1.inux内核中的BPF程序。举个例子,拿一个处理网络数据包的应用程序来说,在每秒都要处理几十万个数据包的情况下,在一个数据包和下一个数据包之间,加载到网络系统调用hook上的BPF程序是可以自动替换的,可以预见到的结果是,上一个数据包是旧版本的程序在处理,而下一个数据包就会看到新版本的程序了,没有任何的中断。这就是无缝升级,从而实现持续交付的能力。因此,大名鼎鼎的系统性能优化专家Brend
9、anGregg对于BPF的到来,就给出了以下的名言:uSuperpowershavefinallycometo1.inux,一BrendanGreggBPF的超能力第一个,BPFHooks,即BPF钩子,也就是在内核中,哪些地方可以加载BPF程序,在目前的1.inUX内核中已经有了近10种的钩子,如下所示:kernelfunctions(kprobes)userspacefunctions(uprobes)systemcallsfentry/fexitTracepointsnetworkdevices(tc/xdp)networkroutesTCPcongestionalgorithmssoc
10、kets(datalevel)从文件打开、创建TCP链接、SOCket链接到发送系统消息等几乎所有的系统调用,加上用户空间的各种动态信息,都能加载BPF程序,可以说是无所不能。它们在内核中形成不同的BPF程序类型,在加载时会有类型判断。下图的内核代码片段是用来判断BPF程序类型:staticintload_and_attach(constcharevent,structbpfvinsnprog,intsize)77(boolis_socketstrnc(eventfsocket,r6)00;boolI1.kProbe-strnc(event,kprobc1,7)-O;boolis.krtpro
11、bstrncsp(event,tprobe/,r10)三0;boolie_trcpoint,Strncnpteventrtrcepoin*.11)三t=0;boolis.raw_tracepointstrncap(eventzrawtracepoint-f15)=0;boolis_xdpstrncap(event,xdp,3)三0;boolis_perf_event-Strncnpievent,perfevent,10)0;booli-cgroup-skb-Strncnpieventr,10)-0;boolis_cgroup_,kstrncnp(event,roupsock*,11)三0;bo
12、olis_sockops-strncsp(event,ockops*r7)三三0;boolis_sk_skb=Strncap(event,k-skbr6)三三0;boolis.skmsg-Strncap(event,skjnsg,r6)三-0;sixe_tinsns_cnt-size/sizeof(structbpf_insn;enumbpf_prog_typeprog-type;charbuf(256)7intfdrfdrerr,id;structperf_vent_attrattr三(;95一一attr,typePERjunTPEJRACEPOlBrT;attr.aple-typeePER
13、F_6AMP1.E_RAW;attr.sA*ple_period-1;attr.wakupvnts1;100一if(iseocket)prog_typeBPFePROGeTTPBeSOCKETeFI1.TBR;elseif(iskprobeiskretprobe)prog-typeBPP_PROG_TYPE_KPROBEelseif(s_tracpoint(pro9-typeBPF_PROG_TTPE_TRACEPOXNT; elseif(is_raw_tracepoint)proqtypeBPFPRoGTYPERAWTRACEPOINT; elseif(is_xdp)(pro9_typeBP
14、F_PROG_TTPE_XDP; elsif(is-prf-vnt)pr9-typBPF_PROG_TXPE_PERF_BVBHT;elseif(is_cgroup_skb)progtypeBPF_PROG_TTPB_CGROUP_SKB;elseif(iscgroupsk)UnuxvS.8.7第二个核心技能点BPFMapo一个程序通常复杂的逻辑都有一个必不可少的部分,那就是记录数据的状态。对于BPF程序来说,可以在哪里存储数据状态、统计信息和指标信息呢?这就是BPFM叩的作用,BPF程序本身只有指令,不会包含实际数据及其状态。我们可以在BPF程序创建BPFMap,这个M叩像其他编程语言具有的
15、Map数据结构类似,也有很多类型,常用的就是HaSh和AiTay类型,如下所示:Hashtables,Arrays1.RU(1.eastRecentlyUsed)RingBufferStackTrace1.PM(1.ongestPrefixmatch)下图所示是一个典型的BPFMap创建代码:structbpf_map_defSEC(maps)my_bpf_map=.type=BPF_MAP_TYPE_HASHr.key_size-sizeof(int),.value_size=sizeof(int),.max_entries=100,6. .map-flags-BPF_F_NO_PREA1.
16、1.OC,;值得一提的是:BPFM叩是可以被用户空间访问并操作的BPFMap是可以与BPF程序分离的,即当创建一个BPFMap的BPF程序运行结束后,该BPFM叩还能存在,而不是随着程序一起消亡基于上面两个特点,意味着我们可以利用BPFMap持久化数据,在不丢失重要数据的同时,更新BPF程序逻辑,实现在不同程序之间共享信息,在收集统计信息或指标等场景下,尤其有用。第三个核心技能点BPFHelperFunction,即BPF辅助函数。BPF超能力技能点BPFHE1.PERS辅助函数清单,flandomnumbers,Getcurrenttime,Mapccss,GetPrOCeSVCfroUPc
17、ontext,Manubtcnetworkp*dctsandIOrW,Acctss)ctadiu,Pedonnailcal,Acctnprocessttidc,AcctsssysoNarmem它们是面向开发者的,提供操作BPF程序和BPFMaP的工具类函数。由于内核本身会有不定期更新迭代,如果直接调用内核模块,那天可能就不能用了,因此通过定义和维护BPF辅助函数,由BPF辅助函数来去面对后端的内核函数的变化,对开发者透明,形成稳定API接口。例如,BPF程序不知道如何生成一个随机数,有一个BPF辅助函数会可以帮你检索并询问内核,完成“给我一个随机数”的任务,或者“从BPFM叩中读取某个值”等等
18、。任何一种与操作系统内核的交互都是通过BPF辅助函数来完成的,由于这些都是稳定的API,所以BPF程序可以跨内核版本进行移植。下图是部分BPF辅助函数的列表:17 *helperfunctionscalledfromeBPFprogramswritteninC*/18 staticvoid*(*bpf_map_I.ookup_elem)(void*maptvoid*key)=(void*)BPF_FUNC_map_lookup_elem;20 staticint(*bpf_map_update_el.em)(void*map,void*keytvoid*valuetunsignedlonglo
19、ngflags)三(void*)BPF_FUNC_map_update_elem;21 3staticint(*bpf_map_delete_elem)(void*mapfvoid*key)三(void)BPF_FUNC_map_delete_elem;25 staticint(*bpf_probe-read)(void*dstfintsize,void*unsafe_ptr)三(void*)BPF_FUNC_probe_read;staticunsignedlonglong(*bpf_ktime_get_ns)(void)=(void)BPF_FUNC_ktime_get_ns;29 sta
20、ticint(*bpf_trace_printk)(constchar*fmt,intfmt_size,)=(void)BPF_FUNC_trace_printk;staticvoid(*bpf_tail_call)(void*ctx,void*map,intindex)=(void*)BPF_FUNC_tail_call;staticunsignedlonglong(*bpf_get_smp_processor_id)(void)=(void*)BPF_FUNC_get_smp_processor_id;staticunsignedlonglong(*bpf_get_current_pid_
21、tgid)(void)三(void)BPF_FUNC_get_current_pid_tgid;staticunsignedlonglong(*bpf_get_current_uid_gid)(void)三(void*)BPF_FUNC_get_current_uid_gid;staticint(*bpf_get_current_comm)(void*buf,intbuf_size)=(void*)BPF_FUNC_get_current_comm;41 staticunsignedlonglong(*bpf_perf_event_read)(void*map,unsignedlonglong
22、flags)=(void*)BPF_FUNC_perf_event_read;staticint(*bpf_clone_redirect)(void*ctxfintifindefintflags)=(void)BPF_FUNC_cIonjredirect;介绍完这些BPF超能力的技能点,接下来要讲讲超能力也有限制的地方。BPF技术虽然强大,但是为了保证内核的处理安全和及时响应,内核对于BPF技术也给予了诸多限制,如下是几个重点限制:eBPF程序不能调用任意的内核参数,只限于内核模块中列出的BPFHelper函数,函数支持列表也随着内核的演进在不断增加eBPF程序不允许包含无法到达的指令,防止加
23、载无效代码,延迟程序的终止eBPF程序中循环次数限制且必须在有限时间内结束eBPF堆栈大小被限制在MAX_BPF_STACK,截止到内核1.inux5.8版本,被设置为512。目前没有计划增加这个庶加,解决方法是改用BPFM叩,它的大小是无限的。eBPF字节码大小最初被限制为4096条指令,截止到内核1.inux5.8版本,当前已将放宽至100万指令(BPF_COMP1.EXITY_1.IMIT_INSNS),对于无权限的BPF程序,仍然保留4096条限J(BPF_MAXINSNS)更多相关信息可以查看这里的限制随着技术的发展和演进,限制也在逐步放宽或者提供了对应的解决方案。K8s已经成为一线
24、大厂分布式平台的标配技术。你是不是还在惆怅怎么掌握它?来这里,大型互联网公司一线工程师亲授,不来虚的,直接上手实战,3天时间带你搭建K8s平台,快速学会K8s,点击下方图片可了解培训详情。BPF应用场景接下来我们通过3个案例分析,来看下具有强大超能力的BPF在实际环境中的应用场景:1、Cilium,是首款完全基于eBPF程序实现了kube-proxy的所有功能的KubernetesCNI网络插件,无需依赖iptables和IPVSo我们知道kube-proxy基于iptables的服务负载均衡功能在大规模容器场景下具有严重的性能瓶颈,同时由于容器的创建和销毁非常频繁,基于IP做身份关联的故障排
25、除和安全审计等也很难实现。Cilium作为一款KubemetesCNl插件,从一开始就是为大规模和高度动态的容器环境而设计,并且带来了API级别感知的网络安全管理功能,通过使用基于1.inux内核特性的新技术BPF,提供了基于Service/Pod/Container作为标识,而非传统的IP地址,来定义和加强容器和POd之间网络层、应用层的安全策略。因此,CiliUm不仅将安全控制与寻址解耦来简化在高度动态环境中应用安全性策略,而且提供传统网络第3层、4层隔离功能,以及基于http层上隔离控制,来提供更强的安全性隔离。另外,由于BPF可以动态地插入控制1.inux系统的程序,实现了强大的安全可
26、视化功能,而且这些变化是不需要更新应用代码或重启应用服务本身就可以生效,因为BPF是运行在系统内核中的。以上这些特性,使Cilium能够在大规模容器环境中也具有高度可伸缩性、可视化以及安全性。如何通过eBPF程序实现请求转发的原理分析:CiIiUm通过将eBPF程序加载到内核的几个网络Hook上,包括TC,XDP,实现了原来KUbe-PrOXy请求转发的能力。在内核网络栈中,这两个网络hook都要比kube-proxy所依赖的iptables处于更早的网络前端,还没有生成完全的网络报文的上下文(包含更复杂的元数据结构),因此具有更高的网络数据处理效率,如下图所示:XDPBPFProgramTC
27、BPFProgramSocketBPFProgramNativeVlngress配合eBPFMap存储后端Pod地址和端口,实现高效查询和更新。CIusterIP(podtopod)inCilium更多Cilium部署使用内容可以看我的这篇博文:最CoolKubernetes网络方案Cilium入门教程2、Falco,来自老牌安全厂商SySdig开源的关注云原生运行时安全的项目。当前问题:目前云原生时代Kubernetes技术解决了基础架构平台DaylOperation问题,而Day2Operation包含了monitor,maintain,和troubleshoot等一系列运行时工作,其中云
28、原生安全问题已经引起越来越多的注意。Falco解决方案:Falco是第一个加入CNCF的关注云原生运行时安全的开源项目,目前是威胁KUberneteS平台监测引擎的事实标准,还可以监测意外的应用行为和运行时发出的威胁警告。如何使用eBPF程序实现实时应用监控:FaICO主要使用了rawracepoint的系统调用hook,检测应用进程的启动和退出,然后通过Perf类型的BPFM叩将检测到的数据发送回用户空间,实现监控的Pipelineo官方Falco已自带了很多默认监控规则,具体可以查看Falcopod中的/etc/falco/falcoUIeS.yaml文件。下面几个样例:1.如果使用kub
29、ectlexec进入一个Pod/container,就会触发下面的规则:-rule:Terviinalshellincontainerdesc:Ashellwasusedastheentrypointeecpointintoacontainerwithanattachedtrainal.condition:spawneda-proce88andcontainerandshell-procsandproc.tty!-Candcontainerentrypointoutput:AshellwasspamedInacontainerwithanAttachedteminal(useruser.nar
30、ccontainer.infoshellproc.namoparentproc.pna3ccdlinproccndlnctenlnalproc.ttycontainoridcontainr.idin9ec0ntainr.image.repository)priority:NOTICBtA9s:containrrshllvitrooxcutioni监控输出:NoticeAshllwsspamcdinacontainerwithanattachedterminal(usrrootk8s.nsdfaultk8s.odfAlco*rw8wgcontainrb915a43871(Mshllshprntr
31、uncc*dlinshterminalaKricontainrMidb91543710dinag)k8s.nsdfultk8s.podafalco-rw8wgcontinrab915a438710d1.如果在Pod/Container内运行wgetsth,就会触发:-rul:Nrltbelowrootdec:anattettowrittoanyfiledirectlybelow/or/rootcondition:ootawdirandvt.dirandopcjrtandnotfd.naoQin(knom_rtf1lIandnotfd.directoryin(knotm_root_dirctor
32、lt)andr*ocjumHng.doclcjvandnotgi19flt_writlng_gvestaqent_lo9andnotds-wrlti119vUndnott*p_wrItin9_statandnotalrflow_writlng_statandnotrpa_wrltin9_root_rpdbndnotMvn_wrItin9gr00vyandrtchfvwritin9MConfandnotkubectlwriting-tatandnotcast4ndr_wrltlng_st4tandnotgalleywrltlngatatAandnotcalicowrItingaStateandn
33、otranchr-wrlting-rootandnotruncurItlng-xc-f1f0andnotKnownJooJCotMHSonsandnotuer-knom-writ-root-conditionBndnotur-kf*own-writ-blow-root-activitloutput:Fileblow/or/rootopenedforwritingarentproc.pnaMfilefd.nanepr09ra*%pr0c.nasecont4lnr-ld%containr.idi*9cont4inr.iM9e.repository)priority:ERRORtags:filesy
34、steBfmitre_persistence.监控输出:ErrorFi1blow/or/rootopendforwriting(usrrootcocrmandwgethttp1 .如果在Pod/COntainer中执行如aptget等包安装行为,就会U发:Containerissupposedtobnnutbl.Pack9an9Mntshouldb(Soninbuildingthelnag.-rul:1.aunchPckagIUnagenentProcinContainerdc:P*ck9m*nagspAwned_proc88.ndcontalnrandusr.nanQ!apt-andPACX
35、aBjngntjrocsndnotpcUgQf9ntIjnS八。工-procandtotusjkno*nMPdgomngrn.8ntanrOUtfMltlIMnegenentprocesslaunchedincontaineruser8er.nfw*ccaAandproc.cBdllneont*inerMld%contAlnr.idcntalftrnnawcontAiftr.nfMina9c0ntalnr.i11qc.repos1tory:continr.imq.tag)priority:KRROtg:process,aXtrMPr,*tnc监控输出:ErrorPackagesunagnntp
36、rocesslaunchedincontainer(usrrootccMnSakadd-no-cachnysql-clientcontaineridcclcdca736ccontalnrsnanelnagsk89.ns*dfaultk8s.podt9tboxcontalnercclcdce473ck85.n9dcfaultk8sp3tetboxcontainercclcdcea736c最近两年KubeCon大会上都有Falco的精彩talk,都是来自Sysdig的大神,介绍了Falco的运作原理和使用场景,是了解掌握Falco非常好的材料,其中KriSNoVa是个非常COOI的技术女神,她的P
37、pT也非常有个性,是基于命令行的。3、Kubectl-trace,一款基于bpftrace的kubectl插件,帮助用户追踪排查Kubernetes应用的运行情况。首先介绍下bpftrace,它是一种高阶的描述系统追踪的语言,其优势在于可以通过一行脚本(One-1.iner)实现多种应用追踪能力,如下图所示:FilesopenedbyprocessbpftraceY,tracepoint:syscalls:sys.enter.opeprintf(sswrcfileame);9SyscaUcountbyprograbpftraceY,tracepoInt:rawsysealIs:sys-eter
38、0cocm三count();,,Readbytesbyprocess:bpftraceY,tracepoint:syscaUs:$y$_exit_reedargj-ret(com=sun(rgsret);,9Readsizedistributionbyprocess:bpftraceYtracepoint:syscalls:sys.exitread9(cret);,9Shower-secondsyscallrates:bpftraceY,tracepoint:raw_syscaUs:sys_enter=count();interval:s:lprint(0);cIear(Q);)#Traced
39、isksizebyprocessbpftraceYtracepoint:block:block.rq_i$ueprintf(,Msd11,rpidcom,argsbytes);通过和BCC集成,能实现更强追踪能力。如果你想要的脚本比较复杂,可以放在一个扩展名为bt的文件里,如下图所示的bashreadline.bt:18 BEGIN19 printf(Tracingbashcommands.HitCtrl-Ctoend.nu);printf(%-9s%-6s%sn,fTIME,PIDm,COMMAND);24uretprobe:binbash:readline25printf(,-6dsn,p
40、id,str(retval);28回到我们的kubectl-trace,它的能力就是可以把定义好的bpftrace脚本attach到指定的节点、Pod,来追踪脚本里定义的目标。具体来说,kubectl-trace插件在Kubemetes集群内是通过名为trace-runner的job形式指定执行一个bpftrace程序,如下图所示:CreateConfigMapCreateJobGetlogskubectl-trace$kubectltraceexecclientpluginInstallBPFprogramkernelworkernodeMylaptopKubernetescluster而在这个trace-runnerjob的Pod中,它的工作原理就是一个原生BPF程序原生的工作流程了,通过加载BPF程序(即bpftrace生成的)到kprobes,uprobes,tracepoints等系统调用hook上,并把程序的输出重定向到磁盘上,以便进行事后查询,整体工作流程如下图所示:ConfigMapvolume/programsHostvolumeKernelheaderslibmodles,bpftraceprocessinthe,tracerunnerpodIibbcc(BPFCompilerCollection)