关于后端:一文详解用eBPF观测HTTP

2次阅读

共计 8813 个字符,预计需要花费 23 分钟才能阅读完成。

简介:随着 eBPF 推出,因为具备高性能、高扩大、安全性等劣势,目前曾经在网络、平安、可察看等畛域广泛应用,同时也诞生了许多优良的开源我的项目,如 Cilium、Pixie 等,而 iLogtail 作为阿里内外千万实例可观测数据的采集器,eBPF 网络可观测个性也预计会在将来 8 月公布。下文次要基于 eBPF 观测 HTTP 1、HTTP 1.1 以及 HTTP2 的角度介绍 eBPF 的针对可观测场景的利用,同时回顾 HTTP 协定本身的倒退。

作者 | 少旋起源 | 阿里开发者公众号前言随着 eBPF 推出,因为具备高性能、高扩大、安全性等劣势,目前曾经在网络、平安、可察看等畛域广泛应用,同时也诞生了许多优良的开源我的项目,如 Cilium、Pixie 等,而 iLogtail 作为阿里内外千万实例可观测数据的采集器,eBPF 网络可观测个性也预计会在将来 8 月公布。下文次要基于 eBPF 观测 HTTP 1、HTTP 1.1 以及 HTTP2 的角度介绍 eBPF 的针对可观测场景的利用,同时回顾 HTTP 协定本身的倒退。eBPF 根本介绍 eBPF 是近几年 Linux Networkworking 方面比拟火的技术之一,目前在平安、网络以及可察看性方面利用宽泛,比方 CNCF 我的项目 Cilium 齐全是基于 eBPF 技术实现,解决了传统 Kube-proxy 在大集群规模下 iptables 性能急剧下降的问题。从基本功能上来说 eBPF 提供了一种兼具性能与灵活性来自定义交互内核态与用户态的新形式,具体表现为 eBPF 提供了敌对的 api,使得能够通过依赖 libbpf、bcc 等 SDK,将自定义业务逻辑平安的嵌入内核态执行,同时通过 BPF Map 机制(不须要屡次拷贝)间接在内核态与用户态传递所需数据。

当聚焦在可观测性方面,咱们能够将 eBPF 类比为 Javaagent 进行介绍。Javaagent 的基本功能是程序启动时对于已存在的字节码进行代理字节码织入,从而在无需业务批改代码的状况下,主动为用户程序退出 hook 点,比方在某函数进入和返回时增加 hook 点能够计算此函数的耗时。而 eBPF 相似,提供了一系列内核态执行的切入点函数,无需批改代码,即可观测利用的外部状态,以下为罕用于可观测性的切入点类型:kprobe:动静附加到内核调用点函数,比方在内核 exec 零碎调用前查看参数,能够 BPF 程序设置 SEC(“kprobe/sys_exec”) 头部进行切入。tracepoints:内核曾经提供好的一些切入点,能够了解为动态的 kprobe,比方 syscall 的 connect 函数。uprobe:与 krobe 对应,动静附加到用户态调用函数的切入点称为 uprobe,相比如 kprobe 内核函数的稳定性,uprobe 的函数由开发者定义,当开发者批改函数签名时,uprobe BPF 程序同样须要批改函数切入点签名。perf_events:将 BPF 代码附加到 Perf 事件上,能够根据此进行性能剖析。

TCP 与 eBPF 因为本文观测协定 HTTP 1、HTTP1.1 以及 HTTP2 都是基于 TCP 模型,所以先回顾一下 TCP 建设连贯的过程。首先 Client 端通过 3 次握手建设通信,从 TCP 协定上来说,连贯代表着状态信息,比方蕴含 seq、ack、窗口 /buffer 等,而 tcp 握手就是协商进去这些初始值;而从操作系统的角度来说,建设连贯后,TCP 创立了 INET 域的 socket,同时也占用了 FD 资源。对于四次挥手,从 TCP 协定上来说,能够了解为开释终止信号,开释所维持的状态;而从操作系统的角度来说,四次挥手后也意味着 Socket FD 资源的回收。而对于应用层的角度来说,还有一个罕用的概念,这就是长连贯,但长连贯对于 TCP 传输层来说,只是应用形式的区别:应用层短连贯:三次握手 + 单次传输数据 + 四次挥手,代表协定 HTTP 1 应用层长连贯:三次握手 + 屡次传输数据 + 四次挥手,代表协定 HTTP 1.1、HTTP2

