研发效能 | Jacoco dump基于k8s的实现

server/2024/11/13 5:32:11/

问题描述

总所周知,jacoco的dump操作如果是使用server模式只需要使用以下命令就能获取到 exec 文件。

java -jar jacococli.jar dump --address 192.169.110.1 --port 6300 --destfile ./jacoco-demo.exec

如果是非 k8s 的集群,也只需要遍历执行这条命令即可,但是对于 k8s 服务的处理有有点力所不逮。

当我们使用 k8s 部署服务后,应用实例将会无状态话,用户不再去关心实例的 ip,端口等信息,service 自动会帮我们做负载均衡等操作,pod 不会暴露出 ip 和端口等信息给集群外部访问,这样对我们的 dump 操作带来了困难。

问题解决

针对上述问题,网络上也有一些解决方案,最常用的方式是切换 jacooc server 模式为 client 模式,这样当 jvm 关闭时就会将 dump 数据写入指定服务的文件里。虽然能从一定程度解决问题,但是这样生成报告的节奏就会被打断,就不能随时生成报告了,这里提供一种解决方式。

首先,我们还是采用 server 模式,在服务启动时注入

-javaagent:/jacoco/agent/jacocoagent.jar=includes=*,output=tcpserver,port=6300,address=0.0.0.0

然后,当我们想要去获取 exec 文件时,可以在 pod 中执行

java -jar /jacoco/agent/jacococli.jar dump --address 127.0.0.1 --port 36300 --destfile /app/jacoco.exec

然后我们从 pod 读取文件/app/jacoco.exec 写入我们的报告生成服务即可

