面向Java应用网络流的非侵入可观测指标采集联合方案 – Sermant & Gopher

作者:杨奕 华为云技术规划专家 | 殷森道 华为云高级软件工程师 | 张豪鹏 华为云高级软件工程师

摘要

随着2022年来eBPF的技术大火,该技术以其非侵入的优点在可观测领域开始大放异彩。我们基于eBPF技术也做了许多实践,总的来看,eBPF在网络运维的四层网络客观性方面具备得天独厚的优势,然而在七层(应用流)监控领域仍有力不从心的"死角",比如对Java应用的观测。我们基于这个场景,结合gala-gopher和Sermant两大利器,通过eBPF对四层的观测,以及Sermant对Java应用七层的观测能力,互相补充网络、应用两个层面的运维能力,真正做到了应用侧/网络侧问题的快速界定。本文亦给出了一个实践案例来呈现效果。

一、 背景

随着2022年来eBPF的技术大火,该技术以其非侵入的优点在可观测领域开始大放异彩。eBPF是一个能够在内核中运行沙箱程序的技术,提供安全的注入代码的机制,通过注入eBPF代码可以安全、高性能地访问整个系统的运行状态。其在OS内核中运行有一个验证器(eBPF Verifier),对注入内核的eBPF代码进行严格把关,保证不侵害内核安全。并且提供了JIT即时编译器,对注入内核的eBPF代码进行即时编译运行。对用户态提供eBPF MAP机制做数据交互。同时,eBPF在用户空间提供了丰富的SDK,支持C/C++、Golang、Rust等编程语言的接口,使不同领域的用户都能使用这门技术,在网络、安全、可观测性和追踪领域都有所建树。eBPF技术全景图如下图[1]所示。

图1-1 eBPF技术架构及其生态

eBPF由于其可通过内核埋入观测点的技术原理,在面向网络流的应用监控方面有着以下天然优势:

  1. 面向4层的网络监控,其本身具备对内核"编程"的能力,天然可在内核协议栈中添加观测点;

  2. 对于一些7层的网络协议,可简单通过捕获原始报文,在监控程序处进行协议解码,还原业务信息,并进行metrics统计。

但是针对一些比较复杂的7层协议,用eBPF做监控就比较繁琐,甚至力不从心。设想以下几个典型场景:

  1. 应用流之间的加密访问场景,比如Java应用默认使用JDK中的JSSE,OS上的OpenSSL,GoSSL,RustSSL等;

  2. 复杂RPC协议,如gRPC, Dubbo 3.0等场景下容易导致采集的数据(非业务面数据)丢失。

为了解决以上问题,业界的通用做法是采用eBPF + uprobe方案,去截取带有业务语义的应用层数据。例如,Deepflow、Pixie针对gRPC协议数据采集的做法。

但是基于uprobe的做法同样缺点很明显。主要体现在:

1) 实现比较复杂,要基于三方库协议一个个适配,还要找准uprobe的观测点;

2) 版本兼容难度高,业务层如果有函数改动,则需要重新适配;

3) 如果以上两点开发者还算都能克服,第三点则算是硬伤了,就是针对国内政企行业面向业务开发使用最主流的编程语言------Java,暂时没有比较好的解决方案。

所谓白猫、黑猫,抓到老鼠就是好猫。对于面向应用流的可观测技术,只要能做到非侵入就是好技术。在Java领域,很自然地,有一个技术能补充Java应用场景的可观测性能力,那就是基于字节码增强技术的JavaAgent方案。

这个方案其实在Java领域已经相对比较成熟了,其核心就是利用JavaAgent通过运行时的字节码增强,在Java函数埋入可观测点。在业界也有很多优秀的开源实践,如Skywalking, OpenTelemetry等。

图1-2 JavaAgent技术生态

与eBPF相比,JavaAgent对Java的L7业务层的观测具备更加完备的能力的和优势,同时亦能收到更好的监控效果。但是针对在内核层才能观测到的4层网络指标,JavaAgent则没有太好的解决方案。然而,一个完整应用流的监控产品,不仅需要L7指标,同时也需要L4指标。这样当业务出现问题时,才能第一时间定界到底是应用侧的问题 (L7异常,L4正常), 还是网络侧的问题 (L7, L4均异常)。

