令和元年に送る!Docker for WindowsでのJenkins(Docker in Docker)環境構築ハンズオン


はじめに

元号が変わり、2020年を迎えるにあたって「CI環境も存在しない開発体制をこの先も続けていくつもりなのか?」と悶々とする日々から脱却すべく「JenkinsのパイプラインでDockerfileを指定してビルド実行」を行うための環境構築に試行錯誤をした成果を紹介します。

目的

  • ゼロからDocker入りJenkinsコンテナの立ち上げまでを、最小手順で実現する
  • 手順の意図を理解する

以上の2点を目指しています。

私自身、DockerもJenkinsも最低限の知識しか持ち合わせておらず、結論に到達するまでに数多くのWebページを駆けずり回ることとなりました。
この記事は、まさしく「一ヶ月前の私がほしかったモノ」です。

動作確認環境

  • Windows10 Pro 1903 - 64bit
  • Docker Desktop for Windows 2.1.0.5 (stable)

1. Docker入りJenkinsコンテナの構築〜立ち上げ

1.1. Dockerfile 作成

Dockerfile.sample
# 1.1.1.
FROM jenkins:2.60.3

# 1.1.2.
USER root

# 1.1.3.
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=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" \
    && apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

# 1.1.4.
RUN usermod -aG docker jenkins

# 1.1.5.
RUN cd /tmp/ \
    && wget https://updates.jenkins-ci.org/latest/jenkins.war \
    && mv ./jenkins.war /usr/share/jenkins/

# 1.1.6.
USER jenkins

1.1.1. ベースイメージ指定

ここでは奇をてらわずに、Jenkinsの公式イメージを指定しています。
タグについては、今後の更新で手順が変更になる可能性を考慮して、投稿時点で最新の 2.60.3 を明示的に指定しています。