参考下图 TCP 建设连贯过程内核函数的调用,对于 eBPF 程序能够很容易的定义好 tracepoints/kprobe 切入点。例如建设连贯过程能够切入 accept 以及 connect 函数,开释链接过程能够切入 close 过程,而传输数据能够切入 read 或 write 函数。

基于 TCP 大多数切入点曾经被动态化为 tracepoints,因而 BPF 程序定义如下切入点来笼罩上述提到的 TCP 外围函数(sys_enter 代表进入时切入,sys_exit 代表返回时切入)。SEC(“tracepoint/syscalls/sys_enter_connect”) SEC(“tracepoint/syscalls/sys_exit_connect”) SEC(“tracepoint/syscalls/sys_enter_accept”) SEC(“tracepoint/syscalls/sys_exit_accept”) SEC(“tracepoint/syscalls/sys_enter_accept4”) SEC(“tracepoint/syscalls/sys_exit_accept4”) SEC(“tracepoint/syscalls/sys_enter_close”) SEC(“tracepoint/syscalls/sys_exit_close”) SEC(“tracepoint/syscalls/sys_enter_write”) SEC(“tracepoint/syscalls/sys_exit_write”) SEC(“tracepoint/syscalls/sys_enter_read”) SEC(“tracepoint/syscalls/sys_exit_read”) SEC(“tracepoint/syscalls/sys_enter_sendmsg”) SEC(“tracepoint/syscalls/sys_exit_sendmsg”) SEC(“tracepoint/syscalls/sys_enter_recvmsg”) SEC(“tracepoint/syscalls/sys_exit_recvmsg”) …. 联合上述概念,咱们以 iLogtail 的 eBPF 工作模型为例,介绍一个可观测畛域的 eBPF 程序是如何真正工作的。更多具体内容能够参考此分享:基于 eBPF 的利用可观测技术实际。如下图所示,iLogtaileBPF 程序的工作空间分为 Kernel Space 与 User Space。Kernel Space 次要负责数据的抓取与预处理:抓取:Hook 模块会根据 KProbe 定义拦挡网络数据,虚线中为具体的 KProbe 拦挡的内核函数(应用上述形容的 SEC 进行定义),如 connect、accept 以及 write 等。预处理:预处理模块会依据用户态配置进行数据的拦挡抛弃以及数据协定的推断,只有合乎需要的数据才会传递给 SendToUserSpace 模块,而其余数据将会被抛弃。其后 SendToUserSpace 模块通过 eBPF Map 将过滤后的数据由内核态数据传输到用户态。User Space 的模块次要负责数据分析、聚合以及治理:剖析:Process 模块会一直解决 eBPF Map 中存储的网络数据,首先因为 Kernel 曾经推断协定类型,Process 模块将依据此类型进行细粒度的协定剖析,如剖析 MySQL 协定的 SQL、剖析 HTTP 协定的状态码等。其次因为 Kernel 所传递的连贯元数据信息只有 Pid 与 FD 等过程粒度元信息,而对于 Kubernetes 可观测场景来说,Pod、Container 等资源定义更有意义,所以 Correlate Meta 模块会为 Process 解决后的数据绑定容器相干的元数据信息。聚合:当绑定元数据信息后,Aggreate 模块会对数据进行聚合操作以防止反复数据传输,比方聚合周期内某 SQL 调用 1000 次,Aggreate 模块会将最终数据抽象为 XSQL:1000 的模式进行上传。治理:整个 eBPF 程序交互着大量着过程与连贯数据,因而 eBPF 程序中对象的生命周期须要与机器理论状态相符,当过程或链接开释,相应的对象也须要开释,这也正对应着 Connection Management 与 Garbage Collection 的职责。

