SIer だけど技術やりたいブログ

Jenkinsジョブ(ビルド~テスト)をDockerコンテナ上で実行する ~Docker Pipeline Pluginを使ってみる~

Docker Jenkins

ビルドやテストなどの一連の作業をJenkinsで自動実行することを考える。
通常これらのジョブはJenkinsのmasterやslaveで実行されることになり、ビルドに必要な環境構築(例えばjavaのインストールやmavenのインストール)はJenkinsのmasterやslaveに対して、事前に行うことになる。そして一度環境構築したサーバは長いこと使われる。

そうなると、同一サーバで複数バージョンのjavaを入れる苦労をしたりディストリビューションの差に基づくLinuxコマンドのあるなしに翻弄されたり、ローカルにしかないファイルを参照してそのうちジョブが動かなくなったりする。

これを解決するための手段としてビルド〜テストをDockerコンテナ上で実行しようという例は多くみられ、以下の記事が導入検討の参考になる。

Dockerでビルドを改善するwww.buildinsider.net

JenkinsとDockerって何が良いの? 〜言うてるオレもわからんわ〜 #jenkinsstudywww.slideshare.net

以下、JenkinsジョブをDockerコンテナ上で実行するための方法を調べたときのメモ。
Jenkinsのpipelineが前提。使い方については昔にまとめました。

jenkinsのpipeline入門(jenkinsfile) - SIerだけど技術やりたいブログkimulla.hatenablog.com

JenkinsのDocker関連のplugin

Jenkins上でDockerコンテナを利用する方法は複数あるらしく

Docker Pluginは、Dockerイメージを指定してスレーブに登録する作業が必要になるし、バージョン管理しているDockerfileからイメージを毎回生成する用途には向いてなさそう。(偏見かも)

またpluginを使わなくともJenkins上でshが実行できるので、自分でDockerコンテナを管理する方法もある。以下の記事がとても参考になった。

Docker で「速くてウマイ」な CI 環境を構築するための 5 つの Tips | 株式会社ヌーラボ(Nulab inc.)nulab-inc.com

現在自分がやってること

pluginは特に使わず、自身でDockerコンテナを管理している。
具体的には以下の4ステップをMakefile内で行っている。

  • Dockerイメージの生成
  • Dockerコンテナの生成とタスクの実行
  • 成果物の取り出し
  • Dockerコンテナの破棄

また、mavenやnpmのライブラリがコンテナ上で毎回全ダウンロードされないように、ホストにマウントしてキャッシュしている。

現在自分がやってることの詳細

以下にサンプルを作ったので抜粋する。
kimullaa/docker-multibranch-samplegithub.com

基本的にDockerfileを自分でビルドしてコンテナを起動している。
主なファイルは以下の4つ。

ファイル説明
Dockerfile.buildビルドサーバを構築するためのDockerfile
Dockerfile.integration結合試験を実施するためのDockerfile
Makefile開発時にローカル環境で利用するコマンドをまとめたMakefile
Makefile.dockerdockerコンテナを操作するためのコマンドをまとめた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ですよ!!!

CloudBees Jenkins Enterprise

動作確認環境

  • 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  61 19:24 .
drwx------. 30 kimura kimura 4096  6月  1 19:20 ..
drwxrwxr-x.  2 kimura kimura    6  61 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コンテナを管理してみた。

kimullaa/docker-multibranch-samplegithub.com

実行した結果はこちら。
ビルドの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  61 14:36 .
drwxrwxr-x. 18 kimura kimura 4096  61 13:20 ..
-rw-rw-r--.  1 kimura kimura  528  61 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 のほうがおすすめらしい。
使い方はリファレンスに載っている。

Pipeline Syntaxjenkins.io

既存イメージを利用する場合

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
...
サンプルコード

とりあえずscripted pipeline pluginのコードに変えてみた。

kimullaa/docker-multibranch-samplegithub.com

注意点

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を作ってみた。

kimullaa/docker-multibranch-samplegithub.com

これが言いたかった

参考