ビルドやテストなどの一連の作業をJenkinsで自動実行することを考える。
通常これらのジョブはJenkinsのmasterやslaveで実行されることになり、ビルドに必要な環境構築(例えばjavaのインストールやmavenのインストール)はJenkinsのmasterやslaveに対して、事前に行うことになる。そして一度環境構築したサーバは長いこと使われる。
そうなると、同一サーバで複数バージョンのjavaを入れる苦労をしたりディストリビューションの差に基づくLinuxコマンドのあるなしに翻弄されたり、ローカルにしかないファイルを参照してそのうちジョブが動かなくなったりする。
これを解決するための手段としてビルド〜テストをDockerコンテナ上で実行しようという例は多くみられ、以下の記事が導入検討の参考になる。
以下、JenkinsジョブをDockerコンテナ上で実行するための方法を調べたときのメモ。
Jenkinsのpipelineが前提。使い方については昔にまとめました。
- JenkinsのDocker関連のplugin
- 現在自分がやってること
- Docker Pipeline Plugin
JenkinsのDocker関連のplugin
Jenkins上でDockerコンテナを利用する方法は複数あるらしく
- Docker Plugin
- https://wiki.jenkins-ci.org/display/JENKINS/Docker+Plugin
- スレーブ自体をDockerコンテナで作る
- Docker Pipeline Plugin
- https://wiki.jenkins-ci.org/display/JENKINS/Docker+Pipeline+Plugin
- ジョブを実行するスレーブでDockerコンテナを起動する
- pipelineのコード中で複数のコンテナを起動できる
Docker Pluginは、Dockerイメージを指定してスレーブに登録する作業が必要になるし、バージョン管理しているDockerfileからイメージを毎回生成する用途には向いてなさそう。(偏見かも)
またpluginを使わなくともJenkins上でshが実行できるので、自分でDockerコンテナを管理する方法もある。以下の記事がとても参考になった。
現在自分がやってること
pluginは特に使わず、自身でDockerコンテナを管理している。
具体的には以下の4ステップをMakefile内で行っている。
- Dockerイメージの生成
- Dockerコンテナの生成とタスクの実行
- 成果物の取り出し
- Dockerコンテナの破棄
また、mavenやnpmのライブラリがコンテナ上で毎回全ダウンロードされないように、ホストにマウントしてキャッシュしている。
現在自分がやってることの詳細
以下にサンプルを作ったので抜粋する。
github.com
基本的にDockerfileを自分でビルドしてコンテナを起動している。
主なファイルは以下の4つ。
ファイル | 説明 |
Dockerfile.build | ビルドサーバを構築するためのDockerfile |
Dockerfile.integration | 結合試験を実施するためのDockerfile |
Makefile | 開発時にローカル環境で利用するコマンドをまとめたMakefile |
Makefile.docker | dockerコンテナを操作するためのコマンドをまとめたMakefile |
Makefile
ビルドに必要なタスクはMakefileにまとめて記述する。
setup: npm --prefix client install build: setup npm --prefix client run build cp -R client/dist/* server/src/main/resources/static mvn -f server/pom.xml package -DskipTests=true
使い方
# server/target配下にjarができる
make build
Dockerfile.build
ビルドサーバをDockerイメージとして構築する。
タスクはMakefileにまとまっているので、ENTRYPOINTにmakeを指定する。
FROM openjdk:8u131-jdk-alpine RUN apk update && apk upgrade && \ apk add curl nodejs-npm=6.10.3-r0 maven=3.3.9-r1 make RUN curl -Ls "https://github.com/dustinblackman/phantomized/releases/download/2.1.1/Dockerized-phantomjs.tar.gz" | tar xz -C / WORKDIR /build COPY . /build ENTRYPOINT ["make"] CMD ["help"]
Makefile.docker
Dockerの操作が煩雑(イメージの作成~コンテナの生成~ビルドの実行~成果物の取り出し~破棄)なのでMakefileにまとめている。
# ホストで複数のDockerを起動できるようにイメージ/コンテナ名にidを付与する ID = 'default' BUILD_SERVER_IMAGE = sample-base-build-server-$(ID) BUILD_CONTAINER = sample-build-$(ID) CACHE_PATH='/tmp/docker/cache' base: docker build -t $(BUILD_SERVER_IMAGE) -f Dockerfile.build . build: base docker run --name $(BUILD_CONTAINER) -v $(CACHE_PATH)/.m2:/root/.m2 -v $(CACHE_PATH)/.node_modules:/build/client/node_modules $(BUILD_SERVER_IMAGE) build || Docker rm $(BUILD_CONTAINER) docker cp $(BUILD_CONTAINER):/build/server/target server docker rm $(BUILD_CONTAINER
使い方
# コンテナ内でビルドしてホスト環境のserver/target配下にjarをコピーしてくる make ID=1 -f Makefile.docker build
Jenkinsfile
Makefile.dockerを実行するだけ。
node { stage('build') { sh 'make ID=${BUILD_ID} -f Makefile.docker build' archiveArtifacts 'server/target/*jar' } ... }
自分でDockerを管理する問題点
成果物の取り出しやコンテナの破棄を作り込む(考慮する)のがめんどくさい。
対応できる範囲だけど、なんだかんだめんどくさい。
ジョブ実行時のファイルパスについても考慮が必要で、コンテナ内でビルドしたときのパスとJenkinsのworkspaceのパスが違うとlintツールの出力結果で問題になることがある。
例えばcheckstyleのテスト結果(checkstyle-result.xml)は各javaファイルへのリンクがフルパスで記述されるので、lint実行時のDockerコンテナ内のパスとJenkinsのworkspaceのパスが違うと出力レポートのリンクが開けないなんてこともある。
ただこれはコンテナのworkspaceをDocker起動時のディレクトリと同じにすれば解決する問題。
でも
つもりつもると・・・めんどくさい!
Docker Pipeline Plugin
そこでDocker Pipeline Pluginですよ!!!
動作確認環境
- Jenkins version 2.46.3
- Docker version 17.03.1-ce, build c6d412e
使い方(scripted pipeline)
新しめのバージョンだと、Global Variable Referenceに載ってる。
Global Variable Referenceはここ。
既存イメージを利用する場合
Pipelineプロジェクトを作成する。
とりあえずGlobal Variable Referenceに記述されてる動きを確認するために、scripted pipelineで記述する。
node { sh 'id' docker.image('openjdk:8u131-jdk').inside() { sh 'java -version' } }
ビルド実行をクリックする。
コンソール出力を読む。
裏でDockerコマンドを実行してるのがわかる。
ユーザーanonymousが実行 [Pipeline] node Running on master in /home/kimura/.jenkins/workspace/sample [Pipeline] { [Pipeline] sh [sample] Running shell script + id uid=1000(kimura) gid=1000(kimura) groups=1000(kimura),10(wheel),987(docker) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 [Pipeline] sh [sample] Running shell script + docker inspect -f . openjdk:8u131-jdk . [Pipeline] withDockerContainer Jenkins does not seem to be running inside a container $ docker run -t -d -u 1000:1000 -w /home/kimura/.jenkins/workspace/sample -v /home/kimura/.jenkins/workspace/sample:/home/kimura/.jenkins/workspace/sample:rw -v /home/kimura/.jenkins/workspace/sample@tmp:/home/kimura/.jenkins/workspace/sample@tmp:rw -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** --entrypoint cat openjdk:8u131-jdk [Pipeline] { [Pipeline] sh [sample] Running shell script + java -version openjdk version "1.8.0_131" OpenJDK Runtime Environment (build 1.8.0_131-8u131-b11-1~bpo8+1-b11) OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode) [Pipeline] } $ docker stop --time=1 c98ccf713004056fc3a59cebe1b4cd1f924653c63e0ba2301ed0fa9ed86f976d $ docker rm -f c98ccf713004056fc3a59cebe1b4cd1f924653c63e0ba2301ed0fa9ed86f976d [Pipeline] // withDockerContainer [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline Finished: SUCCESS
コンテナ起動部分について抜粋し、さらに見ていく。
ローカル環境で実行したときと同じ環境になるように、
- -wコマンドでコンテナのworkspaceをJenkinsのworkspaceと合わせている
- -vコマンドでJenkinsのworkspaceをコンテナのworkspaceディレクトリにマウントしている
- -uコマンドでJenkinsの起動ユーザでコンテナを実行している
... + id uid=1000(kimura) gid=1000(kimura) groups=1000(kimura),10(wheel),987(docker) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 ... $ docker run -t -d -u 1000:1000 -w /home/kimura/.jenkins/workspace/sample -v /home/kimura/.jenkins/workspace/sample:/home/kimura/.jenkins/workspace/sample:rw -v /home/kimura/.jenkins/workspace/sample@tmp:/home/kimura/.jenkins/workspace/sample@tmp:rw -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** --entrypoint cat openjdk:8u131-jdk ...
あーなるほどー、そうすればよかったのか。
inside(){...}内で記述した...はコンテナ内で実行されている。
[Pipeline] sh [sample] Running shell script + java -version openjdk version "1.8.0_131" OpenJDK Runtime Environment (build 1.8.0_131-8u131-b11-1~bpo8+1-b11) OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode) [Pipeline] }
コンテナが残らないようにstopしてrmしている。
... [Pipeline] } $ docker stop --time=1 a900ace94102ba4096ddeec5d172ae010d16379a8d0c486dbf8058c01dc7271b $ docker rm -f a900ace94102ba4096ddeec5d172ae010d16379a8d0c486dbf8058c01dc7271b ...
既存のイメージを使う場合、docker.image(...).inside(...)を使えばいい感じにDockerを裏で実行してくれる。
Dockerfileからイメージをビルドする場合
Dockerfileからイメージを自分でビルドしたい場合に使うコマンド。
通常はcheckout scmでチェックアウトしたDockerfileを利用するが、とりあえずworkspaceにDockerfileを用意する。
$ pwd /home/kimura/.jenkins/workspace/sample $ ls -l 合計 4 -rw-rw-r--. 1 kimura kimura 528 6月 1 14:35 Dockerfile.build $ cat Dockerfile.build FROM openjdk:8u131-jdk-alpine RUN apk update && apk upgrade && \ apk add curl nodejs-npm=6.10.3-r0 maven=3.3.9-r1 make RUN curl -Ls "https://github.com/dustinblackman/phantomized/releases/download/2.1.1/dockerized-phantomjs.tar.gz" | tar xz -C /
node { docker.build("${BUILD_ID}", "-f Dockerfile.build .").inside() { sh 'node -v' } }
第一引数はイメージ名、第二引数はコマンド引数になる。
Dockerfileからビルドでき、またコマンドが実行されていることがわかる。
... [sample] Running shell script + docker build -t 38 -f Dockerfile.build . Sending build context to Docker daemon 2.56 kB Step 1/3 : FROM openjdk:8u131-jdk-alpine ---> c7105179e75b Step 2/3 : RUN apk update && apk upgrade && apk add curl nodejs-npm=6.10.3-r0 maven=3.3.9-r1 make ---> Using cache ---> 0a22744743d5 Step 3/3 : RUN curl -Ls "https://github.com/dustinblackman/phantomized/releases/download/2.1.1/dockerized-phantomjs.tar.gz" | tar xz -C / ---> Using cache ---> 9b99b208c53e Successfully built 9b99b208c53e [Pipeline] dockerFingerprintFrom [Pipeline] sh [sample] Running shell script + docker inspect -f . 38 . [Pipeline] withDockerContainer Jenkins does not seem to be running inside a container $ docker run -t -d -u 1000:1000 -w /home/kimura/.jenkins/workspace/sample -v /home/kimura/.jenkins/workspace/sample:/home/kimura/.jenkins/workspace/sample:rw -v /home/kimura/.jenkins/workspace/sample@tmp:/home/kimura/.jenkins/workspace/sample@tmp:rw -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** --entrypoint cat 38 [Pipeline] { [Pipeline] sh [sample] Running shell script + node -v v6.10.3 [Pipeline] } $ docker stop --time=1 00cb2f603ca6eb3ea558bb82e906c6075f8593a5f2cb52115ae86ff4c3d82726 $ docker rm -f 00cb2f603ca6eb3ea558bb82e906c6075f8593a5f2cb52115ae86ff4c3d82726 ...
Docker Pipeline Pluginの注意点
docker.image(...).inside(...)で実行するコマンドはDockerfileに記述したユーザではなくて、jenkinsの起動ユーザになる。
sampleのpipelineプロジェクトのworkspaceにDockerfileを置いてみる。
$ pwd
/home/kimura/.jenkins/workspace/sample
$ cat Dockerfile.build
FROM openjdk:8u131-jdk-alpine
USER root
Jenkinsのスクリプトに以下を書いて実行する。
node { sh 'id' docker.build("${BUILD_ID}","-f Dockerfile.build .").inside() { sh 'id' } }
コンソール出力を見ると、userがrootではなくて1000になってる。
パーミッションで困ることがあるかも。困る場合は -u で実行ユーザを明示的に指定しちゃえばよさそう。
... [sample] Running shell script + id uid=1000(kimura) gid=1000(kimura) groups=1000(kimura),10(wheel),987(docker) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 [Pipeline] sh [sample] Running shell script + docker build -t 5 -f Dockerfile.build . Sending build context to Docker daemon 2.048 kB Step 1/2 : FROM openjdk:8u131-jdk-alpine ---> c7105179e75b Step 2/2 : USER root ---> Using cache ---> 4dc2ef524f48 Successfully built 4dc2ef524f48 [Pipeline] dockerFingerprintFrom [Pipeline] sh [sample] Running shell script + docker inspect -f . 5 . [Pipeline] withDockerContainer Jenkins does not seem to be running inside a container $ docker run -t -d -u 1000:1000 -w /home/kimura/.jenkins/workspace/sample -v /home/kimura/.jenkins/workspace/sample:/home/kimura/.jenkins/workspace/sample:rw -v /home/kimura/.jenkins/workspace/sample@tmp:/home/kimura/.jenkins/workspace/sample@tmp:rw -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** --entrypoint cat 5 [Pipeline] { [Pipeline] sh [sample] Running shell script + id uid=1000 gid=1000 ...
ホスト環境にライブラリをキャッシュするときの注意点
Jenkinsのworkspace以外のホストディレクトリにキャッシュさせる場合、存在しないディレクトリにマウントするとアクセス権がrootになってしまうので注意。
Add ability to mount volume as user other than root · Issue #2259 · moby/moby · GitHub
以下、動きを確かめる。
dir1は作っておき、dir2をDockerコンテナ起動時の-vに指定する。
$ ls -al 合計 4 drwxrwxr-x. 3 kimura kimura 33 6月 1 19:24 . drwx------. 30 kimura kimura 4096 6月 1 19:20 .. drwxrwxr-x. 2 kimura kimura 6 6月 1 19:21 dir1 -rw-rw-r--. 1 kimura kimura 0 6月 1 19:20 hello.txt
起動時に-vに指定して生成されたdir2はrootになっていることがわかる。
]$ docker run -it --rm -u 1000:1000 -v $(pwd)/dir1:$(pwd)/dir1 -v $(pwd)/dir2:$(pwd)/dir2 -w $(pwd) alpine /bin/sh /home/kimura/sample $ ls -al total 0 drwxr-xr-x 4 root root 28 Jun 1 10:24 . drwxr-xr-x 3 root root 19 Jun 1 10:24 .. drwxrwxr-x 2 1000 1000 6 Jun 1 10:21 dir1 drwxr-xr-x 2 root root 6 Jun 1 10:24 dir2
なので、ライブラリをキャッシュするディレクトリは事前に作っておくと安全だと思う。
以下のようにするとうまくいった。(本当はユーザごとのディレクトリを指定したほうが安全)
sh 'mkdir -p /tmp/docker/cache/.node_modules || true' sh 'mkdir -p /tmp/docker/cache/.m2 || true' docker.build("${BUILD_ID}", "-f Dockerfile.build .").inside("-v /tmp/docker/cache/.m2:/var/maven/.m2 -v /tmp/docker/cache/.node_modules:${WORKSPACE}/client/node_modules") { ...
npm使うときの注意点
jenkinsの実行ユーザがDockerコンテナ内でコマンドを実行するため、パーミッションに注意が必要。とくにnpmを使う場合は /.npm にキャッシュディレクトリを作ろうとしてJenkins実行ユーザにパーミッションがなくて失敗することがある。回避策は npm_config_cache変数を環境変数に設定してキャッシュディレクトリの作られる場所を変えること。
npm install fails in jenkins pipeline in docker - Stack Overflow
docker.build(...).inside(...) { withEnv(['npm_config_cache=npm-cache']) { stage('build') { .... }
サンプルコード
上記をふまえてJenkinsfile内でDockerコンテナを管理してみた。
実行した結果はこちら。
ビルドの48からキャッシュさせたところ、ダウンロード時間が大幅に減ってるのがわかる。
Image.withRun[(args[, command])] {…}
Dockerコンテナを起動したあとに、ホスト側で{...}のコマンドを実行する。
node { docker.image('postgres:9.6.3').withRun("-p 5432:5432") { sh 'ls -al' } }
argsで指定した引数が起動引数に追加される。
Dockerコンテナの停止は{...}を抜けたあとに勝手に行われる。
... + docker run -d -p 5432:5432 postgres:9.6.3 [Pipeline] dockerFingerprintRun [Pipeline] sh [sample] Running shell script + ls -al 合計 8 drwxrwxr-x. 2 kimura kimura 29 6月 1 14:36 . drwxrwxr-x. 18 kimura kimura 4096 6月 1 13:20 .. -rw-rw-r--. 1 kimura kimura 528 6月 1 14:35 Dockerfile.build [Pipeline] sh [sample] Running shell script + docker stop 4be5a879c0ed353e7e0d5031a78a450d88c7392febcee75e90671b8171b95268 4be5a879c0ed353e7e0d5031a78a450d88c7392febcee75e90671b8171b95268 + docker rm -f 4be5a879c0ed353e7e0d5031a78a450d88c7392febcee75e90671b8171b95268 4be5a879c0ed353e7e0d5031a78a450d88c7392febcee75e90671b8171b95268 ...
テストのために一時的にDBを上げる用途に使うんでしょう、たぶん。
使い方(declarative pipeline)
最近は scripted pipeline よりも declarative pipeline のほうがおすすめらしい。
使い方はリファレンスに載っている。
既存イメージを利用する場合
scripted pipeline と同じように、とりあえずPipelineプロジェクトで進める。
pipeline { agent { docker 'openjdk:8u131-jdk' } stages() { stage('build') { steps() { sh 'java -version' } } } }
コンソール出力をみると、 scripted pipeline と同じようなことをやってる。
Dockerコンテナ上でshが実行され、ジョブが終わったときにコンテナをstopしてrmしている。
ユーザーanonymousが実行 [Pipeline] node Running on master in /home/kimura/.jenkins/workspace/yyy [Pipeline] { [Pipeline] stage [Pipeline] { (Declarative: Agent Setup) [Pipeline] sh [yyy] Running shell script + docker pull openjdk:8u131-jdk 8u131-jdk: Pulling from library/openjdk Digest: sha256:90c3ac824aa5ce63d8540bb73b6d548b40dc0d536702a48e6a7f21efdc10a861 Status: Image is up to date for openjdk:8u131-jdk [Pipeline] } [Pipeline] // stage [Pipeline] sh [yyy] Running shell script + docker inspect -f . openjdk:8u131-jdk . [Pipeline] withDockerContainer Jenkins does not seem to be running inside a container $ docker run -t -d -u 1000:1000 -w /home/kimura/.jenkins/workspace/yyy -v /home/kimura/.jenkins/workspace/yyy:/home/kimura/.jenkins/workspace/yyy:rw -v /home/kimura/.jenkins/workspace/yyy@tmp:/home/kimura/.jenkins/workspace/yyy@tmp:rw -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** --entrypoint cat openjdk:8u131-jdk [Pipeline] { [Pipeline] stage [Pipeline] { (build) [Pipeline] sh [yyy] Running shell script + java -version openjdk version "1.8.0_131" OpenJDK Runtime Environment (build 1.8.0_131-8u131-b11-1~bpo8+1-b11) OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode) [Pipeline] } [Pipeline] // stage [Pipeline] } $ docker stop --time=1 3143403b1d45c91f61b64b1d983cbf0562e9fe682aee566f2cdf1f16216b9437 $ docker rm -f 3143403b1d45c91f61b64b1d983cbf0562e9fe682aee566f2cdf1f16216b9437 [Pipeline] // withDockerContainer ...
Dockerfileからイメージをビルドする場合
まずはJenkinsのworkspaceにDockerfileを用意する。
$ pwd /home/kimura/.jenkins/workspace/yyy $ cat Dockerfile.build FROM openjdk:8u131-jdk-alpine RUN apk update && apk upgrade && \ apk add curl nodejs-npm=6.10.3-r0 maven=3.3.9-r1 make
以下のようにファイル名を指定する。
pipeline { agent { dockerfile { filename 'Dockerfile.build' args '-v /tmp/docker/cache/.m2:/var/maven/.m2' } } stages() { stage('build') { steps() { sh 'java -version' } } } }
ただしDockerfileという名前でworkspace直下にある場合は指定不要。
その場合は以下のようにする。
agent { dockerfile true } ...
コンソール出力をみると、Dockerfile.buildからイメージをビルドしているのがわかる。
また、argsに指定した引数がdocker run時に指定されているのがわかる。
ユーザーanonymousが実行 [Pipeline] node Running on master in /home/kimura/.jenkins/workspace/yyy [Pipeline] { [Pipeline] stage [Pipeline] { (Declarative: Agent Setup) [Pipeline] readFile [Pipeline] sh [yyy] Running shell script + docker build -t 05acb8528ede11dbba8c1b1dc5d2f3ef110d3a0a -f Dockerfile.build . Sending build context to Docker daemon 2.048 kB Step 1/2 : FROM openjdk:8u131-jdk-alpine ---> c7105179e75b Step 2/2 : RUN apk update && apk upgrade && apk add curl nodejs-npm=6.10.3-r0 maven=3.3.9-r1 make ---> Using cache ---> 0a22744743d5 Successfully built 0a22744743d5 [Pipeline] dockerFingerprintFrom [Pipeline] } [Pipeline] // stage [Pipeline] sh [yyy] Running shell script + docker inspect -f . 05acb8528ede11dbba8c1b1dc5d2f3ef110d3a0a . [Pipeline] withDockerContainer Jenkins does not seem to be running inside a container $ docker run -t -d -u 1000:1000 -v /tmp/docker/cache/.m2:/var/maven/.m2 -w /home/kimura/.jenkins/workspace/yyy -v /home/kimura/.jenkins/workspace/yyy:/home/kimura/.jenkins/workspace/yyy:rw -v /home/kimura/.jenkins/workspace/yyy@tmp:/home/kimura/.jenkins/workspace/yyy@tmp:rw -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** --entrypoint cat 05acb8528ede11dbba8c1b1dc5d2f3ef110d3a0a ...
注意点
agentをDockerにしたときにpostでdeleteDir()すると、エラーになる。
#!/usr/bin/env groovy pipeline { agent { dockerfile { filename 'Dockerfile.build' args '-v /tmp/docker/cache/.m2:/var/maven/.m2 -v /tmp/docker/cache/.node_modules:${WORKSPACE}/client/node_modules' } } ... post { always { deleteDir() } }
コンソール出力のエラー内容から予想するに、agentが1つしか指定されていない場合はすべてのステップをコンテナ内で実行するため、コンテナ起動時に割り当てられたworkspaceのディレクトリをdeleteDir()で消そうとしてエラーになってるっぽい。(自信ない)
[Pipeline] } [Pipeline] // stage [Pipeline] stage [Pipeline] { (Declarative: Post Actions) [Pipeline] deleteDir [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // withEnv [Pipeline] } $ docker stop --time=1 dddaf3a162e5d72f112fa040cd2af2012dbd0df5d25b794d17e72412787ed750 $ docker rm -f dddaf3a162e5d72f112fa040cd2af2012dbd0df5d25b794d17e72412787ed750 [Pipeline] // withDockerContainer [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline java.nio.file.FileSystemException: /home/kimura/.jenkins/workspace/zzz_plugin-declarative-CRDZ7E2TTQRHZTXPZFVEXD5PM6IOKJAECQWTPPBF52DR5B4W7TKQ/client/node_modules: デバイスもしくはリソースがビジー状態です at sun.nio.fs.UnixException.translateToIOException(UnixException.java:91) at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102) at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107) at sun.nio.fs.UnixFileSystemProvider.implDelete(UnixFileSystemProvider.java:244)
試しにtargetディレクトリをdocker起動時にマウントして消そうとすると同じエラーが出る。
$ pwd /home/kimura/docker/sample $ mkdir target $ ls -al 合計 0 drwxr-xr-x. 3 kimura kimura 19 6月 2 07:27 . drwxrwxr-x. 5 kimura kimura 57 6月 1 19:36 .. drwxrwxr-x. 2 kimura kimura 6 6月 2 07:27 target $ docker run -it -v $(pwd)/target:$(pwd)/target -w $(pwd) centos:7 /bin/bash # ls target # ll total 0 drwxrwxr-x. 2 1000 1000 6 Jun 1 22:27 target # rm -rf target rm: cannot remove 'target': Device or resource busy #
とはいえ、scripted pipelineと似たような要領で進められそう。
declarative pipelineのほうが事後処理がやりやすいので(always, failureなどなど)、新規に作成する人は declarative pipelineで作成したほうがよさそう。
Docker Pipelin Plugin の良いところ
- ビルドサーバのメンテナンス性向上
- ビルドタスクの柔軟性の向上
- バージョン管理できる
- ほぼローカルで実行するのと変わらないくらいコンテナ起動停止が早い
- コンテナに関するあれこれの考慮をあんまり意識せずに使える
Docker Pipelin Plugin の悪いところ
- Jenkinsfileにビルドやテストに関するあれこれを書きすぎるとローカル環境でDockerを気軽に使えなくなりそう
ということでローカルでもDockerを利用したビルドができるように、Docker Pipeline Pluginを参考にMakefileを作ってみた。
これが言いたかった
参考