透過 GitLab CI + JMeter 進行小規模 Load testing

本文將說明如何透過 GitLab CI + JMeter 實現小規模的 Load testing。

(本文同步發表於 Medium。)

操作步驟

先準備一個能夠操控 Container 的 GitLab Runner,gitlab.com 的使用者可直接用官方提供的 Shared runners。

站在巨人的肩膀上,我們要借用 justb4/docker-jmeter 這個優秀的 GitHub 專案,用它作為基底。

【補充說明】justb4/docker-jmeter 的作者已經寫好一個立即可用的 Test Plan 並搭配變數和幾個 script,也有易讀易懂的文件。因此如果你只是想要立即使用 JMeter 執行小規模的 Load testing,它只需要稍微微調就可以直接在 local 環境中拿來運用。

先將此專案 import 至 GitLab。由於是公開的 GitHub 專案,所以 Import project from 我們直接選擇 Repo by URL 並填入 GitHub 的 Git repository URL 即可順利 Import。

(等待 Import 動作執行完畢。)

由於 justb4/docker-jmeter 是設計用來直接拿來使用的,因此我們要稍作微調,讓它變成適合我們放在 GitLab CI 的模樣。調整的方向有 2 個:

  • 修改 Test Plan,讓我們可以用 Variables 動態調整更多的 Load testing 參數。
  • 讓 Variables 可以透過 GitLab CI 的各種方式輸入,方便以各種測試參數進行 Load testing。

決定好方向之後,開始實施客製作業。首先修改 Test Plan (.jmx),加入更多的 Variables。檔案位於 tests/trival/test-plan.jmx,修改以下幾個地方,增加更多的 Variables。

# Line: 18
## <stringProp name="LoopController.loops">2</stringProp>
## 加上 ${TARGET_LOOPS} 控制 JMeter 跑 Load testing 時,要 Loop 幾次。
<stringProp name="LoopController.loops">${TARGET_LOOPS}</stringProp>

# Line: 52
## <stringProp name="HTTPSampler.protocol"></stringProp>
## 加上 ${TARGET_PROTOCOL} 控制 JMeter 用正確的 Protocol (http 或 https) 連上目標網站。
<stringProp name="HTTPSampler.protocol">${TARGET_PROTOCOL}</stringProp>

# Line: 72
## <stringProp name="HTTPSampler.path">/</stringProp>
## 改成 ${TARGET_PATH},控制 JMeter 能用來測試指定的 URL。
<stringProp name="HTTPSampler.path">${TARGET_PATH}</stringProp>

# 因為我們增加了兩個 Variable,所以第 105 行 開始的 <Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> 裡面要補上更多 User Defined Variables 的設定。
# 從 Line: 137 開始增加以下內容

<elementProp name="TARGET_LOOPS" elementType="Argument">
  <stringProp name="Argument.name">TARGET_LOOPS</stringProp>
  <stringProp name="Argument.value">${__P(TARGET_LOOPS,1)}</stringProp>
  <stringProp name="Argument.desc">1 or 2 or more</stringProp>
  <stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="TARGET_PROTOCOL" elementType="Argument">
  <stringProp name="Argument.name">TARGET_PROTOCOL</stringProp>
  <stringProp name="Argument.value">${__P(TARGET_PROTOCOL,http)}</stringProp>
  <stringProp name="Argument.desc">http or https</stringProp>
  <stringProp name="Argument.metadata">=</stringProp>
</elementProp>

接著修改 test.sh 讓它能接收我們新增的 Variables。

# Line: 8 ~ 11 是原作者用來設定 JMeter 的地方,
# 這裡我們要改成可以接受我們要傳遞的 Variables
## export TARGET_HOST="www.map5.nl"
## export TARGET_PORT="80"
## export TARGET_PATH="/kaarten.html"
## export TARGET_KEYWORD="Kaartdiensten"
## 修改如下

export TARGET_HOST="$1"
export TARGET_PORT="$2"
export TARGET_PATH="$3"
export TARGET_KEYWORD="$4"
export TARGET_PROTOCOL="$5"
export TARGET_LOOPS="$6"
export THREADS="$7"

在執行 Test 時,也要帶入新增的 Variables,JMeter 要傳遞 Variables 要用 -J<Variable name>

# Line: 25 ~ 29
## ./run.sh -Dlog_level.jmeter=DEBUG \
##  -JTARGET_HOST=${TARGET_HOST} -JTARGET_PORT=${TARGET_PORT} \
##  -JTARGET_PATH=${TARGET_PATH} -JTARGET_KEYWORD=${TARGET_KEYWORD} \
##  -n -t ${T_DIR}/test-plan.jmx -l ${T_DIR}/test-plan.jtl -j ${T_DIR}/jmeter.log \
##  -e -o ${R_DIR}
## 修改如下

