Posted at

Pipeline PluginでGatlingをスケールアウト

More than 1 year has passed since last update.


TL;DR


  1. クライアント側の負荷はparallelで分散させる

  2. テスト結果はstash / unstashで集約する


  3. gatling -reports-onlyでテスト結果を統合する


  4. publishHTMLでレポートをJenkins上で見られるようにする


  5. archiveArtifactsで後からダウンロード可能にする


Jenkinsfile


Jenkinsfile

// Get Parameters via env

def parallelism = env.LT_VAR_parallelism.toInteger()
def nodeLabel = env.LT_VAR_nodeLabel
def clients = [:]

for (int i = 0; i < parallelism; i++) {
def index = i // bind current i

// Define load test and store them in the clients
clients["client${i}"] = {
node(nodeLabel) {
def mvnHome

stage('Preparation') {
// Cleanup Workspace
step([$class: 'WsCleanup'])

// Load Test Script
git changelog: false, credentialsId: 'xxx', poll: false, url: 'URL_OF_TEST_SCRIPT'

// Get Maven Home
mvnHome = tool 'maven'
}

stage('Load Test') {
sh "'${mvnHome}/bin/mvn' gatling:execute -Dgatling-noReports=true -Dgatling.simulationClass=SIMULATION_CLASS_NAME"
}

stage('Stash Simulation log') {
sh "mkdir dist"

// Rename simulation.log to simulation${index}.log to discriminate
sh "mv target/gatling/**/simulation.log dist/simulation${index}.log"

// Stash simulation log as log${index}
stash includes: "dist/simulation${index}.log", name: "log${index}"
}
}
}
}

// Execute stored jobs in parallel
parallel clients

node('master') {
def workspace = pwd()

stage('Preparation for Generating Report') {
// Cleanup Workspace
step([$class: 'WsCleanup'])

// Get Gatling Home
gatlingHome = tool 'gatling'

// Unstash logs
for (int i = 0; i < parallelism; i++) { unstash "log${i}" }
}

stage('Generate Report') {
// Generate report with gatling
sh "${gatlingHome}/bin/gatling.sh --reports-only ${workspace}/dist"

// simulation logs are no more needed
sh 'rm -v dist/simulation*.log'
}

stage('Publish Report') {
// Publish report
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: false,
keepAll: false,
reportDir: 'dist',
reportFiles: 'index.html',
reportName: 'Load Test Report'])

// Archive also
archiveArtifacts artifacts: 'dist/**/*', onlyIfSuccessful: true
}
}



なぜPipeline Scriptを使ったのか?

Gatlingは負荷試験のクライアントとして便利だけれど、大きな負荷をかけたい場合に複数のクライアントをハンドリングするが辛い。

同時実行の排他制御とか面倒。。

それに、せっかくレポートを吐けるのであれば直ぐに見たいよね。

そこで、Pipeline Scriptですよ。


戦略


スケーラビリティ

GatlingをインストールしたサーバをJenkinsのSlaveとすることで、いくらでもスケール可能。

何並列で負荷試験を実行するかは、parallelに渡す配列の数で制御しています。

def parallelism = env.LT_VAR_parallelism.toInteger()

上記の行は、並列数をLT_VAR_parallelismという名前のパラメータ経由で受け取っている例です。

取ってきた直後はStringですので、Integerに型変換します。


排他制御

Jenkins Nodesの設定から、executorの数を1にしておくことで、排他制御をJenkinsに保証してもらえる。


レポーティング



  1. publishHTML使うことで、最新のレポートを直ぐに見られる


  2. archiveArtifactsを使うことで、過去のレポートをダウンロードできるようにする

simulation.logという名前のままログファイルを集めると、名前が被って上書きされてしまうので、index番号付きの名前にリネームしてます。

また、simulation.logのサイズは大きくなりがちなので、今回のサンプルスクリプトではレポート作成後に削除しています。


マルチテナント対応

Jenkins Nodesの設定から、各サーバにラベルを貼っておくことで、サーバのグルーピングが可能となる。

DEV, STG, PRODラベルで環境を分ける、TeamA, TeamB, TeamCラベルでチーム専用のサーバを確保する等の使い方が考えられます。


終わりに

ググり辛いのが玉に瑕ですが、Pipeline Plugin凄く便利です。

for文をもう一個追加して、複数のSimulationを同時に実行する、など色々と応用が考えられます。

複雑なスクリプトを実行するよりは、シンプルなスクリプトをPipelineでフロー制御する方が、書きやすいですし見やすいと感じています。

ただし、調子に乗って壮大なPipelineを作成するとメンテが辛くなるので、何事もほどほどに。