透過 GitLab CI + Drill 進行小規模 Load Testing

在 2021 年四月的文章中,我分享了透過 GitLab CI + JMeter 進行小規模 Load Testing;而在 2021 年七月的文章中,我則是試用了 Drill 這個新興的 Load Testing 工具。

承襲這兩次的經驗,在本文我們就以 GitLab CI + Drill,試一試「如何透過 GitLab CI + Drill 進行小規模 Load Testing」。

(本文同步發表於 Medium。)

操作步驟

首先我們要準備合適的 GitLab CI 環境,老樣子會需要一個可以操控 Container 的 GitLab Runner。如果你只是打算先試玩看看,那最簡單的方式就是使用 gitlab.com 以及官方提供的 Shared runners。

【小提醒】再次提醒大家 gitlab.com 的 Free plan 現在只有 400 CI/CD minutes per month。而且由於一直都有人在濫用這些免費資源,因此官方也有在評估該如何規範濫用的狀況。假如你真的想要由 GitLab CI 觸發 Load Testing,最好還是使用自己架設的 GitLab Runner 喔!

準備 Docker image - drill

接著準備合適的 Docker image。這邊一樣偷懶,就不追求容器 size 最小化,用最直白的方式建立一個可以在 GitLab CI 中使用的 Docker image - drill。

# Dockerfile

FROM rust:1.53