./run.sh -Dlog_level.jmeter=DEBUG \
    -JTARGET_HOST=${TARGET_HOST} -JTARGET_PORT=${TARGET_PORT} \
    -JTARGET_PATH=${TARGET_PATH} -JTARGET_KEYWORD=${TARGET_KEYWORD} \
    -JTARGET_LOOPS=${TARGET_LOOPS} -JTARGET_PROTOCOL=${TARGET_PROTOCOL} \
    -JTHREADS=${THREADS} \  
    -n -t ${T_DIR}/test-plan.jmx -l ${T_DIR}/test-plan.jtl -j ${T_DIR}/jmeter.log \
    -e -o ${R_DIR}

繼續修改 test.sh,要配合 GitLab CI 變更 JMeter 執行路徑及 Report 輸出的路徑。

# Line: 16 
## T_DIR=tests/trivial
## 修改如下
T_DIR=$CI_PROJECT_DIR/tests/trivial

# Line: 19
## R_DIR=${T_DIR}/report
## 修改如下
R_DIR=$CI_PROJECT_DIR/report

# Line: 25
## ./run.sh -Dlog_level.jmeter=DEBUG \
## 修改如下
sh $CI_PROJECT_DIR/run.sh -Dlog_level.jmeter=DEBUG \

接著修改會被 test.sh 呼叫的 run.sh,將其中的 sudo 移除。

## sudo docker stop ${NAME} > /dev/null 2>&1
## sudo docker rm ${NAME} > /dev/null 2>&1
## sudo docker run --name ${NAME} -i -v ${PWD}:${PWD} -w ${PWD} ${IMAGE} $@
## 修改如下
docker stop ${NAME} > /dev/null 2>&1
docker rm ${NAME} > /dev/null 2>&1
docker run --name ${NAME} -i -v ${PWD}:${PWD} -w ${PWD} ${IMAGE} $@

最後新增 .gitlab-ci.yml

# 透過 services: 提供可以運行 Container 的 docker。
services:
  - docker:19.03.12-dind

# 只需要一個 Stage
stages:
    - load-testing

# 唯一的 CI Job 就是我們的 JMeter
jmeter:
    # 需要 docker client,因此 image 也選擇 docker
    image: docker:19.03.12
    stage: load-testing
    script:
        - sh test.sh $TARGET_HOST $TARGET_PORT $TARGET_PATH $TARGET_KEYWORD $TARGET_PROTOCOL $TARGET_LOOPS $THREADS
    when: manual
    
    # 將 Report 儲存為 artifacts
    artifacts:
        paths:
        - report
        # Report 要保留多久可以自己設定
        expire_in: 1 week

到此我們的事前作業都完成了,可以開始用 GitLab CI 實施簡單的 Load testing。來到 GitLab 的 Pipeline 介面,點擊 Run Pipeline

接著輸入我們規劃的各個 Variables。

【小提醒】TARGET_LOOPSTHREADS 這兩個 Variables 會影響 JMeter 對目標 HOST 發出的 Thread 總次數。當 TARGET_LOOPS=1、THREADS=100,即是 1x100 = 100,若是 TARGET_LOOPS=2、THREADS=100,即是 2x100 = 200。由於 JMeter 會紀錄每一次 Thread 的成功與否及錯誤訊息,因此當總次數較高時,JMeter 就會消耗較多的記憶體來處理資料。所以假如你設定的總次數過高時,有可能會發生記憶體不足導致無法產出 Report 的狀況。

接著按下 Run Pipeline

當 CI Pipeline 產生後,再驅動 CI Job。

CI Job 跑完就可以去 Job artifactsBrowse 查看 Report。

直接瀏覽 index.html。

即可看到 JMeter 產生的 Report 嘍!

結語

GitLab CI 可以用來執行各式各樣的工作,JMeter Load testing 只是其中之一。本文只是透過單一 GitLab Runner 來執行 JMeter,因此能夠模擬的流量規模有限,如果你想要模擬大流量的真實情境,那麼建議使用具備 Distributed load generation 功能的 Load testing 工具。

【補充說明】JMeter 即具備 Distributed load generation 功能,有興趣者可以再進一步研究如何使用該功能,你會需要啟用多台 Server 並安裝好 JMeter 作為 Worker Node,接著由一個 Controller Node 發動 Distributed Testing,更多訊息可以查看 JMeter 的官方文件

本文的 DEMO Project 艦長有公開放在 gitlab.com,有興趣者可自行 Fork 參考,但為避免 Public Project 的 CI/CD 被人亂按,Pipeline 就關閉權限只讓我自己使用了。

參考資料

轉貼本文時禁止修改,禁止商業使用,並且必須註明來自「艦長,你有事嗎?」原創作者 Cheng Wei Chen,及附上原文連結。

工商服務

更多文章