为此,针对应用流的可观测场景,能否将JavaAgent和eBPF二者相结合,就成了解决问题的关键。以下则是我们解决这个问题做的一次实践------结合Sermant + gala-gopher的数据采集方案。

二、 Sermant + gala-gopher的数据采集方案

2.1 gala-gopher

gala-gopher是一款C/S架构、基于AI的操作系统亚健康诊断工具。其基于eBPF + Java Agent无侵入观测技术,并以AI技术辅助,实现亚健康故障(比如性能抖动、错误率提升、系统卡顿等问题现象)分钟级诊断,简化IT基础设施的运维过程。架构图如下图[2]所示:

图2-1 gala技术框架

gala的整个架构由gala-gopher、gala-ops两个软件组成,gala-ops安装在管理节点内, gala-gopher安装在生产节点内。

gala-gopher是gala项目内负责数据采集的组件,其为gala项目提供Metrics、Event、Perf等数据,便于gala项目完成系统拓扑的绘制和故障根因的定位。其是一款结合eBPF、Java Agent等非侵入可观测技术的观测平台,探针是gala-gopher用于观测和采集数据的主要工具,通过探针式架构gala-gopher可以轻松实现增加、减少探针。gala-gopher应用drill-down观测,将Linux操作系统层、基础设施层、容器、应用、业务等多层次采集到的数据结合起来,避免形成数据孤岛,从而达到更好的可观测效果。其覆盖了业界主流的开源应用协议的观测,使用非侵入式、零修改的能力使得用户能够安全、便捷地使用,并且基于插件化的架构,也能让用户可以定制化自己的可观测探针,如下图所示:

图2-2 gala-gopher的能力框架

图2-3 gala-gopher能力架构及支持场景

gala-gopher主要包括探针框架和探针程序两部分,探针有Native Probe、Extend Probe两类。Native Probe只能使用C语言实现,Extend Probe不限定编程语言。

图2-4 gala-gopher技术框架

如图所示[3],gala-gopher框架进程负责探针的生命周期管理(探针启停、保活等),收集并处理探针采集到的数据,并提供kafka、prometheus等输出方式。

框架进程中,API模块提供了用户动态配置接口,用户可以自定义开启的探针及每个探针的监控范围;probe-mng模块负责探针的声明周期管理和调度;ipc模块负责与每个探针交互,动态下发监控配置等;ingress负责接收探针,并缓存到imdb模块中;imdb模块负责数据缓存处理;egress负责将结构化数据通过kafka、prometheus等接口形式输出。

而探针分为native probes和extend probes,native probe内嵌于gala-gopher框架进程中,非独立进程运行,且不支持扩展;extend probes则是扩展探针,其以独立的子进程形式运行,在gala-gopher框架下可以自定义扩展。

整体的运行流程为:用户通过访问gala-gopher框架进程提供的动态配置API,向gopher下发探针配置及启动命令;框架进程的probe-mng模块处理配置信息,并通过IPC机制向探针进程下发配置参数等信息,探针根据这些信息动态调整采集行为和监控范围等,最后将采集到的数据通过上报PIPE文件的方式传递给框架进程的ingress。ingress将数据结构化后缓存到imdb中,最终由egress统一输出。

2.1.1 gala-gopher探针扩展机制开发框架

  1. 在gopher探针框架中新增扩展的探针类型、配置api、探针子功能(采集子项)

  2. 定义meta文件,用于探针框架与探针之间的数据上报的协议约束

  3. 编写扩展探针程序

a) 与探针框架建立IPC通道,获取IPC消息,并使能配置

b) 完成自身探针的采集功能

c) 通过PIPE将采集到的数据上送给探针框架

  1. 如果探针涉及编译,需要提供build.sh脚本负责本探针的编译(python探针无需提供)

2.2 Sermant

Sermant是基于Java字节码增强技术的无代理的服务网格技术。其利用JavaAgent为宿主应用程序提供增强的服务治理功能,以解决大规模微服务场景中的服务治理问题。Sermant架构如下所示:

图2-5 Sermant架构

Sermant中JavaAgent主要包含两层功能,框架核心层和插件服务层。框架核心层提供Sermant的基本框架功能,以简化插件开发,包括心跳、数据传输、动态配置等。插件服务层为宿主应用提供实际的治理服务。

2.2.1 Sermant的动态插件机制

