性能测试即服务-docker部署jmeter及.netcore应用

前言

  现在各种业务都追求上云,通俗的讲,“XX即服务”,作为一名专职的性能测试调优人员的我,由于会点三脚猫的开发功夫,“性能测试即服务”这种开发大任就落到我头上了,先做一个能完成核心压测功能的基础版。

  本着不再重复造轮子的思想,充分利用已有的优秀软件,性能测试要想上云的话,当然封装jmeter是不二选择,但是大佬的硬性要求是,所有要提供的交付物,必须是docker镜像。

  jmeter貌似没有官方镜像啊。。有好多个人做的,也没啥详细介绍,算了,还是自己做吧。

  最终,需要实现前端传入用户输入的压测相关配置,后端jmeter docker镜像+.netcore测试服务镜像,测试服务镜像调用jmeter镜像进行测试,然后分析生成的结果,生成报告。

  本文只涉及后端,如果你是用的Jenkins镜像+jmeter镜像做的持续集成相关,可能也会遇见跟我一样的jmeter容器调用问题。

过程及一些坑

  目前后端用.netcore开发的性能测试服务基本已经完成了,可以实现前端传入一个规定格式的json,后端解析,自动在某个路径下生成jmx脚本。

  现在需要的就是,在我们的测试服务容器里,调用另一个容器里的jmeter,对当前路径下的脚本测试,生成结果。

创建一个jmeter docker镜像

  首先下载linux版本的jmeter,推荐下载binary版的,拷贝完直接可用,source版部署麻烦不说,还可能有找不到jar的坑。

  然后就是Dockerfile,由于容器只能读取容器内的文件,脚本肯定是在容器外的,所以需要把jmeter相关的脚本、结果、报告等路径挂载出来,这个根据自己的实际需求就行。

  Dockerfile:

FROM java:8
ENV http_proxy ""
ENV https_proxy ""
RUN mkdir /jmeterspace
RUN mkdir -p /jmeterspace/test
RUN mkdir -p /jmeterspace/test/input/jmx
RUN mkdir -p /jmeterspace/test/input/testdata
RUN mkdir -p /jmeterspace/test/report/html
RUN mkdir -p /jmeterspace/test/report/jtl
RUN mkdir -p /jmeterspace/test/report/outputdata
RUN chmod -R 777 /jmeterspace
ENV JMETER_VERSION=5.1.1
ENV JMETER_HOME=/jmeterspace/apache-jmeter-${JMETER_VERSION}
ENV JMETER_PATH=${JMETER_HOME}/bin:${PATH}
ENV PATH=${JMETER_HOME}/bin:${PATH}
COPY apache-jmeter-5.1.1.tgz /jmeterspace
RUN cd /jmeterspace\
&& tar xvf apache-jmeter-5.1.1.tgz \
&& rm apache-jmeter-5.1.1.tgz

  分的有点细,其实可能用不了这么多路径。

  然后 docker build -t invokerr/jmeter . 生成镜像。接下来是运行了,把路径挂载出来:

docker run --name="jmeter" -v /jmeterspace/test/input/jmx:/jmeterspace/test/input/jmx \
-v /jmeterspace/test/input/testdata:/jmeterspace/test/input/testdata \
-v /jmeterspace/test/report/html:/jmeterspace/test/report/html \
-v /jmeterspace/test/report/jtl:/jmeterspace/test/report/jtl \
-v /jmeterspace/test/report/outputputdata:/jmeterspace/test/report/outputdata \
-v /etc/localtime:/etc/localtime \
-it -d invokerr/jmeter

  运行起来后,docker exec -it jmeter /bin/bash 进入容器,jmeter -v 如果出来jmeter相关的信息,就表示ok了。当然如果需要运行脚本的话,目前是需要像这样进入容器后用指令运行的。

  至此,jmeter镜像成功。

创建一个.net core docker镜像

  这个网上有好多说明,而且官方也提供了docker支持。但是微软默认生成的Dockerfile是拷贝源码-编译-发布,感觉有点多余,而且我试过,这么做有时候会报nuget路径错误,所以还是发布出来自己搞吧。

  首先发布工程,选择发布成linux64版本,基于框架,然后咱们就得到了基于.net core运行时环境的linux版本,在安装.net core sdk的linux上可直接运行。

  接下来,基于.net core runtime镜像,制作我们自己的镜像,为了保证我们生成的脚本可以让jmeter容器读取到,这里挂载到了与jmeter镜像相同的路径:

FROM microsoft/dotnet:2.1-aspnetcore-runtime
RUN mkdir -p /jmeterspace
RUN mkdir -p /jmeterspace/test
RUN mkdir -p /jmeterspace/test/input/jmx
RUN mkdir -p /jmeterspace/test/input/testdata
RUN mkdir -p /jmeterspace/test/report/html
RUN mkdir -p /jmeterspace/test/report/jtl
RUN mkdir -p /jmeterspace/test/report/outputdata
RUN chmod -R 777 /jmeterspace
WORKDIR /app
COPY ApiPerftestService /app/
EXPOSE 5000
WORKDIR /app
ENTRYPOINT ["dotnet", "ApiPerftestService.dll"]

  然后 docker build -t invokerr/apiperftestservice . 生成镜像。

  运行:

docker run --name="perftest" -p 5000:5000 -v /jmeterspace:/jmeterspace\
-v /jmeterspace/test:/jmeterspace/test \
-v /jmeterspace/test/input/jmx:/jmeterspace/test/input/jmx \
-v /jmeterspace/test/input/testdata:/jmeterspace/test/input/testdata \
-v /jmeterspace/test/report/html:/jmeterspace/test/report/html \
-v /jmeterspace/test/report/jtl:/jmeterspace/test/report/jtl \
-v /jmeterspace/test/report/outputdata:/jmeterspace/test/report/outputdata \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \
-v /usr/lib64/libltdl.so.7:/usr/lib/x86_64-linux-gnu/libltdl.so.7 \
-v /etc/localtime:/etc/localtime \
-d invokerr/apiperftestservice

  注意几行加粗的,下文会提及。目前这个容器已经成功运行了,并且可以完成在指定路径下生成脚本。

容器互相调用及一些问题

  接下来,就是容器互相调用了,测试服务容器需要调用jmeter容器实现压测。

  要想完成调用,最简单的办法,就是docker in docker的方式,即在某个容器里,可以访问宿主机的其他容器。

  这就需要在启动容器的时候,把宿主机的docker及docker.sock文件挂载进容器,也就是

-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \

  这两句,路径根据自己的实际情况,一般linux文件就是这俩路径。

坑1:找不到文件libltdl.so.7。

  不知道为啥会有这种问题,简单粗暴的方式就是缺什么补什么,从宿主机挂载,-v /usr/lib64/libltdl.so.7:/usr/lib/x86_64-linux-gnu/libltdl.so.7

坑2:容器能访问了,怎么调用另一个容器

  如果是能docker run方式调用还好,现在是需要进入容器,然后调用,如果用docker exec&&jmeter&&exit这种方式,连续发三个命令,你会发现这三个指令都是针对当前容器的,是没法用第一个指令进入jmeter容器,然后在jmeter的容器中执行第二个jmeter指令的。

  既然这样,得用一个指令一步到位啊,进入容器后直接执行指令,docker是有相关语法的,在/bin/bash后直接跟命令:

  docker exec -it jmeter /bin/bash jmeter -n -t {filePath.scriptPath}{Path.DirectorySeparatorChar}script.jmx -l {filePath.resultPath}{Path.DirectorySeparatorChar}result.jtl

坑3:找不到ApacheJmeter.jar

  用上述命令执行的时候,报了个找不到ApacheJmeter.jar的问题,真是奇怪,在jmeter容器中执行压测命令是没问题的,不知道为啥会有这个问题,猜测可能是执行的时候,jmeter path相关的锅。

  灵机一动,我直接写死执行文件不就行了,于是命令就成了这样:

  docker exec -it jmeter /bin/bash /jmeterspace/apache-jmeter-5.1.1/bin/jmeter -n -t {filePath.scriptPath}{Path.DirectorySeparatorChar}script.jmx -l {filePath.resultPath}{Path.DirectorySeparatorChar}result.jtl

  直接把jmeter执行路径写上,果然解决了问题。

坑4:the input device is not a TTY

  为了方便,以上的执行我都是在linux环境下直接用dotnet启动的测试服务,然后测试服务里调用的jmeter命令,并没有将测试服务打包到容器里调用,我认为应该是一样的。

  结果打脸了,同样的应用程序,在linux下用dotnet启动后直接调用jmeter指令没问题,但是打包成镜像,在容器里调用,就报了the input device is not a TTY。

  搜了下,说是-it 的问题,去掉 -it ,把指令改成:

  docker exec jmeter /bin/bash /jmeterspace/apache-jmeter-5.1.1/bin/jmeter -n -t {filePath.scriptPath}{Path.DirectorySeparatorChar}script.jmx -l {filePath.resultPath}{Path.DirectorySeparatorChar}result.jtl

  果然解决了问题。

  最后,附上.netcore 中调用jmeter容器的代码吧,执行压测并读取控制台log:

public static string RunJmeterInDocker(FilePath filePath)
{
  string result = $"result path:{filePath.resultPath},log:";
  //创建一个ProcessStartInfo对象 使用系统shell 指定命令和参数 设置标准输出
  var psi = new ProcessStartInfo("docker", $"exec jmeter /bin/bash /jmeterspace/apache-jmeter-5.1.1/bin/jmeter -n -t {filePath.scriptPath}      {Path.DirectorySeparatorChar}script.jmx -l {filePath.resultPath}{Path.DirectorySeparatorChar}result.jtl") { RedirectStandardOutput = true };
  //启动
  try
  {
    var proc = Process.Start(psi);
    if (proc == null)
    {
      return "Process start failed!";
    }
    else
    {
    //开始读取
      using (var sr = proc.StandardOutput)
      {
        while (!proc.HasExited)
        {
          result += sr.ReadLine();
        }  
      }
    return result;
    }
  }
  catch (Exception ee)
  {
    return ee.Message;
  }
}

posted @ 2019-07-11 18:24  孙小侠  阅读(1478)  评论(0编辑  收藏  举报