前言
現在各種業務都追求上雲,通俗的講,“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;
}
}