Sermant的动态插件机制是指Sermant可以在宿主服务运行过程中进行插件的安装和卸载。Sermant支持通过指令的方式进行插件的动态安装和卸载,如下图所示:

图2-6 Sermant动态插件机制

Sermant的动态插件机制主要基于Java Agent的agentmain模式实现,agentmain是Java Agent的入口方法,它可以在Java应用程序启动后动态地加载并运行Java Agent。和premain模式不同,agentmain模式不能通过添加启动参数的方式来连接agent和主程序,需要使用VirtualMachine的attach机制来将agent加载到主程序中, 使用者可以通过Attach参数下发指令来控制Sermant的安装和卸载生命周期。如下所示:

图2-7 Sermant动态插件机制实现方式

2.3 Sermant和gala-gopher的联合方案

Sermant和gala-gopher的联合方案主要是通过gala-gopher的探针扩展机制,将Sermant作为gala-gopher的探针的一部分来进行指标采集和上报,架构图如下所示:

图2-8 gala-gopher和Sermant联合方案的技术架构

从架构图可以看出,Sermant通过JavaAgent技术采集主程序的指标信息,并通过Java Agent技术来解决针对复杂协议eBPF无法进行协议解码、还原业务信息并进行metrics统计的问题。

在扩展探针中,Sermant Probe会接受到gala-gopher下发的配置,Sermant Probe接收到gala-gopher下发的配置之后,会通过IPC进程间通信机制和Sermant的动态安装机制将Sermant安装到Java主程序上,Sermant安装到Java主程序上之后会自动开始采集Java主程序的指标信息。当Sermant Probe接收到Sermant采集的指标信息之后,会根据Meta文件将采集的指标信息解析并通过上报PIPE文件的方式传递给框架进程的ingress。ingress将数据结构化后缓存到imdb中,最终由egress统一输出。

三、联合方案使用案例

以上方案为了引入Sermant对Java应用的Dubbo协议调用性能指标采集的能力,在gala-gopher的基础上扩展了Serment Probe探针。本案例中,分别部署了Dubbo协议的客户端和服务端,作为受观测对象,以此来验证本方案的效果。

3.1 操作指南

3.1.1 编译docker镜像

  1. 取包

Sermant取包地址:github.com/huaweicloud...

图3-1 Sermant取包位置

gopher编译出包或release取包指南:gitee.com/openeuler/g...

  1. 构建Docker容器镜像,并手动分发到所有K8S负载节点上。

a) 以上步骤得到Sermant软件包及gala-gopher的RPM包,将其放在同一个目录下。

b) 解压Sermant压缩包,将其中的agent整个目录放入当前目录的sermant/目录下。

c) 从gala-gopher源码中取出entrypoint.sh脚本,放到当前目录下,内容如下:

sql 复制代码
[root@master gopher-sermant-docker]# ll  
总用量 952  
-rw-r--r--. 1 root root   1121 12月 25 19:54 Dockerfile  
-rw-r--r--. 1 root root   2188 12月 25 19:54 entrypoint.sh  
-rw-r--r--. 1 root root 961605 12月 25 19:54 gala-gopher-1.0.2-3.x86_64.rpm  
drwxr-xr-x. 3 root root   4096 12月 25 19:54 sermant  

d) 编写Dockerfile,将上述两个软件打包到同一个Docker镜像中。

bash 复制代码
# 基础镜像使用OpenEuler-22.03-LTS-SP1  
FROM hub.oepkgs.net/openeuler/openeuler_x86_64:22.03-lts-sp1  
MAINTAINER GALA  
# 指定容器工作目录  
WORKDIR /gala-gopher  
COPY gala-gopher-1.0.2-3.x86_64.rpm .  
# 将当前宿主机目录(gopher的rpm包和Sermant软件包所在目录)下的所有内容都添加到容器的/gala-gopher/目录下  
ADD . /gala-gopher  
# 赋予entrypoint.sh脚本执行权限  
RUN chmod +x /entrypoint.sh  
# 在容器中安装gopher  
RUN yum localinstall -y gala-gopher-1.0.2-3.x86_64.rpm  
# 将Sermant拷贝到容器的/opt/目录下  
COPY sermant/ /opt/sermant  
# 启动容器进程入口,启动容器  
ENTRYPOINT [ "/entrypoint.sh" ]  
CMD [ "/usr/bin/gala-gopher" ]