eBPF 数据解析 HTTP 1、HTTP1.1 以及 HTTP2 数据协定都是基于 TCP 的,参考上文,肯定有以下函数调用:connect 函数:函数签名为 int connect(int sockfd, const struct sockaddr addr, socklen_t addrlen), 从函数签名入参能够获取应用的 socket 的 fd,以及对端地址等信息。accept 函数:函数签名为 int accept(int sockfd, struct sockaddr addr, socklen_t addrlen), 从函数签名入参同样能够获取应用的 socket 的 fd,以及对端地址等信息。sendmsg 函数:函数签名为 ssize_t sendmsg(int sockfd, const struct msghdr msg, int flags), 从函数签名能够看出,基于此函数能够拿到发送的数据包,以及应用的 socket 的 fd 信息,但无奈间接基于入参通晓对端地址。recvmsg 函数:函数签名为 ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags), 从函数签名能够看出,基于此函数咱们拿到接管的数据包,以及应用的 socket 的 fd 信息,但无奈间接基于入参通晓对端地址。close 函数:函数签名为 int close(int fd), 从函数签名能够看出,基于此函数能够拿到行将敞开的 fd 信息。HTTP 1 / HTTP 1.1 短连贯模式 HTTP 于 1996 年推出,HTTP 1 在用户层是短连贯模型,也就意味着每一次发送数据,都会随同着 connect、accept 以及 close 函数的调用,这就认为这 eBPF 程序能够很容易的寻找到 connect 的起始点,将传输数据与地址进行绑定,进而构建服务的上下游调用关系。

能够看出 HTTP 1 或者 HTTP1.1 短连贯模式是对于 eBPF 是十分敌对的协定,因为能够轻松的关联地址信息与数据信息,但回到 HTTP 1/HTTP1.1 短连贯模式 自身来说,‘敌对的代价’不仅意味着带来每次 TCP 连贯与开释连贯的耗费,如果两次传输数据的 HTTP Header 头雷同,Header 头也存在冗余传输问题,比方下列数据的头 Host、Accept 等字段。

HTTP 1.1 长连贯 HTTP 1.1 于 HTTP 1.0 公布的一年后公布(1997 年),提供了缓存解决、带宽优化、谬误告诉治理、host 头解决以及长连贯等个性。而长连贯的引入也局部解决了上述 HTTP1 中每次发送数据都须要通过三次握手以及四次挥手的过程,晋升了数据的发送效率。但对于应用 eBPF 察看 HTTP 数据来说,也带来了新的问题,上文提到建设地址与数据的绑定依赖于在 connect 时进行 probe,通过 connect 参数拿到数据地址,从而与后续的数据包绑定。但回到长连贯状况,如果 connect 于 1 小时之前建设,而此时才启动 eBPF 程序,所以咱们只能探测到数据包函数的调用,如 send 或 recv 函数。此时应该如何建设地址与数据的关系呢?

首先能够回到探测函数的定义,能够发现此时尽管没有明确的地址信息,然而能够晓得此 TCP 报文应用的 Socket 与 FD 信息。因而能够应用 netlink 获取此 Socket 的元信息,进行对长连贯补充对端地址,进而在 HTTP 1.1 长连贯协定构建服务拓扑与剖析数据明细。ssize_t sendmsg(int sockfd, const struct msghdr msg, int flags) ssize_t recvmsg(int sockfd, struct msghdr msg, int flags)

HTTP 2 在 HTTP 1.1 公布后,因为冗余传输以及传输模型串行等问题,RPC 框架基本上都是进行了私有化协定定义,如 Dubbo 等。而在 2015 年,HTTP2 的公布突破了以往对 HTTP 协定的很多诟病,除解决在上述咱们提到的 Header 头冗余传输问题,还解决 TCP 连接数限度、传输效率、队头拥塞等问题,而 gRPC 正式基于 HTTP2 构建了高性能 RPC 框架,也让 HTTP 1 时代层出不穷的通信协议,也逐步走向了归一时代,比方 Dubbo3 全面兼容 gRPC/HTTP2 协定。个性以下内容首先介绍一些 HTTP2 与 eBPF 可察看性相干的要害个性。多路复用 HTTP 1 是一种同步、独占的协定,客户端发送音讯,期待服务端响应后,才进行新的信息发送,这种模式节约了 TCP 全双工模式的个性。因而 HTTP2 容许在单个连贯上执行多个申请,每个申请相应应用不同的流,通过二进制分帧层,为每个帧调配一个专属的 stream 标识符,而当接管方收到信息时,接管方能够将帧重组为残缺音讯,晋升了数据的吞吐。此外能够看到因为 Stream 的引入,Header 与 Data 也进行了拆散设计,每次传输数据 Heaer 帧发送后为尔后 Data 帧的对立头部,进一步提醒了传输效率。

