JenkinsのビルドをDockerコンテナ内で行う手順

なんとなくは理解していたつもりですが、JenkinsのビルドをDockerコンテナ内で実行する方法を改めて調査しました。環境構築手順と合わせてまとめ記事にしました。

目次

目次を開く

Jenkinsとは

Jenkins(ジェンキンス) とは、継続的インテグレーション(CI)や継続的デリバリー(CD)、継続的デプロイメントを実現するツールです。
Jenkinsは、「ソフトウェアのリリーススピードの向上」「開発プロセスの自動化」「開発コストの削減」といった目的とするオープンソースのツールです。現在、プロジェクトはLinux Foundationによって管理されています。 CI/CDツールはたくさんありますが、そのなかでも Jenkinsは、汎用性が高いことから大きな支持を得ています。170万ユーザー以上の導入実績があり、世界中のソフトウェアエンジニアによって開発された 1,800以上ものプラグインが利用可能なことが特長です。 Jenkinsは、Javaが動く環境であればLinuxでもWindowsでも動作させることができます。また、DockerやKubernetes上の動作もサポートされているため、コンピューターリソースをより効率的に利用することができるようになりました。プラグインが提供する機能だけでなく、スクリプトを記述することにより任意の処理を実行できるので、ビルドプロセスに合わせてビルドからリリースまで柔軟に自動化が行うことができます。

出典:CloudBees

歴史は結構長く、最新のLTSバージョンは2.361.1です。

Jenkins環境の構築

Jenkins環境をDocker上に構築します。
JenkinsのビルドジョブをDockerで実行させるため、"Docker in Docker"を実現する docker:dindイメージを使用します。

  • イメージ図

image.png

Dockerfileの作成

Docker Hubに公開されいてるjenkins/jenkinsイメージを使用します。dockerコマンドが実行できるようにdocker-ce-cliをインストールし、Jenkinsのプラグインをインストールします。

DOCKER OFFICIAL IMAGEとしてjenkinsイメージも公開されていますが、こちらは DEPRECATED 扱いになっており、2018年から更新されていません。

jenkins

  • Dockerfile
FROM jenkins/jenkins:lts-jdk11

USER root