1.1.2. ユーザー変更(root <- jenkins

jenkinsイメージ内で、ユーザーが jenkins に変更されています。
jenkins ユーザーはroot権限を持たないため、以降のコマンドを実行できるよう一時的に root ユーザーに変更します。

1.1.3. Dockerインストール

Get Docker Engine - Community for Debian | Docker Documentationを参考に、Dockerエンジンを導入します。
インストール後には、Dockerfileのお作法としてキャッシュを削除しておきます。

余談ですが、 curl で指定するURLは、Docker v17.09以前のドキュメントでは $(. /etc/os-release; echo "$ID") の結果を参照していました。
ホスト環境がRaspberry Piの場合等、URLにしたい文字列と $(. /etc/os-release; echo "$ID") の結果とが一致しないケースがあることから、最新のドキュメントでは見直されているのではないかと想像します。 ※要出典

1.1.4. dockerjenkins ユーザーのセカンダリグループとして追加

Post-installation steps for Linux | Docker Documentationを参考に、jenkins ユーザーが sudo を指定せずに docker コマンドを実行するための準備としてグループ設定を行います。

1.1.5. Jenkinsアップデート

jenkins:2.60.3 の最終更新日は、投稿時点で一年以上前となっていることから、最新のJenkinsを利用するために、アップデートします。

1.1.6. ユーザー変更(jenkins <- root

一時的に変更したユーザーをもとに戻します。

1.2. docker-compose.yml 作成

docker-compose.yml
version: "3"

services:
    jenkins:
        build: .
        ports:
            - 8080:8080
        volumes:
# 1.2.1.
            - ./jenkins_home:/var/jenkins_home
# 1.2.2.
            - /var/run/docker.sock:/var/run/docker.sock:rw

1.2.1. ホスト環境との共有ディレクトリの指定

JenkinsコンテナにホストPC上で作成した Dockerfile を送り込めるように、Jenkinsのホームディレクトリを共有ディレクトリ化します。

1.2.2. Dockerデーモンの共有

Dockerコンテナ内でDockerコンテナを動かすために、ホストPCのDockerソケットを共有します。
※この手順については Dockerコンテナ内からDockerを使うことについて - Qiita の記事が非常に参考になりました。

1.3. .env 作成(しません)

Docker for WindowsでDockerデーモンを共有するために .env を作成して COMPOSE_CONVERT_WINDOWS_PATHS=1 を指定する必要がある、という記事を目にしましたが、私の環境では作成せずに進めることができました。

.env
COMPOSE_CONVERT_WINDOWS_PATHS=1

1.4. イメージ構築~コンテナ作成~バックグラウンド実行

カレントディレクトリに先に示した Dockerfiledocker-compose.yml とを配置して、コマンドプロンプトから以下のコマンドを実行します。

$ docker-compose up --build -d && docker-compose exec -u root jenkins chown root:docker /var/run/docker.sock

Docker for Windowsでは(?) docker グループに /var/run/docker.sock へのアクセス権限が割り当てられていないようです。
そのため、コンテナのバックグラウンド実行後にグループ所有権を設定します。

※コマンドプロンプト以外のCUIツールを仕様した場合、パスの解決がうまくいかずに chown root:docker /var/run/docker.sock を実行できないケースがあるようです。私はこれにハマって半日つぶしました。

2. Jenkinsコンテナ上でDocker Pipeline Pluginの登録〜実行

2.1. Jenkinsセットアップ

任意のWebブラウザで localhost:8080 へアクセスし、正規の手順でセットアップします。
※以下、テキストベースで説明していますが、他に詳しく正確で分かりやすい記事が山のようにありますので、そちらも参考にしてください。

2.1.1. Unlock Jenikins

いきなり「Administrator password」を求められます。
ここには docker-compose logs -f jenkins を実行して、以下のとおり出力されるパスワードを入力します。

...
jenkins_1  | 2019-12-15 08:10:47.628+0000 [id=30]       INFO    jenkins.install.SetupWizard#init:
jenkins_1  |
jenkins_1  | *************************************************************
jenkins_1  | *************************************************************
jenkins_1  | *************************************************************
jenkins_1  |
jenkins_1  | Jenkins initial setup is required. An admin user has been created and a password generated.
jenkins_1  | Please use the following password to proceed to installation:
jenkins_1  |
jenkins_1  | <Administrator password>
jenkins_1  |
jenkins_1  | This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
jenkins_1  |
jenkins_1  | *************************************************************
jenkins_1  | *************************************************************
jenkins_1  | *************************************************************
jenkins_1  |
jenkins_1  | --> setting agent port for jnlp
jenkins_1  | --> setting agent port for jnlp... done
...

2.1.2. Customize Jenkins

特にこだわりがなければ「Install suggested plugins」を選択して問題ないでしょう。

2.1.3. Gettings Started

オールグリーンになるまで数分の休憩です。

2.1.4. Create First Addmin User

初期ユーザの情報を求められるので、適宜入力欄を埋めます。

2.1.5. Instance Configuration

JenkinsのURLを指定することができます。
特にこだわりがなければデフォルトの http://localhost:8080/ で問題ないでしょう。

2.1.6. Jenkins is ready!

これでJenkinsのセットアップです!

2.2. パイプライン登録

以下、「sample」という名前のジョブを作成し、Dockerfileを指定してコンソールに「hello world.」と表示する例における手順です。

2.2.1. 下準備

  1. 以下内容の Dockerfile を作成
Dockerfile
FROM alpine:latest
  1. ~/jenkins_home/workspace/sample/ ディレクトリを作成し、配下に 1. のファイルを配置

2.2.2. WEB画面操作

  1. ホーム画面「新規ジョブ作成」をクリック
  2. 「Enter an item name」に「sample」と入力して「パイプライン」を選択し、「OK」をクリック
  3. Generalタブ「パイプライン」「Pipeline script」を選択して「Script」に以下の内容を入力し、「保存」をクリック
Script
node {
    docker.build("sample-${BUILD_ID}", "-f Dockerfile .").inside() {
        sh 'echo "Hello World!"'  
    }
}

2.3. Docker Pipeline Plugin実行〜結果確認

2.2. で作成したジョブ画面の「ビルド実行」をクリックします。
今回のサンプルでは、コンソールに Hello World! と出力するだけなので、ビルド履歴画面「Console Output」から、以下のような出力がなされていれば成功です!

コンソール出力
Started by user admin
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/jenkins_home/workspace/sample
[Pipeline] {
[Pipeline] isUnix
[Pipeline] sh
+ docker build -t sample-9 -f Dockerfile .
Sending build context to Docker daemon  2.048kB

Step 1/1 : FROM alpine:latest
 ---> 965ea09ff2eb
Successfully built 965ea09ff2eb
Successfully tagged sample-9:latest
[Pipeline] isUnix
[Pipeline] sh
+ docker inspect -f . sample-9
.
[Pipeline] withDockerContainer
Jenkins seems to be running inside container 0fb06afff12dcba3803da391819176848ca54934737d486d99a72210b37c9955
$ docker run -t -d -u 1000:1000 -w /var/jenkins_home/workspace/sample --volumes-from 0fb06afff12dcba3803da391819176848ca54934737d486d99a72210b37c9955 -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** sample-9 cat
$ docker top 3702a7909fdacf3b77b04edcd381dba1309c8c9d0991def0ab1e392e07eb56b2 -eo pid,comm
[Pipeline] {
[Pipeline] sh
+ echo 'Hello World!'
Hello World!
[Pipeline] }
$ docker stop --time=1 3702a7909fdacf3b77b04edcd381dba1309c8c9d0991def0ab1e392e07eb56b2
$ docker rm -f 3702a7909fdacf3b77b04edcd381dba1309c8c9d0991def0ab1e392e07eb56b2
[Pipeline] // withDockerContainer
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

おわりに

0 -> 1のステップを超えるためには多大な労力を要します(要しました)。
この記事が、第二、第三のワタシの労力を減らすお役にたつことががあれば幸いです。

参考

Docker関連

Jenkins(Docker Pipeline Plugin)関連

CIツール選定