首部压缩 HTTP 首部用于发送与申请和响应相干的额定信息,HTTP2 引入首部压缩概念,应用与注释压缩不同的技术,反对跨申请压缩首部,能够防止注释压缩应用算法的平安问题。HTTP2 采纳了基于查问表和 Huffman 编码的压缩形式,应用由事后定义的动态表和会话过程中创立的动静表,没有援用索引表的首部能够应用 ASCII 编码或者 Huffman 编码传输。

但随着性能的晋升,也意味着越来越多的数据防止传输,这也同时意味着对 eBPF 程序可感知的数据会更少,因而 HTTP2 协定的可察看性也带来了新的问题,以下咱们应用 gRPC 不同模式以及 Wireshark 剖析 HTTP2 协定对 eBPF 程序可观测性的挑战。GRPCSimple RPCSimple RPC 是 GRPC 最简略的通信模式,申请和响应都是一条二进制音讯,如果放弃连贯能够类比为 HTTP 1.1 的长连贯模式,每次发送收到响应,之后再持续发送数据。

但与 HTTP 1 不同的是首部压缩的引入,如果维持长连贯状态,后续发的数据包 Header 信息将只存储索引值,而不是原始值,咱们能够看到下图为 Wirshark 抓取的数据包,首次发送是蕴含残缺 Header 帧数据,而后续 Heders 帧长度升高为 15,缩小了大量反复数据的传输。

Stream 模式 Stream 模式是 gRPC 罕用的模式,蕴含 Server-side streaming RPC,Client-side streaming RPC,Bidirectional streaming RPC,从传输编码上来说与 Simple RPC 模式没有不同,都分为 Header 帧、Data 帧等。但不同的在于 Data 帧的数量,Simple RPC 一次发送或响应只蕴含一个 Data 帧 模式,而 Stream 模式能够蕴含多个。1、Server-side streaming RPC:与 Simple RPC 模式不同,在 Server-side streaming RPC 中,当从客户端接管到申请时,服务器会发回一系列响应。此响应音讯序列在客户端发动的同一 HTTP 流中发送。如下图所示,服务器收到来自客户端的音讯,并以帧音讯的模式发送多个响应音讯。最初,服务器通过发送带有呼叫状态详细信息的尾随元数据来完结流。

2、Client-side streaming RPC:在客户端流式 RPC 模式中,客户端向服务器发送多条音讯,而服务器只返回一条音讯。

3、Bidirectional streaming RPC:客户端和服务器都向对方发送音讯流。客户端通过发送标头帧来设置 HTTP 流。建设连贯后,客户端和服务器都能够同时发送音讯,而无需期待对方实现。

tracepoint/kprobe 的挑战从上述 wirshark 报文以及协定模式能够看出,历史针对 HTTP1 时代应用的 tracepoint/kprobe 会存在以下挑战:Stream 模式: 比方在 Server-side stream 下,如果 tracepoint/kprobe 探测的点为 Data 帧,因 Data 帧因为无奈关联 Header 帧,都将变成有效 Data 帧,但对于 gRPC 应用场景来说还好,个别 RPC 发送数据和承受数据都很快,所以很快就会有新的 Header 帧收到,但这时会遇到更大的挑战,长连贯下的首部压缩。

长连贯 + 首部压缩:当 HTTP2 放弃长连贯,connect 后的第一个 Stream 传输的 Header 会为残缺数据,而后续 Header 帧如与前置 Header 帧存在雷同 Header 字段,则数据传输的为地址信息,而真正的数据信息会交给 Server 或 Client 端的应用层 SDK 进行保护,而如下图 eBPF tracepoints/kprobe 在 stream 1 的尾部帧才进行 probe,对于后续的 Header2 帧大概率不会存在残缺的 Header 元数据,如下图 Wireshark 截图,蕴含了很多 Header 信息的 Header 长度仅仅为 15,能够看出 eBPF tracepoints/kprobe 对于这种状况很难解决。

从上文可知,HTTP2 能够归属于有状态的协定,而 Tracepoint/Kprobe 对有状态的协定数据很难解决欠缺,某些场景下只能做到进化解决,以下为应用 Tracepoint/Kprobe 解决的根本流程。