RUN apt-get update && apt-get install -y apt-transport-https \
       ca-certificates curl gnupg2 \
       software-properties-common && \
    curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - && \
    apt-key fingerprint 0EBFCD88 && \
    add-apt-repository \
        "deb [arch=arm64] https://download.docker.com/linux/debian \
        $(lsb_release -cs) stable" && \
    apt-get update && apt-get install -y docker-ce-cli && rm -rf /var/lib/apt/lists/*

USER jenkins

RUN jenkins-plugin-cli --plugins "blueocean:1.25.0 docker-workflow:1.26"

x86アーキテクチャの場合は、 arch=arm64 の部分を arch=amd64 にしてください。

公式サイトのドキュメントを参考にしました。

docker-compose.yamlファイルの作成

  • docker-compose.yaml
version: '3.9'

services:
  docker:
    image: docker:dind
    privileged: true
    networks: 
      - jenkins-network
    environment:
      DOCKER_TLS_CERTDIR: /certs
    volumes:
      - ./jenkins-docker-certs:/certs/client
      - ./jenkins-data:/var/jenkins_home
    expose:
      - "2376"

  jenkins-blueocean:
    build: .
    depends_on:
      - docker
    networks: 
      - jenkins-network
    environment:
      DOCKER_HOST: "tcp://docker:2376"
      DOCKER_CERT_PATH: /certs/client
      DOCKER_TLS_VERIFY: 1
    expose:
      - "8080"
      - "50000"
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - ./jenkins-docker-certs:/certs/client:ro
      - ./jenkins-data:/var/jenkins_home

networks:
  jenkins-network:
    driver: bridge

起動

起動します。

mkdir jenkins-data jenkins-docker-certs
docker compose up -d

初期設定に必要なAdministrator passwordがログに出力されます。

docker compose logs
jenkins-jenkins-blueocean-1  | *************************************************************
jenkins-jenkins-blueocean-1  | *************************************************************
jenkins-jenkins-blueocean-1  | *************************************************************
jenkins-jenkins-blueocean-1  | 
jenkins-jenkins-blueocean-1  | Jenkins initial setup is required. An admin user has been created and a password generated.
jenkins-jenkins-blueocean-1  | Please use the following password to proceed to installation:
jenkins-jenkins-blueocean-1  | 
jenkins-jenkins-blueocean-1  | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
jenkins-jenkins-blueocean-1  | 
jenkins-jenkins-blueocean-1  | This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
jenkins-jenkins-blueocean-1  | 
jenkins-jenkins-blueocean-1  | *************************************************************
jenkins-jenkins-blueocean-1  | *************************************************************
jenkins-jenkins-blueocean-1  | *************************************************************
jenkins-jenkins-blueocean-1  | 

Jenkinsの初期設定

ブラウザでhttp://[IPアドレス]:8080にアクセスします。
Administrator passwordを入力します。

image.png

Install suggested pluginsを選択します。

image.png

しばらく待ちます。

image.png

プラグインのインストールが完了するとAdmin Userの作成画面になりますので、ユーザーを作成します。

image.png

Jenkins URLを設定します。

image.png

Jenkins is ready!
初期設定は以上で完了です。

image.png

ジョブを作成

公式ドキュメントのUsing Docker with Pipelineを参考に進めます。

Create a jobをクリックします。

image.png

ジョブ名を入力しパイプラインを選択、OKボタンをクリックします。

image.png

パイプライン定義Pipeline scriptを選択し、Script欄に以下を入力します。

pipeline {
    agent any
    stages {
        stage('docker build') {
            agent {
                docker { 
                    image 'node:lts-alpine'
                }
            }
            steps {
                sh 'node -v'
            }
        }
    }
}

保存ボタンをクリックします。

Pipeline Syntaxからスクリプトに記載する内容の確認ができます。

ビルドの実行

ビルド実行をクリックします。

image.png

ビルド結果
Started by user jenkins
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/jenkins_home/workspace/job1
[Pipeline] {
[Pipeline] stage
[Pipeline] { (docker build)
[Pipeline] node
Running on Jenkins in /var/jenkins_home/workspace/[email protected]
[Pipeline] {
[Pipeline] isUnix
[Pipeline] sh
+ docker inspect -f . node:lts-alpine

Error: No such object: node:lts-alpine
[Pipeline] isUnix
[Pipeline] sh
+ docker pull node:lts-alpine
lts-alpine: Pulling from library/node
9b18e9b68314: Pulling fs layer
d4bbdc18f3e0: Pulling fs layer
bb54f68a6fc3: Pulling fs layer
6f6c5903b8c0: Pulling fs layer
6f6c5903b8c0: Waiting
9b18e9b68314: Verifying Checksum
9b18e9b68314: Download complete
bb54f68a6fc3: Verifying Checksum
bb54f68a6fc3: Download complete
9b18e9b68314: Pull complete
6f6c5903b8c0: Verifying Checksum
6f6c5903b8c0: Download complete
d4bbdc18f3e0: Download complete
d4bbdc18f3e0: Pull complete
bb54f68a6fc3: Pull complete
6f6c5903b8c0: Pull complete
Digest: sha256:2c405ed42fc0fd6aacbe5730042640450e5ec030bada7617beac88f742b6997b
Status: Downloaded newer image for node:lts-alpine
docker.io/library/node:lts-alpine
[Pipeline] withDockerContainer
Jenkins seems to be running inside container 7c1ed956707f43bcc5619c86e56d764cefa5d8048a7fcbfe8d3f965cac434077
but /var/jenkins_home/workspace/[email protected] could not be found among []
but /var/jenkins_home/workspace/[email protected]@tmp could not be found among []
$ docker run -t -d -u 1000:1000 -w /var/jenkins_home/workspace/[email protected] -v /var/jenkins_home/workspace/[email protected]:/var/jenkins_home/workspace/[email protected]:rw,z -v /var/jenkins_home/workspace/[email protected]@tmp:/var/jenkins_home/workspace/[email protected]@tmp:rw,z -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** node:lts-alpine cat
$ docker top 271432717ae56100564239d332d6b93af6de373b560e8fdf213ac84678046c0b -eo pid,comm
[Pipeline] {
[Pipeline] sh
+ node -v
v16.17.0
[Pipeline] }
$ docker stop --time=1 271432717ae56100564239d332d6b93af6de373b560e8fdf213ac84678046c0b
$ docker rm -f 271432717ae56100564239d332d6b93af6de373b560e8fdf213ac84678046c0b
[Pipeline] // withDockerContainer
[Pipeline] }
[Pipeline] // node
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

ログがたくさん出ますが、node -vの実行結果が出力されています。

v16.17.0

Dockerコンテナ内で処理を実行することができました。

ジョブを作成(応用編)

GitHubからPullしたソースに対してビルドしてみましょう。

  1. ビルドステージを追加
    Stagesの中にStageを追加します。(docker buildの前に追加)
  2. docker buildステージのStepsnpm inpm run buildに変更
pipeline {
    agent any
    stages {
        stage('Git') {
            steps {
                git branch: 'main', url: 'https://github.com/begin-examples/node-create-react-app.git'
            }
        }
        stage('docker build') {
            agent {
                docker { 
                    image 'node:16.13.1-alpine'
                }
            }
            steps {
                sh 'npm i'
                sh 'npm run build'
            }
        }
    }
}

https://github.com/begin-examplesは様々なサンプルプロジェクトを公開してくれています。

この状態でビルド実行してみます。

ビルド結果
Started by user jenkins
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/jenkins_home/workspace/job1
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Git)
[Pipeline] git (hide)
The recommended git tool is: NONE
No credentials specified
Cloning the remote Git repository
Cloning repository https://github.com/begin-examples/node-create-react-app.git
 > git init /var/jenkins_home/workspace/job1 # timeout=10
Fetching upstream changes from https://github.com/begin-examples/node-create-react-app.git
 > git --version # timeout=10
 > git --version # 'git version 2.30.2'
 > git fetch --tags --force --progress -- https://github.com/begin-examples/node-create-react-app.git +refs/heads/*:refs/remotes/origin/* # timeout=10
 > git config remote.origin.url https://github.com/begin-examples/node-create-react-app.git # timeout=10
 > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
Avoid second fetch
 > git rev-parse refs/remotes/origin/main^{commit} # timeout=10
Checking out Revision 078c613e98e7aaa1fdc29305f37edfd86417c44e (refs/remotes/origin/main)
 > git config core.sparsecheckout # timeout=10
 > git checkout -f 078c613e98e7aaa1fdc29305f37edfd86417c44e # timeout=10
 > git branch -a -v --no-abbrev # timeout=10
 > git checkout -b main 078c613e98e7aaa1fdc29305f37edfd86417c44e # timeout=10
Commit message: "Merge pull request #169 from begin-examples/dependabot/npm_and_yarn/architect/sandbox-5.2.4"
First time build. Skipping changelog.
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (docker build)
[Pipeline] node
Running on Jenkins in /var/jenkins_home/workspace/[email protected]
[Pipeline] {
[Pipeline] isUnix
[Pipeline] sh
+ docker inspect -f . node:16.13.1-alpine

Error: No such object: node:16.13.1-alpine
[Pipeline] isUnix
[Pipeline] sh
+ docker pull node:16.13.1-alpine
16.13.1-alpine: Pulling from library/node
9b3977197b4f: Pulling fs layer
704435388496: Pulling fs layer
ed62d339f639: Pulling fs layer
9b944dbd96a5: Pulling fs layer
9b944dbd96a5: Waiting
ed62d339f639: Verifying Checksum
ed62d339f639: Download complete
9b3977197b4f: Verifying Checksum
9b3977197b4f: Download complete
9b3977197b4f: Pull complete
9b944dbd96a5: Verifying Checksum
9b944dbd96a5: Download complete
704435388496: Download complete
704435388496: Pull complete
ed62d339f639: Pull complete
9b944dbd96a5: Pull complete
Digest: sha256:0e071f3c5c84cffa6b1035023e1956cf28d48f4b36e229cef328772da81ec0c5
Status: Downloaded newer image for node:16.13.1-alpine
docker.io/library/node:16.13.1-alpine
[Pipeline] withDockerContainer
Jenkins seems to be running inside container 7c1ed956707f43bcc5619c86e56d764cefa5d8048a7fcbfe8d3f965cac434077
but /var/jenkins_home/workspace/[email protected] could not be found among []
but /var/jenkins_home/workspace/[email protected]@tmp could not be found among []
$ docker run -t -d -u 1000:1000 -w /var/jenkins_home/workspace/[email protected] -v /var/jenkins_home/workspace/[email protected]:/var/jenkins_home/workspace/[email protected]:rw,z -v /var/jenkins_home/workspace/[email protected]@tmp:/var/jenkins_home/workspace/[email protected]@tmp:rw,z -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** node:16.13.1-alpine cat
$ docker top 39f23aa21e75fb0b52ae3090dddb3ed44e794c9d3e9743eb85afe40cc20200c0 -eo pid,comm
[Pipeline] {
[Pipeline] sh
+ npm i
npm ERR! code ENOENT
npm ERR! syscall open
npm ERR! path /var/jenkins_home/workspace/[email protected]/package.json
npm ERR! errno -2
npm ERR! enoent ENOENT: no such file or directory, open '/var/jenkins_home/workspace/[email protected]/package.json'
npm ERR! enoent This is related to npm not being able to find a file.
npm ERR! enoent 

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/node/.npm/_logs/2022-09-20T13_02_25_666Z-debug.log
[Pipeline] }
$ docker stop --time=1 39f23aa21e75fb0b52ae3090dddb3ed44e794c9d3e9743eb85afe40cc20200c0
$ docker rm -f 39f23aa21e75fb0b52ae3090dddb3ed44e794c9d3e9743eb85afe40cc20200c0
[Pipeline] // withDockerContainer
[Pipeline] }
[Pipeline] // node
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
ERROR: script returned exit code 254
Finished: FAILURE

結果:失敗

npm ino such file or directory, open '/var/jenkins_home/workspace/[email protected]/package.json'でエラーとなっています。

JenkinsはStageごとに違うディレクトリでビルドを行います。
ジョブ実行後のWorkspaceの状態を見るとjob1ディレクトリ(1つ目のStage)と[email protected]ディレクトリ(2つ目のStage)があることがわかります。

image.png

同一のディレクトリでビルドがしたい場合はreuseNode trueを指定する必要があります。

            agent {
                docker { 
                    image 'node:16.13.1-alpine'
                    reuseNode true
                }
            }

上記修正を行い、再度ビルド実行します。

ビルド結果
Started by user jenkins
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/jenkins_home/workspace/job1
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Git)
[Pipeline] git
The recommended git tool is: NONE
No credentials specified
 > git rev-parse --resolve-git-dir /var/jenkins_home/workspace/job1/.git # timeout=10
Fetching changes from the remote Git repository
 > git config remote.origin.url https://github.com/begin-examples/node-create-react-app.git # timeout=10
Fetching upstream changes from https://github.com/begin-examples/node-create-react-app.git
 > git --version # timeout=10
 > git --version # 'git version 2.30.2'
 > git fetch --tags --force --progress -- https://github.com/begin-examples/node-create-react-app.git +refs/heads/*:refs/remotes/origin/* # timeout=10
 > git rev-parse refs/remotes/origin/main^{commit} # timeout=10
Checking out Revision 078c613e98e7aaa1fdc29305f37edfd86417c44e (refs/remotes/origin/main)
 > git config core.sparsecheckout # timeout=10
 > git checkout -f 078c613e98e7aaa1fdc29305f37edfd86417c44e # timeout=10
 > git branch -a -v --no-abbrev # timeout=10
 > git branch -D main # timeout=10
 > git checkout -b main 078c613e98e7aaa1fdc29305f37edfd86417c44e # timeout=10
Commit message: "Merge pull request #169 from begin-examples/dependabot/npm_and_yarn/architect/sandbox-5.2.4"
 > git rev-list --no-walk 078c613e98e7aaa1fdc29305f37edfd86417c44e # timeout=10
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (docker build)
[Pipeline] getContext
[Pipeline] isUnix
[Pipeline] sh
+ docker inspect -f . node:16.13.1-alpine
.
[Pipeline] withDockerContainer
Jenkins seems to be running inside container 7c1ed956707f43bcc5619c86e56d764cefa5d8048a7fcbfe8d3f965cac434077
but /var/jenkins_home/workspace/job1 could not be found among []
but /var/jenkins_home/workspace/[email protected] could not be found among []
$ docker run -t -d -u 1000:1000 -w /var/jenkins_home/workspace/job1 -v /var/jenkins_home/workspace/job1:/var/jenkins_home/workspace/job1:rw,z -v /var/jenkins_home/workspace/[email protected]:/var/jenkins_home/workspace/[email protected]:rw,z -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** node:16.13.1-alpine cat
$ docker top 25f9603bb345bfb64b6757bc5127dd36a50e8ca90eba5a5abff79bdf6bae8c1d -eo pid,comm
[Pipeline] {
[Pipeline] sh
+ npm i
npm WARN deprecated [email protected]: Please see https://github.com/lydell/urix#deprecated
npm WARN deprecated [email protected]: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated [email protected]: some dependency vulnerabilities fixed, support for node < 10 dropped, and newer ECMAScript syntax/features added
npm WARN deprecated [email protected]: https://github.com/lydell/resolve-url#deprecated
npm WARN deprecated [email protected]: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-babel.
npm WARN deprecated [email protected]: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm WARN deprecated [email protected]: flatten is deprecated in favor of utility frameworks such as lodash.
npm WARN deprecated [email protected]: This SVGO version is no longer supported. Upgrade to v2.x.x.
npm WARN deprecated @hapi/[email protected]: This version has been deprecated and is no longer supported or maintained
npm WARN deprecated [email protected]: babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.
npm WARN deprecated @hapi/[email protected]: This version has been deprecated and is no longer supported or maintained
npm WARN deprecated @hapi/[email protected]: Moved to 'npm install @sideway/address'
npm WARN deprecated @hapi/[email protected]: This version has been deprecated and is no longer supported or maintained
npm WARN deprecated [email protected]: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.
npm WARN deprecated [email protected]: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.
npm WARN deprecated [email protected]: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated @hapi/[email protected]: Switch to 'npm install joi'
npm WARN deprecated [email protected]: [email protected]<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.

added 2043 packages, and audited 2044 packages in 3m

178 packages are looking for funding
  run `npm fund` for details

39 vulnerabilities (8 moderate, 22 high, 9 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.
[Pipeline] sh
+ npm run build

> [email protected] build
> react-scripts build

Creating an optimized production build...
Browserslist: caniuse-lite is outdated. Please run:
  npx [email protected] --update-db
  Why you should do it regularly: https://github.com/browserslist/browserslist#browsers-data-updating
Compiled successfully.

File sizes after gzip:

  43.97 KB  build/static/js/2.74c8cf8f.chunk.js
  884 B     build/static/js/main.c65693da.chunk.js
  775 B     build/static/js/runtime-main.50b1cbe5.js
  527 B     build/static/css/main.72f2f873.chunk.css

The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.

The build folder is ready to be deployed.
You may serve it with a static server:

  npm install -g serve
  serve -s build

Find out more about deployment here:

  https://cra.link/deployment

[Pipeline] }
$ docker stop --time=1 25f9603bb345bfb64b6757bc5127dd36a50e8ca90eba5a5abff79bdf6bae8c1d
$ docker rm -f 25f9603bb345bfb64b6757bc5127dd36a50e8ca90eba5a5abff79bdf6bae8c1d
[Pipeline] // withDockerContainer
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

成功しました。

GitHubから取得したソースをDockerコンテナ内でビルドすることができました。
この手順のあとにデプロイする手順を追加すれば良さそうです。