性能测试即服务-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;
}
}