Uprobe 可行吗?从上述 tracepoint/kprobe 的挑战能够看到,HTTP 2 是一种很难被观测的协定,在 HTTP2 的协定标准上,为缩小 Header 的传输,client 端以及 server 端都须要保护 Header 的数据,下图是 grpc 实现的 HTTP2 客户端保护 Header 元信息的截图,所以在应用层能够做到拿到残缺 Header 数据,也就绕过来首部压缩问题,而针对应用层协定,eBPF 提供的探测伎俩是 Uprobe(用户态),而 Pixie 我的项目也正是基于 Uprobe 实际了 gRPC HTTP2 流量的探测,具体内容能够参考此文章 [1]。

下图展现了应用 Uprobe 观测 Go gRPC 流量的根本流程,如其中 writeHeader 的函数定义为 func (l *loopyWriter) writeHeader(streamID uint32, endStream bool, hf []hpack.HeaderField, onWrite func()), 能够看到明确的 Header 文本。

Kprobe 与 Uprobe 比照从上文能够看出 Uprobe 实现简略,且不存在数据进化的问题,但 Uprobe 真的完满吗?兼容性:上述计划仅仅是基于 Golang gRPC 的 特定办法进行探测,也就意味着上述仅能笼罩 Golang gRPC 流量的察看,对于 Golang 其余 HTTP2 库无奈反对。多语言性:Uprobe 只能基于办法签名进行探测,更实用于 C /GO 这种纯编译型语言,而对于 Java 这种 JVM 语言,因为运行时动静生成符号表,尽管能够依附一些 javaagent 将 java 程序用于 Uprobe,然而绝对于纯编译型语言,用户应用老本或革新老本还是会更高一些。稳定性:Uprobe 绝对于 tracepoint/kprobe 来说是不稳固的,如果探测的函数函数签名有扭转,这就意味着 Uprobe 程序将无奈工作,因为函数注册表的扭转将使得 Uprobe 无奈找到切入点。综合下来 2 种计划比照如下,能够看到 2 种计划对于 HTTP2(有状态)的观测都存在局部取舍:

总结上述咱们回顾了 HTTP1 到 HTTP2 时代的协定变迁,也看到 HTTP2 晋升传输效率做的种种致力,而正是 HTTP2 的微小效率晋升,也让 gRPC 抉择了间接基于 HTTP2 协定构建,而也是这种抉择,让 gRPC 成为了 RPC 百家争鸣后是隐形事实协定。但咱们也看到了协定的提高意味着更少的数据交互,也让数据可察看变得更加艰难,比方 HTTP2 应用 eBPF 目前尚无完满的解决办法,或应用 Kprobe 察看,抉择的多语言性、流量拓扑剖析、但答应了失去流量细节的危险;或应用 Uprobe 察看,抉择了数据的细节,拓扑,但答应了多语言的兼容性问题。iLogtail 致力于打造笼罩 Trace、Metrics 以及 Logging 的可观测性的对立 Agent,而 eBPF 作为目前可观测畛域的热门采集技术,提供了无侵入、平安、高效观测流量的能力,预计 8 月份,咱们将在 iLogtail Cpp 正式开源后公布此局部性能,欢送大家关注和相互交换。参考:TCP 的几个状态:https://www.s0nnet.com/archiv… 的总结:https://liyaoli.com/2015-04-1… Control Protocol:https://en.wikipedia.org/wiki… Networks:https://www.cse.iitk.ac.in/us… A Deep Dive into the Communication Pattern:https://thenewstack.io/grpc-a… 深刻了解 Linux socket:https://www.modb.pro/db/153725 基于 eBPF 的利用可观测技术实际:https://www.bilibili.com/vide… 重磅来袭!2022 上半年阿里云社区最热电子书榜单!千万浏览量、百万下载量、上百本电子书,近 200 位阿里专家参加编写。多元化抉择、全畛域笼罩,汇聚阿里巴巴技术实际精髓,读、学、练一键三连。开发者藏经阁,开发者的工作伴侣~ 点击这里,查看详情。原文链接:https://click.aliyun.com/m/10… 本文为阿里云原创内容,未经容许不得转载。

正文完
 0