e) 构建Docker镜像,并打上tag为gala-gopher:1.0.2。

  1. $ docker build -f Dockerfile -t gala-gopher:1.0.2 .

f) Docker镜像导出到指定目录(本例导出到/tmp/目录下),image_id为上一步构建好的docker镜像的id。

javascript 复制代码
 $ docker save {image_id}> /tmp/gala-gopher.docker.tar  

g) gDocker镜像导入所有K8S负载节点(Slave),并打上tag,image_id为镜像id。

shell 复制代码
 $ docker load -i /tmp/gala-gopher.docker.tar  
 $ docker tag {image_id} gala-gopher:1.0.2  
  1. 运行gopher + Sermant容器并打开四七层探针。
makefile 复制代码
# 使用docker run命令启动容器,以host模式运行,并开启特权模式  
$ docker run -d --name gala-gopher --privileged --pid=host --network=host -v /:/host -v /etc/localtime:/etc/localtime:ro -v /sys:/sys -v /usr/lib/debug:/usr/lib/debug -v /var/lib/docker:/var/lib/docker -e GOPHER_HOST_PATH=/host gala-gopher:1.0.2  
# 在本机节点上访问gopher的动态配置接口,以开启相关探针(baseinfo、tcpprobe、endpoint、l7probe、Sermant_probe)  
$ curl -X PUT http://localhost:9999/baseinfo --data-urlencode json='{"cmd":{"probe":["cpu","mem","nic","disk","net","fs","proc","host"]},"snoopers":{"proc_name":[{"comm":"java","cmdline":"dubbo"}]},"state":"running","params":{"report_period":60}}'  
$ curl -X PUT http://localhost:9999/tcp --data-urlencode json='{"cmd":{"probe":["tcp_abnormal","tcp_rtt","tcp_windows","tcp_rate","tcp_rtt","tcp_sockbuf","tcp_stats"]},"snoopers":{"proc_name":[{"comm":"java","cmdline":"dubbo"}]},"state":"running","params":{"report_period":60,"l7_protocol":["http"]}}'  
$ curl -X PUT http://localhost:9999/socket --data-urlencode json='{"cmd":{"probe":["tcp_socket"]},"snoopers":{"proc_name":[{"comm":"java","cmdline":"dubbo"}]},"state":"running","params":{"report_period":60,"l7_protocol":["http"]}}'  
$ curl -X PUT http://localhost:9999/sermant --data-urlencode json='{"cmd":{"probe":["l7_bytes_metrics","l7_rpc_metrics","l7_rpc_trace"]},"snoopers":{"proc_name":[{"name":"sermantTest","comm":"java","cmdline":"dubbo"}]},"state":"running","params":{"report_period":60,"l7_protocol":["http"]}}'
  1. 在Prometheus上配置定期拉取数据的任务。
yaml 复制代码
$ vim /etc/prometheus/prometheus.yml  
# 在scrape_configs下添加如下内容,8888端口为gopher的数据端口,可从该端口获取指标数据,{K8S-slave-1_ip}为slave-1节点的ip,slave-2同理  
	  - job_name: "k8s-Slave1-{K8S-slave-1_ip}"  
	    static_configs:  
	      - targets: ["{K8S-slave-1_ip}:8888"]  
	  - job_name: "k8s-Slave2-{K8S-slave-2_ip}"  
	    static_configs:  
	      - targets: ["{K8S-slave-1_ip}:8888"] 
  1. 在Grafana界面上配置好各个指标的panel以呈现。

3.2 验证实验

为了验证以上效果,本案例设计了Dubbo应用客户端到服务端的调用链,将其部署在K8S上,并开启一个定期打流的任务,定时给consumer-dubbo服务打流,触发调用链。本案例实验的调用链应用以及gala-gopher、Sermant的部署形态如下图所示:

图3-2 实验案例部署视图

在consumer-dubbo、provider-dubbo以及gala-gopher都已正常运行时,访问gala-gopher的动态配置接口下发观测配置,即告知gopher要开启Sermant probe来观测dubbo应用指标,此时可以观察到在gopher的docker容器内开启了Sermant probe探针子进程。随即Sermant probe会将sermant的Java Agent通过动态attach的方式注入到指定的dubbo应用进程中,从而Sermant的java-agent不停地采集应用调用产生的指标数据,并即时地上报给Sermant Probe。Sermant Probe再转而上报给gala-gopher主进程进行"临时归档"。

