追記: 2016/01/31
2016/01/18ごろにWorkflowプラグインは、Pipeline プラグインに改名されたようです。
https://wiki.jenkins-ci.org/display/JENKINS/Pipeline+Plugin
自動テストでデータベースなどのリソースの問題で並列実行ができないときに、Dockerを使ってリソースを用意して並列化する方法をまとめました。
なるべく実践的な環境を想定して以下のことができることを目標とします。
- GitLabと連携してマージリクエストに対してテストを実行する
- 手動でブランチ名を指定してジョブを実行できるようにする
- 1つのテストジョブを分割して並列処理できるようにする
構成
構築した環境の構成は以下の通りです。
- OSX
- Docker v1.9.1
- Jenkins v1.625.3
- dockerイメージを使用 https://hub.docker.com/_/jenkins/
- Jenkins Git Plugin v2.4.1
- Jenkins GitLab Plugin v1.1.28
- Jekins Parallel Test Executor Plugin v1.7
- Jenkins Parameterized Trigger plugin v2.30
- Jenkins Workflow Plugin v1.12, Workflow Multibranch
- Jenkins CloudBee Docker Workflow Plugin v1.2
環境構築
環境の構築手順は以下の通りです。
OSXへのdockerのインストールは公式の手順通りに完了し、Dockerサーバーが"default"という名称で動いているところからスタートしています。
Jenkins環境構築
まずは、JenkinsをDockerコンテナで構築します。
# 色々使うのでDockerサーバーのIPを取得
DOCKER_IP=$(docker-machine ip default)
# Docker Remote API 用環境変数設定
eval $(docker env default)
# Jenkins用コンテナからdockerソケットに読み書きできるように権限をつける
docker-machine ssh default sudo chmod o+rw /var/run/docker.sock
# docker でJenkinsを起動
docker pull jenkins
# dockerサーバー内の/var/jenkins_homeをマウントして起動
docker-machine ssh default \
'docker run --detach --name jenkins
-v /var/run/docker.sock:/var/run/docker.sock
-v /var/jenkins_home:/var/jenkins_home
-p 8080:8080 -p 50000:50000 jenkins'
# jenkins-cliとの通信用ポートの50000を変える場合は以下の引数を指定します
# --env JENKINS_SLAVE_AGENT_PORT=50001
# 起動確認
curl http://${DOCKER_IP}:8080/api/json
# => JSONで設定状況を表示
# JenkinsのURLを環境変数に設定
export JENKINS_URL=http://${DOCKER_IP}:8080
# プラグインインストールのためJenkins-cli.jarを取得
wget ${JENKINS_URL}/jnlpJars/jenkins-cli.jar
# プラグインをインストールする
java -jar jenkins-cli.jar install-plugin \
git \ # Git Plugin
gitlab-plugin \ # GitLab Plugin
parallel-test-executor \ # Parallel Test Executor Plugin
parameterized-trigger \ # Parameterized Trigger plugin
workflow-aggregator \ # Workflow: Aggregator
workflow-multibranch \ # Workflow: Multibranch
docker-workflow # CloudBees Docker Workflow
# プラグインをDLするのを待つ。
# 以下の画面で確認できる
# '${JENKINS_URL}/updateCenter/'
# プラグインを反映するため再起動
java -jar jenkins-cli.jar safe-restart
# Jenkinsコンテナ内にDockerコマンドをインストール
docker exec -u root jenkins sh -c \
'apt-get update &&
apt-get install -y docker.io &&
rm -rf /var/lib/apt/lists/*'
テストに使用するDockerイメージを用意します。
今回は、mavenのイメージをそのまま使うので、pullだけしておきます。
docker pull maven
Jenkinsのシステム設定を変更します。
- [Jenkinsの管理]-[システムの設定]を開く
- [Gitlab]の項目に、GitlabのURLと連携用API Tokenを設定
ジョブ設定
次にジョブを作成します。
ジョブは2つ1セットで作成します。1つはGitlabのマージリクエストのトリガを受け取るジョブで、もう1つが先のジョブから起動される実際にテストを行うジョブです。
1つ目:MergeRequestのトリガを受け取るジョブ
- ジョブ名: main
- ジョブの種類: フリースタイル・プロジェクトのビルド
2つ目:テストを実行するジョブ
- ジョブ名: sub
- ジョブの種類: Workflow
GitlabのプロジェクトのHookにmainプロジェクトを追加します。
プロジェクトの[Settings]-[Web Hook]でHookを追加します。
- URL: http://192.168.99.100:8081/project/main
- Trigger:
- Push events
- Merge Request events
テスト実行スクリプトを実装
テスト対象のプロジェクトのルートフォルダに、Workflowプラグインで実行するスクリプトを記述するJenkinsファイルを追加します。
#!groovy
# ↑の行はエディタなどのシンタックスハイライトの判定用
DOCKER_IMAGE = 'maven'
stage 'UnitTest'
node {
// 下記の後部にWorkflow Multibranchが必要
checkout scm
// mavenの依存ライブラリの取得を先に行っておく
sh "${tool 'maven3'}/bin/mvn --batch-mode dependency:go-offline -Dmaven.repo.local=./m2repo"
stash includes: '**/*', name: 'files'
}
// https://github.com/jenkinsci/workflow-plugin/blob/master/TUTORIAL.md#creating-multiple-threads
def splits = splitTests([$class: 'CountDrivenParallelism', size: 3])
def branches = [:]
for (int i = 0; i < splits.size(); i++) {
def exclusions = splits.get(i);
branches["split${i}"] = {
node {
docker.image(DOCKER_IMAGE).inside {
sh 'rm -rf *'
unstash 'files'
writeFile file: 'exclusions.txt', text: exclusions.join("\n")
// テスト実行
sh """
mvn --batch-mode -Dmaven.test.failure.ignore -Dmaven.repo.local=./m2repo test
"""
step([$class: 'JUnitResultArchiver', testResults: 'target/surefire-reports/*.xml'])
}
}
}
}
parallel branches
これでテスト並列実行のための設定は完了です。
解説
ここからは、なぜこういう設定にしているかという意図や、上記の方法にたどり着くまでに試行錯誤したことを紹介します。
Dockerへの接続方法
dockerコマンドを使う時は、ソケットかTCPかの2つの通信方法を選べます。
ソケットを使うにはDockerサーバー上の/var/run/docker.socket
にアクセスできる必要があるので通常はTCPを使うことになると思います。TCPの通信は、ツールによってはHTTPのURL形式で指定します。
TCP方式ではなぜかdocker execが途中でとぎれてしまう
前述の構築方式ではソケット通信を使っていますが、最初はTCPの接続方式を利用するつもりでした。
ですが、私が試した環境ではTCPを利用すると、Jenkinsのジョブ中で実行するdocker exec
コマンドがなぜか処理途中で中断してしまう現象がおきました。
例えば、Dockerサーバー上で以下のようにコマンドを実行すると、1つ目はsleepでの待ち時間た経過してから処理が完了します。ところが、2つ目の方はsleepで時間が経過する前にコマンドが終了してしまいます。
$ docker exec 9a2d5f18ed69 sh -c "echo begin; sleep 10 ;echo end"
begin
end
$ DOCKER_HOST=tcp://192.168.99.100:2375 docker exec 9a2d5f18ed69 sh -c "echo begin; sleep 10 ;echo end"
begin
色々試すとなんとなくttyの有無による影響があるようですが、よくわかりませんでした。
結局は、Jenkinsを実行するDockerコンテナの実行時にdocker.sockもVOLUMEとしてマウントし、それを使ったソケット通信でdockerコマンドを使うようにしました。
httpスキーマのURLを使う方法
今回の方法ではソケットを使いましたが、TCPを使おうとしたときにも詰まったことがあったので紹介しておきます。
TCPのAPIと書きましたが、実際にはDockerをインストールするとデフォルトではTLSでのセキュアな通信(HTTPS)となります。これを利用するには、dockerコマンドを実行する環境にクレデンシャル情報が必要になります。
このHTTPSでの通信はまだ実装されてから間がないらしく、HTTPでなければ利用できないツールが多いようです。JenkinsのDocker Pluginもその一つで、httpsのURLは利用できません。設定画面にクレデンシャルを設定する入力欄もありますが、それは機能しません。
そこで、このDockerのAPIをHTTPにする方法を調べました。
まず、dockerサーバーを構築・管理するdocker-machine
コマンドで切り替えられないかを調べたのですが、どうやら方法は無いようでした。
docker-machine create
でDockerサーバーを新規作成するときのオプションも効果がありません。
調べたところ、Dockerデーモンを起動する時の設定を変えるとTLSをOFFにできるようでした。Dockerサーバーの /var/lib/boot2docker/profile
ファイルの DOCKER_TLS
変数の値を"auto"から"no"に変えて、dockerデーモンを再起動すると変更できます。
-DOCKER_TLS=auto
+DOCKER_TLS=no
ところが、これでHTTP化自体はできるのですが色々と不都合もあります。
dockerコマンドは、自前でDOCKER_HOST変数などを正しく設定すれば使えるようにはなりますが、docker-machineコマンドのdocker-machine env
がエラーになり使えなくなります。OSXについてくる管理用GUIツールのKitematicも同様に使えなくなります。
% docker-machine env default
Error checking TLS connection: Error checking and/or regenerating the certs: There was an error validating certificates for host "192.168.99.100:2376": tls: oversized record received with length 20527
You can attempt to regenerate them using 'docker-machine regenerate-certs [name]'.
Be advised that this will trigger a Docker daemon restart which will stop running containers.
~/.docker/machine/machines/default/config.json
に設定ファイルがあるのですが、私が触った限りではどうやっても対処することができませんでした。
また、docker-machine restart
でdockerサーバーを再起動したら、上記の設定が元に戻ってしまいます。
ということで、デーモンをHTTP設定で起動するのはちょっと良くなさそうです。
別の方法として、HTTP用のProxyサーバーを立てるのが良さそうでした。
下記のページでソケット通信をTCP通信にしてくれるProxyサーバーを稼働させるコンテナが紹介されています。
- Boot2docker TLS workaround - SequenceIQ Blog
このコンテナは、以下のようにDockerサーバー上で実行します。
# dockerサーバー上でDocker Remote API をHTTPアクセス可能にするProxyを起動
docker-machine ssh default eval '$(docker run sequenceiq/socat)'
# Proxy起動確認
curl http://${DOCKER_IP}:2375/_ping
# => OK
上記ではdocker run
時に「-v」オプションがついていませんが、このコンテナ起動の結果としてdocker.sockをマウントしてdocker run
を実行するコマンドが出力されるので、それをevalで実行しています。
これにより、HTTPSでもHTTPでもRemote APIを実行することができるようになります。
JenkinsサーバーへのDockerインストール
前述の方法ではJenkinsサーバー上にDockerコマンドをapt-getでインストールしています。
ちなみに、JenkinsにDocker関連のpluginをインストールすると、システム設定の画面にDockerをツールとして設定する項目が追加されます。そこには、Dockerについて[自動インストール]のチェックがありますが、これにチェックと入れても実際にはインストールされません。
Jenkinsのjenkins_homeディレクトリのマウント
今回の方法では、jenkinsサーバーを起動するときにわざわざDockerサーバーの中でdocker run
を実行し、/var/jenkins_homeをVolumeとして設定しています。これは、dockerコンテナ内でdocker run
したときに「-v」でのマウント場所がおかしくなる問題への対処になります。
dockerコンテナの中でdocker run -v/host/path:/cont/path
といったようにVOLUME指定をすると、なぜかホスト側のパス(/host/path)が実行したコンテナ内のパスではなく、Dockerサーバー上のパスでマウントされてしまいます。
そのため、Jenkinsのジョブ内でテスト用のコンテナを実行する時に、作業用のworkspaceを正常にマウントして使うことができませんでした。
そこで、Jenkinsコンテナ上の/var/jenkins_homeと、Dockerサーバー上の/var/jenkins_homeを同一のものとしてマウントすることで、jenkins_home下にあるworkspaceがDockerサーバー上のパスとして扱われても同じ結果になるようにしています。
また、このマウントにはもう一つ注意することがあります。
workspaceとしてマウントされるディレクトリの所有者(UID)がマウント先のコンテナ上のメイン動作用のユーザのUIDと一致するようにしておく必要があります。
そうでなければ、テスト用のコンテナからworkspace内に書き込みができません。
さらに、細かい条件がわからないのですが、書き込み権限を0777などと緩和してもUIDが異なる場合にはworkflowスクリプトが途中で詰まる現象が発生します。
現象自体は、以下にISSUEとしてすでに登録されていました。
- [JENKINS-28821] Docker Workflow demos: sh step on Docker image hangs if workspace is not writable - Jenkins JIRA
具体的な現象としては、workflow内で image.inside { sh "mvn test" }
といったようにコンテナ内でシェルコマンドを実行しようとするとその処理で止まってしまいます。
Running: Shell Script
[workspace] Running shell script <=ここで止まる
Send Response[retVal=hudson.remoting.UserResponse@212ec6fc,exception=null]
Jan 02, 2016 11:47:28 AM FINE hudson.remoting.Channel
Received UserRequest:org.jenkinsci.plugins.durabletask.BourneShellScript$DarwinCheck@87761c4
Jan 02, 2016 11:47:28 AM FINE hudson.remoting.Channel
Send Response[retVal=hudson.remoting.UserResponse@7d49d292,exception=null]
Jan 02, 2016 11:47:28 AM FINE hudson.remoting.Channel
Received UserRequest:hudson.Launcher$RemoteLaunchCallable@701b314c
Jan 02, 2016 11:47:28 AM FINE hudson.Proc
Running: docker exec 94d7388a388bf746b16afbbbf436ce49a048e10457080a30ef7527e6ea8be770 env BUILD_DISPLAY_NAME=#54 BUILD_ID=54 BUILD_NUMBER=54 BUILD_TAG=jenkins-workflow-54 BUILD_URL=http://192.168.99.100:8080/job/workflow/54/ CLASSPATH= DOCKER_HOST=tcp://192.168.99.100:2376 HUDSON_HOME=/var/jenkins_home HUDSON_SERVER_COOKIE=027abd10c6603ceb HUDSON_URL=http://192.168.99.100:8080/ JENKINS_HOME=/var/jenkins_home JENKINS_SERVER_COOKIE=please-do-not-kill-me JENKINS_URL=http://192.168.99.100:8080/ JOB_NAME=workflow JOB_URL=http://192.168.99.100:8080/job/workflow/ NODE_NAME=192.168.99.1 sh -c echo $$ > '/Users/dev/jenkins/slave/workspace/workflow/.jenkins-11263679/pid'; jsc=durable-751167607317369943dd70151bd14f2d; JENKINS_SERVER_COOKIE=$jsc '/Users/dev/jenkins/slave/workspace/workflow/.jenkins-11263679/script.sh' > '/Users/dev/jenkins/slave/workspace/workflow/.jenkins-11263679/jenkins-log.txt' 2>&1; echo $? > '/Users/dev/jenkins/slave/workspace/workflow/.jenkins-11263679/jenkins-result.txt'
↑このログ出力後にとまっている様子
Jan 02, 2016 11:47:28 AM FINE hudson.remoting.Channel
Send Response[retVal=hudson.remoting.UserResponse@43a401e1,exception=null]
Jan 02, 2016 11:47:28 AM FINE hudson.remoting.Channel
Received UserRequest:RPCRequest(39,getIOtriplet)
Jan 02, 2016 11:47:28 AM FINE hudson.remoting.Channel
Send Response[retVal=hudson.remoting.UserResponse@22a82a0,exception=null]
この現象が発生して処理が詰まっている時は、起動しようとしたdocker exec 〜〜
のプロセスが存在していませんでした。また、JenkinsのFINEレベルのログをみると実行直前に出力されるログ(コマンドの内容)があるので、以下のプロセスを起動する部分でとまっているのではないかと思います。
Gitlabのマージリクエストに対するジョブの起動方法
今回はGitlabプラグインを使いましたが、Gitlabとの連携をするためのプラグインはもう一つあります。
- Gitlab Merge Request Builder Plugin - Jenkins - Jenkins Wiki
こちらの方は非公式のもののようですが、先発なので現在の利用者数はこちらの方が多いのではないかと思います。
今回採用しているGitlabプラグインは、このGitlab Merge Request Builder(以降 Gitlab MRB)を参考にして作成された公式のプラグインのようです。
Gitlab MRBを参考にしているということで、大まかな設定方法(gitlab固有の変数とか)は同じなのでプラグインを切り替えても、そのまま動作できそうな雰囲気です。(ただし未検証です)
主な違いは以下のようなところです。
- Gitlabプラグインは、
- ci-skipプラグインの機能も含んでいる
- トリガの起点がGitlabのHook機能によって行われる
- Gitlab MRBプラグインは定期ポーリングでのトリガ起動ができる
- Gitlab 8.x系の機能との連携がある
- Commit履歴画面で各コミットごとにCI通過結果のマークが表示される など
- Merge Requsetに関係なく、個々のコミットに対してテストを起動できる
Gitlab8.xの機能を気にしなければ、機能的にはどちらを使っても十分だと思います。
ちなみに、8.xでの連携を行うとGitlabで以下の様な表示ができるようになります。
起動用ジョブとテスト処理ジョブの2つを作成する理由
今回の方法でGitlabと連携するジョブとテストを実際二行うWorkflowジョブが分かれているのは、Workflow形式のジョブでは上記のGitlab用プラグインが機能しなかったためです。
Workflow形式のジョブでは、Gitlab MRBプラグインはそもそもジョブ設定画面に設定欄自体が表示されません。一方のGitlabプラグインは設定欄は表示されるものの設定してもトリガが反応しません。そのため、起動用ジョブとメイン処理ジョブを別々に作成しています。
ちなみに、どうもWorkflowジョブは環境変数の展開処理が行われないようで、ブランチ名などに変数を指定しても展開されません。ジョブのビルドパラメータの指定も効きませんし、環境変数を細かく指定できるEnvInjectプラグインも効果がありませんでした。
ビルドパラメータの設定
起動用ジョブにはGitlabプラグインで指定される変数をビルドパラメータに設定しています。これによって、手動で対象ブランチを指定してビルドを実行できるようにしています。
このとき、Gitリポジトリの設定欄で[Branches to build]に指定するブランチ設定は1つだけにしておく必要があります。これは、Gitプラグインで特定のブランチをビルド対象にするには、ブランチ名が一意に決まる状況にしておく必要があるためです。
詳細は、こちらの記事を参照ください。
- JenkinsのGit Pluginの設定の意味がやっとわかった
Gitlabプラグインの設定内容について
Gitlabプラグインを使ったJenkinsとGitlabの連携用設定は、プラグインのGithub上の説明を読んでもらえば詳細が書いてあります。
今回は設定していませんが、テスト対象ブランチをメインブランチにマージした状態でテストするようにすることもできます。
- jenkinsci/gitlab-plugin
なお、Gitlab本体のドキュメントに以下のページがあります。
- GitLab Documentation
ここでは「Services」というところで「Jenkins CI」という項目を設定するとの記載がありますが、これはGitlab EE固有のものでCE版にはこの項目がありません。代わりにHook機能を使えばよいようです。
Jenkinsジョブ側の設定で、デフォルト状態から変えている部分の理由は以下のとおりです。
- [Build on Push Events]はオフ
- これがオンだと、Gitリポジトリ上のすべてのコミットに対してジョブを実行してしまうようだったので切っています
- またMerge Request上のブランチへのコミットだと、Push EventとMR Eventの両方で同じコミットに対するジョブが2つ起動されてしまいます
- [Rebuild open Merge Requests]を「On push to source branch」にする
- 初期はこれが「never」ですが、それだとマージリクエストが作成された時しかジョブが実行されません。
- 作成済みのMerge Requestに追加でコミットが行われたときにもビルドを実行するために、この設定にしています。
Push Eventをオフにすると、メインとするブランチの定期的なビルドができませんが、それは別のトリガを設定することでカバーすれば良いと思います。
Workflowスクリプトについて
Workflowプラグインは、ジョブで行いたい処理をgroovyスクリプトで記述することができるプラグインです。ユーザの入力を行ったり、処理をStageに分けて記録したりすることができます。また、Jenkins Enterpiseであれば、workflowの途中で中断・再開をする機能も使えるようです。
Workflowのスクリプトの記述は、以下のページを参照してください。
このページはTUTORIALとのことらしいのですが、これ以外にリファレンス的なものはみつけられませんでした。
Workflow スクリプトでの checkout scm
今回のスクリプトではコードのチェックアウトをcheckout scm
という記述で行っています。
このscm
変数を使うには「Workflow: Multibranch」プラグインが必要です。scmは、ジョブで指定したリポジトリ設定を保持しています。
起動用ジョブの[Trigger/call builds on other project]タスクで[Pass-through Git Commit that was built]を指定したことで、起動用ジョブで対象としているブランチをチェックアウトできるようになっています。
ちなみに、このscmを使わない場合は直接リポジトリのURLやブランチ名を明記することしかできないようでした。
Git checkoutの後のclean
JenkinsでGitを使用するのであれば、基本的にはジョブ設定のリポジトリ設定欄で「Additional Behaviours:」に[Clean after checkout]を追加しておくのが良いと思います。
Jenkinsではworkspaceのディレクトリを使い回すので、以前のジョブ終了時のファイルが全て残っています。その状態でブランチをcheckoutしてもgit管理外のファイルがあるため、想定外の動作をしてしまう場合があります。
このclean設定を有効にすると、ブランチのチェックアウト後にgit管理外のファイルを全て削除した状態にしてくれます。
docker workflow の使い方
Workflowスクリプトの中でdockerの操作を行うために、docker workflowプラグインを使用しています。
このdocker関連のスクリプトの詳細は以下を参照してください。
このプラグインは内部的にdockerコマンドを使っているので、ジョブが実行されるJenkinsのノードにdockerコマンドをインストールしておく必要があります。また、dockerコマンドがDockerサーバーと接続できる状態にしておくことも必要です。
プラグインの機能のwithServer()
メソッドでTCP(HTTPS非対応)形式のAPIを使うこともできるのですが、前述の通りソケット方式でなければ正常に動作しなったので今回は使っていません。
Parallel Test Executorプラグイン
これはMavenやAntでのJUnit形式のテストを並列実行できるようにするプラグインです。今回はworkflowの中でsplitTests()
メソッドとして利用しましたが、通常のジョブでも利用することができます。
このプラグインでは、テストの並列実行は以下の様な方式で行われています。
- ジョブの実行履歴の中で、正常実行(テストの成否は問わない)した最近の記録を取得する
- 取得した記録からテストクラスの一覧を収集し、指定した並列数に応じて分割する
- 並列実行する各1ジョブごとに、実行しない分の一覧をexclusions.txtファイルに書き出して割りあてる
- mavenやantのテスト実行時に、このexclusions.txtファイルのリストを除外してテストを実行するようにしておく
このように、実行しない分のリストを指定する方式なので、初回実行時は空のリストで1ジョブで実行されます。また、新規にテストを追加した場合は並列実行する全てのジョブで実行されることになります。
Docker Pluginを使う方法
今回は最終的にWorkflowを使いましたが、当初はParallel Test Executor Plugin と Docker Pluginの組み合わせで行おうとしていました。
せっかくなのでその方法も記載しておきます。
- Jenkins Docker Plugin v0.16.0
先の手順のプラグインのインストールを以下の様に変更します。
# プラグインをインストールする
java -jar jenkins-cli.jar install-plugin \
git \ # Git Plugin
gitlab-plugin \ # GitLab Plugin
parallel-test-executor \ # Parallel Test Executor Plugin
docker-plugin # Docker Plugin
Jenkinsのシステム設定を以下の様に設定します
- [Jenkinsの管理]-[システムの設定]を開く
- [クラウド]の項目で、[追加]-[Docker]を選択してDocker設定を追加する
- [Name]を適当につけて、[Docker URL]にRemote APIのHTTP(HTTPS不可)のURLを記載する
- [Images]で[Add Docker Template]メニューから新規イメージテンプレートを追加する
このイメージテンプレートがテスト実行時に起動されることになる - [Docker Image]に使用するDockerイメージ名を指定
* 今回は、「maven」を使用 - [Instance Capacity]にこのイメージを同時起動する最大数を指定
- [Labels]、[用途]は通常のスレーブノードと同じように設定する
* 今回は、「slave」というラベルを指定 - [Launch method]で「Docker SSH computer launcher」を選択し、認証情報にSSH接続のための情報を追加する
- [Pull strategy]は用途に応じて設定
* 個人的には「Never Pull」にして適宜自前で用意するようにするのがいいのではと思います
最後にParallel Test Executorの説明通りに2つのジョブを以下の様に作成します。
1つ目: MergeRequestのトリガを受け取って起動し、subテストを並列実行するジョブ
ジョブ名: main
ジョブの種類: フリースタイル・プロジェクトのビルド
2つ目:テストを実行するジョブ
- ジョブ名: main
- ジョブの種類: フリースタイル・プロジェクトのビルド
Docker Pluginを使うと、Docker Image Templateで指定したイメージがJenkinsの通常のスレーブノードの様に扱われます。
ジョブが開始されて担当するノードを決定するときに、このイメージテンプレートが選択されるとDockerコンテナを起動し、ジョブに割り当てます。
コンテナが起動してからは、通常のスレーブノードと同様にジョブの処理が行われ、終了した時点でDockerコンテナが破棄されます。
この方法ではジョブの設定で実行ノードの制限を指定する以外は特別なことはありません。Parallel Test Executorプラグインで起動される多数のsubジョブでもDockerコンテナが1つずつ起動されます。
この方法はDockerでテストを実行させるのなら便利なのですが、実際にやってみると問題ありました。
複数のsubジョブが一度にジョブキューに登録されるのですが、1つ目のDockerコンテナが起動したら、2つ目以降はノードの空きを待つ状態で止まってしまいます。期待した動作としては、それぞれに対してDockerコンテナを起動して欲しいところです。しかし、どうも「実行するノードを制限」でラベルを指定すると、現在のノードリスト中にそのラベル条件に合致するノードが存在する場合は、そのノードの終了を待つ状態になってしまうようでした。実際にラベル指定を行わずに試してみると、複数のDockerコンテナが起動されました。
結局はテストの並列実行ができなかったので、この方法は採用しませんでした。
[追記] Workflowスクリプトで実行可能なメソッド
(書き忘れていたことがあったので追記です)
WorkflowのGroovyスクリプトは制限されたSandbox環境で実行されます。そのため、実行できるJavaのクラスやメソッドが制限されていて、利用不可のメソッドを使うと以下のようなエラーが発生します。
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use method groovy.lang.GroovyObject invokeMethod java.lang.String java.lang.Object (org.jenkinsci.plugins.workflow.cps.CpsClosure2 sleep java.lang.Integer)
at org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.StaticWhitelist.rejectMethod(StaticWhitelist.java:155)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onMethodCall(SandboxInterceptor.java:77)
これを解除するには、Jenkins設定の[In-process Script Approval]画面にて承認する必要があります。上記のエラーが出たあとで、この設定画面を開くと下記のように許可するか、拒否するかを選択できる状態になっています。[Approve]ボタンをクリックして許可すると、次回以降はエラーがでなくなります。
この設定は事前に許可リストを投入するようなことはできないようなので、先にジョブを実行させなければならないようです。また、許可済みの項目を個別に除外するようなこともできなさそうです。
まとめ
かなり長くなりましたが、色々とはまりながらなんとかそれらしい形にまとめることができました。ですが、まだ実践では使っていないので、これからも試行錯誤することがありそうな気がします。