怎么去 pod 内部执行 shell 命令,各种手动都有,这里我们 java 基于一个 k8s 的 sdk 工具 fabric8 实现

  1. public List<String> dumpK8sExecData(K8sDumpParam k8sDumpParam) {

  2.    try {

  3.        String dumpCmd = "JAVA_TOOL_OPTIONS=\"\" java -jar /jacoco/agent/jacococli.jar dump --address 127.0.0.1 --port 6300 --destfile /app/jacoco.exec";

  4.        if (k8sDumpParam.getResetFlag()) {

  5.            dumpCmd += " --reset";

  6.        }

  7.        String[] cmd = {"sh", "-c", dumpCmd};

  8.        K8sCmdParam k8sCmdParam = OrikaMapperUtils.map(k8sDumpParam, K8sCmdParam.class);

  9.        k8sCmdParam.setCmd(cmd);

  10.        k8sCmdParam.setExecutor(executor);

  11.        return executeCmd(k8sCmdParam);

  12.    } catch (Exception e) {

  13.        log.error("dump操作失败,失败原因:", e);

  14.        throw new BizException(BizCode.JACOCO_DUMP_ERROR);

  15.    }

 
  1. public List<String> executeCmd(K8sCmdParam k8sCmdParam) {

  2.    KubernetesClient client = K8sClientProxy.getOrCreateClient(k8sCmdParam.getKubeConfig());

  3.    if (client == null || k8sCmdParam.getNameSpace() == null || CollectionUtil.isEmpty(k8sCmdParam.getPodList())) {

  4.        throw new BizException(BizCode.JACOCO_DUMP_PARAM_ERROR);

  5.    }

  6.    List<CompletableFuture<String>> priceFuture = k8sCmdParam.getPodList().stream().map(pod ->

  7.            CompletableFuture.supplyAsync(() -> {

  8.                String filename = "";

  9.                // 异步操作

  10.                dumpFileService.podExec(pod, k8sCmdParam.getCmd(), k8sCmdParam.getNameSpace(), client);

  11.                try {

  12.                    //中间等待文件写入一段时间,再去尝试获取

  13.                    Thread.sleep(1000);

  14.                    filename = dumpFileService.downloadFile(pod, k8sCmdParam.getNameSpace(), client, k8sCmdParam.getTaskWorkspace());

  15.                } catch (Exception e) {

  16.                    throw new BizException(BizCode.DUMP_FILE_GET_ERROR);

  17.                }

  18.                return filename;

  19.            }, k8sCmdParam.getExecutor())

  20.    ).collect(Collectors.toList());

  21.    // 等待所有异步操作完成,多个pod并发执行以上操作,减少dump的时间消耗

  22.    CompletableFuture.allOf(priceFuture.toArray(new CompletableFuture[0])).join();

  23.    return priceFuture.stream().map(CompletableFuture::join).filter(Objects::nonNull).collect(Collectors.toList());

  24. }

  1. /**

  2. * 执行单个pod命令

  3. *

  4. * @param podName   pod名字

  5. * @param cmd       cmd

  6. * @param namespace 名称空间

  7. * @param client    客户端

  8. */

  9. public void podExec(String podName, String[] cmd, String namespace, KubernetesClient client) {

  10.    try (ExecWatch watch = client.pods().inNamespace(namespace)

  11.            .withName(podName)

  12.            .redirectingOutput()

  13.            .exec(cmd)) {

  14.    }

  15. }

  16. /**

  17. * 获取文件

  18. *

  19. * @param podName   pod名字

  20. * @param namespace 名称空间

  21. * @param client    客户端

  22. * @param workspace 工作空间

  23. */

  24. @Retryable(value = {IOException.class}, backoff = @Backoff(delay = 1000))

  25. public String downloadFile(String podName, String namespace, KubernetesClient client, String workspace) throws IOException {

  26.    try (InputStream is = client.pods().inNamespace(namespace)

  27.            .withName(podName)

  28.            .file("/app/jacoco.exec").read()) {

  29.        String execPath = workspace + "/exec/" + podName + "/jacoco.exec";

  30.        FileUtil.writeFromStream(is, execPath);

  31.        return execPath;

  32.    }

  33. }

这里有两个细节点

  • Thread.sleep(1000) 操作,是因为执行 dump 命令后,我们无法判定 exec 文件什么时候能在本地生成完成,立马获取就会抛出 IO 异常,等待一定时间后即可获取到文件,这个时间的等待只是第一层保障,具体等待时间,可以视自己的 dump 文件大小调整,当然哪怕没调整也没有关系

  • @Retryable(value = {IOException.class}, backoff = @Backoff(delay = 1000)) 这段代码是使用了 spring 的一个重试框架,当文件获取失败后,默认会重试 3 次,每次重试间隔 1 秒,这是获取文件的第二步保障,用户可以通过调整重试次数来减少文件获取失败风险

这里说明下 spring Retryable 必须在 public 方法上,而且调用它的方法不能和他处于同一个类,否则不会生效重试。

通过以上手段就可以主动去 dump 出想要的数据,当然更好的方式是判断 exec 文件是否存在,或者还在写入中,等写入完成再去获取文件,这个操作也可以通过 shell 去完成,本文只是提供一种实现方案。

行动吧,在路上总比一直观望的要好,未来的你肯定会感 谢现在拼搏的自己!如果想学习提升找不到资料,没人答疑解惑时,请及时加入扣群: 320231853,里面有各种软件测试+开发资料和技术可以一起交流学习哦。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!


http://www.ppmy.cn/server/37991.html

相关文章

Python | Leetcode Python题解之第74题搜索二维矩阵

题目&#xff1a; 题解&#xff1a; class Solution:def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:row,col len(matrix),len(matrix[0])row_l,row_r 0,row-1while row_l < row_r:m (row_lrow_r)//2if target < matrix[m][0]:row_r m-1…

二、双fifo流水线操作——verilog练习与设计

文章目录 一、案例分析二、fifo_ctrl模块设计2.1 波形设计&#xff1a;2.2 代码实现2.2.1 fifo_ctrl2.2.2 顶层文件top_fifo_ctrl&#xff08;rx和tx模块省略&#xff09;2.2.3 仿真文件tb_fifo_ctrl 2.3波形仿真 一、案例分析 案例要求&#xff1a;写一个 fifo 控制器&#x…

我独自升级崛起怎么下载 我独自升级崛起下载教程来了

《我独自升级&#xff1a;崛起》作为一款炙手可热的游戏&#xff0c;其非凡的品质迅速聚拢了大量玩家的目光&#xff0c;就如同磁铁吸引铁屑一般&#xff0c;展现了优质游戏所固有的强大吸引力。在这款游戏中&#xff0c;每位玩家都能化身成为拥有超凡能力的英雄&#xff0c;体…

条码技术如何应用于库存控制?

有效的库存控制对企业至关重要。它有助于避免仓库内库存过多、短缺或其他代价高昂的问题或问题&#xff0c;同时简化流程并改进预测。条形码技术在确保稳健的库存控制方面发挥着至关重要的作用。 点击下载Dynamsoft最新版https://www.evget.com/product/3691/download 了解库…

嵌入式系统的软件构成

嵌入式系统的软件构成包括以下几个方面&#xff1a; 底层硬件驱动程序&#xff1a;嵌入式系统需要与硬件设备进行交互&#xff0c;底层硬件驱动程序负责管理和控制硬件设备&#xff0c;提供操作接口。 实时操作系统&#xff08;RTOS&#xff09;&#xff1a;嵌入式系统通常需要…

数据结构学不会?数据结构可视化网站来了

目录 前言 图码网站 算法可视化 算法编辑器 数据结构全书 数据结构课程 总结 前言 数据结构与算法在计算机的学习中应该是许多小白最头疼的东西&#xff0c;明明听的时候那么容易&#xff0c;为什么转换成代码就那么抽象呢&#xff1f; 有没有一个网站可以数据结构与算…

封装Springboot基础框架功能-03

在些模块中汇总了一些web开发常用的配置和功能。 模块源码结构 Restful API常用定义 QueryParam请求参数 Data public class QueryParam {private String key;private String value; }RestfulController实现 RestfulController.java&#xff0c;主要汇总一些常用的restful的…

matlab中syms用法

syms x; f sin(x); f是syms类型&#xff1a; 这个代码将创建一个名为 x 的符号变量。现在&#xff0c;可以使用 x 进行代数运算&#xff0c;比如求导、积分、解方程等。 用法&#xff1a; 1.求导&#xff1a; syms x f x^2 2*x 1; df diff(f, x);2阶求导&#xff1a; df…