本案例采用prometheus+grafana的方式进行呈现,在prometheus配置了从每个gala-gopher实例处定时拉取指标数据的策略,并在Grafana界面上配置了Dubbo协议指标相关的panel进行呈现。

3.3 实验效果

实验效果如下图3-3 ~ 图3-6所示,在Grafana界面上呈现了当前实践案例中Dubbo客户端、服务端的时延、吞吐量、错误率等性能指标的变化曲线。

图3-3 Dubbo客户端/服务端请求/响应吞吐量、错误率变化曲线

图3-4 Dubbo客户端/服务端请求/响应吞吐量、错误率变化曲线

图3-5 TCP层观测指标变化曲线(时延、丢包、OOM、未处理字节数)

图3-6 TCP层观测指标变化曲线(重传、丢包、收发(错误)字节数)

3.4 实验总结

我们通过在gala-gopher的基础上扩展了Sermant_probe的能力,通过Sermant探针采集dubbo应用的性能指标,并反馈给gala-gopher进行统一上报。在prometheus+grafana的可视化框架下呈现dubbo协议调用以及网络tcp层面相关的指标变化,可以让用户在界面上直观地观测到四层(网络视角)/七层(应用视角)两个层面的性能指标变化,从而提升运维效率。

参考资料:

1\] eBPF官方文档: [ebpf.io/what-is-ebp...](https://link.juejin.cn?target= "") \[2\] gala项目社区官方文档:[gitee.com/openeuler/g...](https://link.juejin.cn?target=https%3A%2F%2Fgitee.com%2Fopeneuler%2Fgala-docs%2Ftree%2Fmaster "https://gitee.com/openeuler/gala-docs/tree/master") \[3\] gopher探针开发指南:[gitee.com/openeuler/g...](https://link.juejin.cn?target=https%3A%2F%2Fgitee.com%2Fopeneuler%2Fgala-gopher%2Fblob%2Fdev%2Fdoc%2Fhow_to_add_probe.md%23%25E5%25BC%2580%25E5%258F%2591%25E6%258E%25A2%25E9%2592%2588%25E5%258A%259F%25E8%2583%25BD "https://gitee.com/openeuler/gala-gopher/blob/dev/doc/how_to_add_probe.md#%E5%BC%80%E5%8F%91%E6%8E%A2%E9%92%88%E5%8A%9F%E8%83%BD") *** ** * ** *** Sermant作为专注于服务治理领域的字节码增强框架,致力于提供高性能、可扩展、易接入、功能丰富的服务治理体验,并会在每个版本中做好性能、功能、体验的看护,广泛欢迎大家的加入。 * *Sermant* *官网:* [*https://sermant.io*](https://link.juejin.cn?target= "") * *GitHub* *仓库地址:* [*https://github.com/huaweicloud/Sermant*](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fhuaweicloud%2FSermant "https://github.com/huaweicloud/Sermant") * *扫码加入 Sermant 社区交流群* ![](https://file.jishuzhan.net/article/1742452431289061378/7fbaf4ac1ff3bfd2158c420811fd3f80.webp)

相关推荐
努力也学不会java2 分钟前
【Java并发】深入理解synchronized
java·开发语言·人工智能·juc
TDengine (老段)2 分钟前
TDengine 数学函数 CEIL 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
LB211223 分钟前
Redis 黑马skyout
java·数据库·redis
豐儀麟阁贵30 分钟前
Java知识点储备
java·开发语言
hrrrrb36 分钟前
【Spring Security】Spring Security 密码编辑器
java·hive·spring
豐儀麟阁贵39 分钟前
2.3变量与常量
java·开发语言
胡斌附体40 分钟前
离线docker安装jupyter(python网页版编辑器)
python·docker·jupyter·image·tar·save
兮动人2 小时前
Eureka注册中心通用写法和配置
java·云原生·eureka
爱编程的小白L4 小时前
基于springboot志愿服务管理系统设计与实现(附源码)
java·spring boot·后端
聪明的笨猪猪6 小时前
Java Redis “持久化”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试