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

# TL;DR

1. クライアント側の負荷は`parallel`で分散させる
2. テスト結果は`stash / unstash`で集約する
3. `gatling -reports-only`でテスト結果を統合する
4. `publishHTML`でレポートをJenkins上で見られるようにする
5. `archiveArtifacts`で後からダウンロード可能にする

## Jenkinsfile

```groovy: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](https://wiki.jenkins-ci.org/display/JENKINS/Pipeline+Plugin)ですよ。

# 戦略

## スケーラビリティ

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

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

```groovy
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を作成するとメンテが辛くなるので、何事もほどほどに。