RUN apt-get update && \
    apt-get install -y curl libssl-dev pkg-config && \
    rm -rf /var/lib/apt/lists/*

RUN cargo install drill

建立好的 Docker image,我就直接存放在 Docker Hub,供後面在 GitLab CI 使用。

建立 CI Pipeline

緊接著我們直接開一個全新的 GitLab Project(Repository),並撰寫 .gitlab-ci.yml

# .gitlab-ci.yml

# Job name 叫做 drill
drill:
  # 指定能操控 docker 的 Runner
  tags:
    - "docker"
  
  # 使用的 Docker image 是我自己打包好的 drill 0.7.2
  image: theqwan/drill:base
  
  # 要執行的動作只有一項,就是根據 benchmark.yml 去跑 Load Testing
  script:
    - "drill -b benchmark.yml --stats"

立刻察看 CI Pipeline 的結果,如預料之中 Failed,因為我們還沒撰寫 benchmark.yml

benchmark.yml 該如何撰寫,可以參閱我先前的文章,或者參閱 Drill 官方文件,這裡就直接上範例,不詳細解釋。

# 要檢測的 BASE URL
base: 'https://gitlab-book.tw'

# Load Testing 的強度
iterations: 10
concurrency: 2
rampup: 1

# 要對哪些 URL 進行測試
plan:
- name: home
  request:
    url: /

- name: errata
  request:
    url: /errata.html

這次就有順利執行。

接著我們再多做一點變化,將 Drill 執行後的結果儲存至 GitLab 的 Job artifacts,方便我們調閱。

【補充】由於 Drill 的參數 --report 雖然會輸出 report,但提供的資訊有點少,因此我選擇直接將 Drill 輸出的執行結果儲存為文字檔,作為我想要的 Report。

修改 .gitlab-ci.yml,如下。

drill:
  tags:
    - "docker"
  image: theqwan/drill:base

  script:
    # 將執行結果,儲存為 result.txt
    - "drill -b benchmark.yml --stats > result.txt"
    # 也將執行結果直接印出在 Job Log 中
    - cat result.txt
  # 將 result.txt ,儲存至 GitLab Job artifacts 中。
  artifacts:
    paths:
      - result.txt

如圖,result.txt 有確實被儲存至 Job artifacts。

如圖,後續便可以直接在 Pipeline 介面那邊下載每次 CI Job 執行後產生的 Report。

提示 Failed requests

讓我們再進一步的修改 CI Job,這次我希望能達成一個目標,即是「當 Drill 執行 Load Testing 時,如果有遇到 Failed requests,即送出 Notification」。

正常來說,被實施 Load Testing 的目標網站,如果能順利支撐負載量,應該 Drill 都會接收到正常 response 並記錄為 Successful requests;反之,如果目標網站無法負荷,則可能會回傳 4xx、5xx 或其他反應,而 Drill 則會將其記錄為 Failed requests。

因此,我希望能在 CI Job 中建立一個檢查,假如某次執行 Drill 出現了 Failed requests 不等於 0 的狀況,就要送出 Notification。因爲當出現 Failed requests 時,即可能反應出本次實施 Load Testing 之負載量即是目標網站的瓶頸點,需要進一步了解狀況。

了解目標之後,來構思如何實現它。這裡我計畫的做法如下:

  1. 透過 shell script 檢查以 Drill 產出的 report.txt 內容,確認 Failed requests 是否為 0。
  2. 續上,該 shell script 如發現 Failed requests 不為 0,就送出 exit 1
  3. GitLab CI 接收到 exit 1 就會中斷 CI Job,並標示為 Failed
  4. 而在 GitLab CI 的 Settings 中,我們可以設定 Failed 的 CI Job 要送出 Notification 通知使用者。

確認完做法,開始實行,第一步是撰寫 shell script,但其實我們不用真的額外撰寫一個 check.sh,可以直接將其撰寫在 .gitlab-ci.yml 當中。

  # 修改 .gitlab-ci.yml 中 script: 的內容
  script:
    - "drill -b benchmark.yml --stats > result.txt"
    - cat result.txt
    
    # 可如往常使用 Command line 的習慣,透過 | 以 pipe 的方式處理字串
    - FAILED_REQUESTS=$(cat result.txt | grep Failed | tail -1 | awk '{print $3}')

    # 驗證一下變數 $FAILED_REQUESTS 的值
    - echo $FAILED_REQUESTS

    # if else 判斷式,如果 Failed Request 不為 0 就送出 exit 1
    - >
      if [ "$FAILED_REQUESTS" != "0" ]; then
        exit 1;
      else
        echo "Failed Request == 0";
      fi      

下面是一切正常的執行結果,Failed Request 為 0,因此 Job succeeded

如果 Failed Request 不為 0 時,則會是 ERROR: Job failed: exit code 1

眼尖的你可能會發現,上面的 Job failed 會帶來一個問題,那就是會導致 CI Job 被中斷,因此 Drill 產出的 report.txt 便沒有上傳至 Job artifacts。為了解決這個問題,我們再次調整 .gitlab-ci.yml,將它切分為兩個 CI Job。

stages:
  - test
  - notification

drill:
  stage: test
  tags:
    - "docker"
  image: theqwan/drill:base
  script:
    - "drill -b benchmark.yml --stats > result.txt"
    - cat result.txt
  artifacts:
    paths:
      - result.txt
  
check failed:
  stage: notification
  tags:
    - "docker"
  variables:
    GIT_STRATEGY: none
  dependencies:
    - drill
  script:
    - FAILED_REQUESTS=$(cat result.txt | grep Failed | tail -1 | awk '{print $3}')
    - >
      if [ "$FAILED_REQUESTS" != "0" ]; then
        echo "Failed Request = $FAILED_REQUESTS";
        cat result.txt
        exit 1;
      else
        echo "Failed Request = 0";
      fi      

如上面的 .gitlab-ci.yml,我將它切分為兩個 Stage,各有一個 CI Job,一個負責 test 並將 result.txt 上傳至 Job Artifacts,另一個則負責確認是否有 Failed request。

【補充】在上面的 CI Job - check failed 中,我有特別加上了 variables:dependencies: 這兩個 GitLab CI 參數,這是因為對這項 Job 而言,其實我們並不需要存放在 Project (Repository) 內的其他檔案,我們只需要前一個 Job - test 產出的 result.txt。在 variables: 設置 GIT_STRATEGY: none,會讓 GitLab CI 略過 Git 的相關動作;而在 dependencies: 設置 - drill 則是告知 GitLab CI 要取得該 Job 的 Artifacts。

(如圖,可以看見 CI Log 中有 skip Git 與 Download artifacts 的動作。)

既然可以成功讓 CI Job 產生出 Job failed 的狀態,最後只要正確設置 Project 的 Notifications 即可。

(你可以設定只有 Failed 的時候才送出通知。)

(以 Slack Notification 為例,會直接送出包含 Pipeline 超連結的訊息,方便你快速前往查看。)

【補充】好吧,老實說上面這種「讓 CI Job Failed 然後觸發 Notification」的方式不一定是最好的做法,但我覺得這是一個比較省事的做法,因為只需要利用 GitLab 既有的 CI 功能即可。

更彈性的進行 Load Testing

最後,再做另一項調整,由於要實施 Load Testing 的目標及強度,都需要撰寫在 benchmark.yml 中,這導致如果想要變更施測的內容時,會需要反覆的修改它,因此讓我們想個方法解決這個問題。

這裡一樣用一個老技巧,運用 GitLab CI 的 Variables 加上 Run Pipeline 時可以動態變更 Variables 的方式處理。

因為 Drill 在執行時,也支援直接取用 ENV,因此先將 benchmark.yml 修改如下。

# 想要動態調整的地方,通通換成變數。
base: '{{ BASE_DOMAIN }}'
iterations: '{{ ITERATIONS }}'
concurrency: '{{ CONCURRENCY }}'
rampup: '{{ RAMPUP }}'

plan:
- name: "{{ URL }}"
  request:
    url: "{{ URL }}"

接著修改 .gitlab-ci.yml,為變數們設定預設值。

variables:
  BASE_DOMAIN: "https://gitlab-book.tw"
  ITERATIONS: 1
  CONCURRENCY: 1
  RAMPUP: 1
  URL: "/"

如此一來,在沒有輸入變數的狀況下,就會用預設值來執行 Drill。

後續就可以透過 GitLab 的 Run pipeline 來進行 Load Testing。

(在 CI/CD > Pipelines 介面右上角,有 Run pipeline 的按鈕。)

(接著可以輸入這次 Pipeline 的變數。)

(根據輸入的變數內容,進行 Load Testing。)

結語

延續上一次介紹用 GitLab CI + JMeter 進行小規模 Load Testing 的想法,本文我們換了一個工具,變成以 GitLab CI + Drill 進行小規模 Load Testing。與 JMeter 比較起來,Drill 真的是一個讓 YAML 工程師們可以快速上手的工具,再次推薦給大家。

另外要提醒大家的是,本文僅是一個簡單的 DEMO,畢竟只靠單一個 Runner 能模擬的最大負載量是有限的,如果你需要進行真正的大流量、高負載的 Load Testing,那麼你還需要更多的準備才行,未來如有機會我在針對這主題另外撰寫文章分享。

GitLab CI 是一項容易上手,可發揮巧思用來執行各種自動化任務的工具,你是否也有自己的獨門秘招,讓 GitLab CI 幫你執行各式各樣的自動化工作呢?歡迎與我或 DevOps Taiwan Communitry 分享你的經驗喔!

同樣的本文也有一個 DEMO Project,已公開放在 gitlab.com,有興趣者可自行 Fork 參考,但一樣為避免 Public Project 的 CI/CD 被人亂按,Pipeline 已關閉權限避免被人隨意誤觸。

工商服務

如果你覺得艦長寫的文章對你有產生幫助,歡迎抖內(Donate)艦長,讓艦長可以將原本用來養家活口的餘力多分一些投入在社群分享上。也歡迎大家幫忙支持一下艦長的著作《和艦長一起 30 天玩轉 GitLab》,買書不只能抖內艦長,還可以一併支持出版社與圖書通路商,一次抖內一舉數得!

【補充】《和艦長一起 30 天玩轉 GitLab》除了介紹 GitLab 的功能及使用方法,也包含 GitLab 之外,純粹是 CI/CD/DevOps 觀念的內容,雖然因為 GitLab 版本更新大爆發,導致書中部分的功能介面截圖已經失準,但本書依然內含許多值得讀者參考的內容。

參考資料

更多文章