近年来,由于 eBPF 在 Linux 内核级别灵活的可编程性、安全性等优势,在云原生网络、安全和可观测性等方面应用广泛。eBPF 可以在不侵入任何业务代码的基础上实现云原生应用的可观测性。但是 eBPF 对 Linux 内核版本是有一定要求的(4.14 以上),伏羲私有云部分生产集群的内核版本比较低,升级内核会影响大量线上应用,成本太高。
而 kindling 正好在基于 eBPF 实现云原生可观测性能力的基础上,借助内核模块技术在低版本 Linux 内核上实现了等价于 eBPF 的相关能力。本文会介绍一些伏羲私有云基于 eBPF 和 kindling 在云原生可观测性领域的一些探索。
背景
伏羲私有云的监控系统是基于主流的 Prometheus+Grafana 实现的,能较好地满足大部分日常的监控需求,但是随着业务的发展,内部多个 RPC 等自研网络通信协议被广泛应用,对监控系统提出了更高的要求。目前主要面临的挑战有:
伏羲私有云内部有多个自研的网络通信协议被各项业务广泛使用,如何低成本、灵活地获取使用业务自研协议相关的监控指标?
业务请求链路上内部服务众多,如何在不侵入业务代码的情况下,为没有经过 kong 代理对外暴露的内部服务提供延时和 TPS 等监控指标,以便更好地了解服务的运行状态和资源需求。基于 TPS 还可以为服务实现自动化的扩缩容,提升集群的资源利用率,实现降本增效。
eBPF 与 kindling
eBPF 简介
eBPF 为 Extended Berkeley Packet Filter 的缩写,字面意思为扩展的伯克利数据包过滤器,经过扩展,eBPF 的功能远比包过滤器强大得多。eBPF 是一个框架,允许用户在操作系统的内核中加载和运行自定义程序,这意味着它可以扩展甚至修改内核的行为。eBPF 程序加载和运行架构图如下图所示:
eBPF 相关技术原理不是本文重点,不再赘述。近年来,eBPF 之所在云原生领域发展迅速,主要得益于几个优势:
eBPF 有一套完整的验证机制来保障 eBPF 程序的安全性,只有当 eBPF 程序代码通过 eBPF 验证器(verifier)的安全性分析后,才能被允许加载到 Linux 内核中运行,且 eBPF 验证器能确保 eBPF 程序不能访问任意内存空间,只能访问特定套接字缓冲区(socket buffer)中的数据;
eBPF 程序可以动态加载到内核中,在无需重启 Linux 系统的情况下,实现对内核自定义功能的增减,且性能开销较小;
eBPF 程序是事件驱动的,功能强大,将原本单一的数据包过滤事件扩展到了内核态函数、用户态函数、跟踪点、性能事件、安全控制等领域,不同的 eBPF 程序可以由不同类型的事件触发运行。
Kubernetes 的可观测性和安全工具大多都采用了 sidecar 模式,但是当 pod 数量很多时,对应的 sidecar 容器也会很多,会占用大量资源,而 eBPF 直接在内核级别就能实现可观测性,无需运行 sidecar 容器。
kindling 简介
kindling是一款基于 eBPF 的云原生可观测性开源工具,旨在帮助用户更好、更快地定界(triage)云原生系统故障。通过 kindling,用户可以快速定界问题类型,比如是应用代码问题还是基础设施问题。如果是代码问题,可以借助 APM(Application Performance Monitoring 应用性能监控)监控进一步排查问题;如果是基础设施问题,那么通过分析来自内核的相关监控指标,定位故障点。
kindling 架构如下图所示:
kindling 整体架构包含三个部分:用户态 Go 程序、用户态 C/C++程序和内核态 drivers 程序。用户态 Go 程序满足的是上层可观测需求的开发,其他两个部分实现是内核需求的开发。
伏羲私有云之所以选择 kindling 进行基于 eBPF 的云原生可观测性探索,是经过大量调研后综合考虑的结果,主要因为 kindling 有以下几个优势:
kindling 兼容不支持 eBPF 的低版本 Linux 内核(4.14 以下),通过内核模块的形式在低版本 Linux 内核实现了等价于 eBPF 的云原生可观测能力;
kindling-collector 部分模块集成了 Opentelemetry 的 SDK,这样 kindling 的指标在输出时有较高的灵活性,可以输出到 opentelemetry collector 、Prometheus 等多种可观测性平台;
kindling 自研了一个 grafana 插件,提供了方便的云原生监控的可视化功能;
kindling 部署维护成本较低,以 daemonset 的形式运行在 Kubernetes 集群中,且性能开销较低。
探索与实践
伏羲私有云基于 eBPF 和 kindling 在云原生可观测性领域探索和实践中取得了一些阶段性工作成果,并回馈开源社区做了一些贡献。
自动化适配任意 Linux 内核版本
kindling 采用的内核模块或 eBPF 形式的探针工作在内核态,和内核版本强绑定,为了程序的准确性,目前是完全匹配内核版本号,使用时需要为每一个内核版本单独编译探针。编译依赖 Linux 内核头文件,但我们的集群众多,不同时期的集群内核版本有所差异,即使在同一个集群内,也有不同批次的机器加入集群的情况,同时共存了多个内核版本。构建 kindling 镜像时就会面临以下两个问题:
构建成本高,需要手动在不同内核版本的节点上执行构建镜像的操作,另外出于安全考虑,线上集群原则上是不能开放过多的操作权限的,下载头文件 -> 编译探针 -> 构建镜像的操作流程需要优化;
部分内核版本的头文件已被 Debian 软件源移除,无法直接下载。
对此,我们实现了一套自动化适配任意 Linux 内核版本的 kindling 镜像构建流程,大幅提升了基于 kindling 进行二次开发的工作效率,整体流程如下图所示:
其核心在于:
将构建镜像流程容器化,在 docker 容器中执行下载头文件-> 编译探针 -> 构建镜像的操作;
通过snapshot.debian.org 时光机自动匹配下载已被 Debian 官方源移除的内核头文件。
伏羲私有云自研 RPC 协议解析方案设计与实现
伏羲私有云分析得出 kindling 现有能力存在一定缺陷,针对自研 RPC 协议的特性提出了自研的解决方案,基于 kindling 源码二次开发实现并验证了方案的正确性,在对业务代码零侵入性的基础上,实现了自研协议的可观测性能力,并回馈社区,参与到 kindling 协议解析核心流程改进方案的设计和验证工作中。
kindling 缺陷分析
kindling 目前支持为使用 HTTP、DNS、MySQL 等通用协议的服务提供请求级的 RED 指标(TPS,错误率、延时)。伏羲私有云内部很多服务使用自研协议进行通信,需要基于 kindling 进行二次开发,解析自研协议,来获取这些服务的 RED 指标。
在开发测试过程中,我们发现,由于自研协议通信场景相较于 HTTP 更为复杂,当测试用例中存在以下三种情况之一时,kindling 提供的 tps 指标不准确:
客户端异步的向服务端发送多次请求;
服务端在响应某些请求前,会先请求客户端;
服务端主动向客户端发送消息,而且不要求得到响应。
通信场景建模
根据自研协议的特性,我们将通信场景进行建模,以下三种通信场景是 kindling 已有的协议解析流程不能支持的。
双工通信:客户端和服务端都可以向对方发起请求;
流式通信:客户端和服务端建立的是 TCP 长连接,在 TCP 长连接上并发发生多次网络调用,客户端在未收到前一次系统调用的响应之前就发送下一次网络调用的请求,同时允许服务端乱序响应,如下图所示,服务端首先响应 response2,后响应 response1;
单向请求:服务端会主动向客户端发送消息,而且不要求得到响应。
自研协议解析
自研协议简介
我们选取其中一个 RPC 自研协议(以下简称 gateway 协议,已脱敏)进行介绍,它的协议格式和字段含义如下:
gateway 协议用在客户端和网关的通信中,网关代理了客户端和服务端的通信。客户端请求网关时,service_id 字段标识真正要请求的服务,sub_service_id 是二级服务标识,标识 service_id 服务的某个接口,网关根据这两个字段把请求转发给对应服务的对应接口。body_size 字段标识 body 字段的长度,body 字段存放请求和响应内容。响应报文 body 字段前 4 个字节必须是 int32 类型的字节码。session_id 为会话标识,访问无状态服务时可为空字符串,访问有状态服务时如果为空,代表开启一个新的会话,由 gateway 根据负载均衡策略分配 session_id,交由响应带回,下次请求若要保持会话则需携带。
解析流程设计与实现
解析流程中,我们专门为 gateway 协议开启一条旁路,优先按照 gateway 协议格式解析输入的数据包。内存中维护 gatewayMap 哈希结构,记录所有按照 gateway 协议格式的 tcp 连接,对于系统捕获的每一个 kindlingEvent,获取唯一标识 TCP 连接的 PID+FD 信息。若 gatewayMap 记录该 TCP 连接采用 gateway 协议,则数据包送入 gateway 专属解析流程处理;若 requestMonitor 记录该 TCP 连接非 gateway 协议,数据包送入已有解析流程处理。对于新建立的 TCP 连接,两个哈希表中均无记录,则按照 gateway 请求报文格式解析数据包,判断是否属 gateway 协议,对应更新哈希表。
gateway 协议专属解析流程进行处理时,判断数据包是请求报文或响应报文,分别按照请求/响应报文的格式解析,若解析失败,则直接丢弃数据包;若因数据包太短无法完成解析,则停止处理,等待合并下一数据包内容,重新解析。成功解析出 gateway 请求报文时,先将 gateway 请求详情缓存到 requestMap 中,然后,数据包截掉当前请求报文,剩余部分继续进入请求报文的解析流程。成功解析出 gateway 响应报文时,从 requestMap 匹配对应请求详情,匹配失败则丢弃响应,匹配成功即完整构成一次网络调用详情,生成 DataGroup。
gateway 协议专属解析流程中涉及包太短等待合并、包太长需要分隔。为了便于进行两种操作,用 ByteStream 存储 TCP 链接连续出现的数据包。它是类似队列的数据结构,入队 KindingEvent,出队字节数组。
基于 TPS 的内部服务自动扩缩容,实现降本增效
伏羲私有云上除了通过 kong 代理对外暴露的服务外,还有很多应用是作为服务调用链的中间服务存在的,并没有通过 kong 代理对外暴露,因此现有的监控系统得不到基于 kong 的服务 TPS 监控指标。
如果需要对这些应用进行 TPS 监控,就需要对业务代码进行侵入性改造,而基于 eBPF 和 kindling 则可以在完全不侵入业务代码的情况下,通过基础指标过滤、转换得到应用的 TPS 指标,伏羲私有云用户可以根据业务特点在页面上配置基于 TPS 的自动扩缩容策略,有助于提升集群的资源利用率,实现降本增效。
参与开源社区
伏羲私有云在 eBPF 和 kindling 的云原生可观测性的探索和实践中,给社区提了多个 issue 和 PR,在开源技术生态建设中与社区保持沟通与合作,下文介绍其中两个案例。
部署更新 kindling 流程优化
在基于 kindling 进行二次开发的过程中,我们会在集群中频繁批量更新部署 kindling daemonset 服务,却发现 kubernetes API Server 所在节点的监控数据会伴随着出现异常,在 kindling 批量删除重建的时间点,正好对应了节点上 ETCD 磁盘 IO 负载和 CPU 负载飙升的情况。
通过阅读源码,我们定位到 kindling 程序刚启动时会从 API Server 同步集群全部的 Pod、Service、ReplicateSet、Node 等资源信息,并且确认调用 API Server 接口时带有参数"resource_version=0"(会默认优先读缓存数据),通过查看集群日志,我们进一步发现是因为开发集群 API Server 的内存配额较小,直接 OOM 重启了,这一过程中 kindling 还在大量并发向 API Server 发送查询请求,连锁引发了 ETCD 的负载飙升。
经过和社区沟通确认,我们做了如下操作优化 kindling 的部署更新流程:
修改了 kindling 源码,使其同步集群的 deployment 作为各指标的 workload_name 标签,而不是 replicateSet,从而大幅减少同步的数据量;
滚动更新,为限制更新 kindling 时对 API Server 的并发请求量,我们让 kindling 分批次滚动更新,分批的间隔时间作为可配置项;
渐次部署,为限制首次部署 kindling 时对 API Server 的并发请求量,我们限制 kindling 只部署在有特定标签的节点上,并通过脚本控制节点标签的增删操作,从而控制 kindling 整体的部署进度。
提供容器中编译 topo plugin 的方法
kindling 提供了一个 Grafana 插件(topo plugin)用来绘制网络调用拓扑图,该插件尚未通过 Grafana 官方的认证,无法从应用商店直接安装使用。kindling 只提供了一个预置了 topo plugin 插件的 Grafana 镜像供用户试用,缺少该插件的可执行文件。为此,提供了在容器中编译 topo plugin 的方法,免于在宿主机配置 node.js 环境(#332)。
总结与展望
通过一段时间的探索和实践,伏羲私有云在基于 eBPF 技术生态的云原生可观测领域取得了一些阶段性成果,与开源社区保持着良好的沟通与合作,也回馈社区参与到 eBPF 开源技术生态的建设中。
近年来,eBPF 技术发展十分迅速,相关的云原生可观测性领域的技术生态也非常活跃,但是目前成熟稳定的基础设施还相对较少,将 eBPF 技术生态在生产环境落地的门槛还是很高,一些比较热门的开源项目,比如 cilium 等,在我们的实际工作中经过测试和试用,发现与我们的预期还有一定差距,我们会对基于 eBPF 的云原生可观测领域保持关注。
作者介绍
石钟浩,网易伏羲高级平台开发工程师。
张龙,网易伏羲平台开发实习生。
评论