システム統括本部基盤システム開発本部 須田(@superbrothers)です。第8回Jenkins勉強会 で「Jenkins with Docker」というタイトルで LT に参加しました。
LT の中で触れた環境を構築するデモコードを Vagrantfile にまとめて GitHub においていますのでよければ触ってみてください。ジョブ登録済の Jenkins が立ち上がるので全く同じ環境を試してもらえます。
LT は5分でざっと流してしまったため、このエントリで補足します。
ジョブ実行毎にクリーンな環境がほしい
特に説明の必要もなく普段 Jenkins を使っていればジョブ毎にクリーンな環境がほしいと思うはずです。スレーブノードをジョブ毎に新規でインスタンスを立ちあげて実行することもできますが インスタンスの作成、起動はそれなりの時間がかかります。事前に作っておくことでその時間を省略することもできますが、利用していないインスタンスを複数作成しておく必要もあり微妙です。
Docker をはじめて知ったときに最初に考えたのが Jenkins のジョブ実行環境として使ってみようというものでした。Docker は Jail のようなものなので chroot に近い使い勝手で通常のプロセスを立ち上げるように高速にコンテナを立ち上げることができます。
スレーブノードから Docker コンテナを作成する
今のところしっくりきているのは Jenkins マスタから Docker がインストールされたスレーブノードに対してジョブを実行し、ジョブの中で Docker を使ってコンテナを作成する方法です。コンテナで sshd を立ちあげておき、コンテナ自体をスレーブノードとして扱うこともできましたが、Docker の使い方としてはあまり筋がよくないと感じたのでやめました。
# jenkins-with-docker/nodejs
FROM ubuntu:12.04
RUN apt-get -q update; apt-get -y upgrade
RUN apt-get -y install build-essential sudo git-core
# NVM インストール
RUN git clone --depth 1 https://github.com/creationix/nvm.git ~/.nvm
# nodejs インストール
RUN bash -c ". /.nvm/nvm.sh; nvm install 0.10"
# スクリプト実行ユーザの作成
RUN useradd -u 45678 -s /bin/bash -m worker
# ワークスペースのマウント先を作成
RUN mkdir /workspace
ENTRYPOINT ["/bin/bash", "-c"]
これは nodejs のテスト実行用の Dockerfile の例です。nvm で nodejs をインストールし、ジョブ実行用のユーザを作っています。
docker run \
-v ${WORKSPACE}:/workspace \ # Workspace をマウント
-w /workspace \ # Working Directory を指定
-u worker \ # 実行ユーザを指定
-e HOME=/home/worker \ # 環境変数を指定
jenkins-with-docker/nodejs "$(cat <<EOL # イメージを指定
source /.nvm/nvm.sh # 実行スクリプトを指定
nvm use 0.10
npm install
npm test
EOL
)"
ジョブではワークスペースをコンテナ内にマウントし、npm install; npm test
を実行するコンテナをジョブ毎に作成します。実行ログは標準出力に出力されます。これでジョブ毎にクリーンな環境を作成し、使い終わったら停止することができました。
travis-ci のように複数バージョンのテストを同時に実行してみる
上の例では一回のジョブの実行にひとつのコンテナを使うだけでしたが、少し工夫すると複数バージョンのテストを同時に実行することもできます。
# Install node
RUN bash -c ". /.nvm/nvm.sh; nvm install 0.8"
RUN bash -c ". /.nvm/nvm.sh; nvm install 0.10"
RUN bash -c ". /.nvm/nvm.sh; nvm install 0.11"
Dockerfile を修正して3つのバージョンを入れておきます。ここでは nodejs の 0.8, 0.10, 0.11 を入れています。
#!/usr/bin/env bash
set -e
VERSIONS=(0.8 0.10 0.11)
for ((i = 0; i < "${#VERSIONS[@]}"; i++)); do
VERSION="${VERSIONS[$i]}"
# ワークスペースをビルド番号、言語バージョン単位で複製する
_WORKSPACE="${WORKSPACE}.${BUILD_NUMBER}.${VERSION}"
cp -R "${WORKSPACE}" "${_WORKSPACE}"
# 今回はコンテナは -d オプションを使いバッググラウンドで作成、実行する。-d オプションを使うと docker run はコンテナ ID を出力する。
CONTAINER_ID=$(docker run -d -v "${_WORKSPACE}":/workspace -w /workspace -u worker -e "HOME=/home/worker" jenkins-with-docker/nodejs "$(cat <<EOL
source /.nvm/nvm.sh
# バージョンを指定する
nvm use "${VERSION}"
npm install
npm test
EOL
)"
)
echo "-----> Started Build: ${VERSION}"
echo "CONTAINER_ID: ${CONTAINER_ID}"
echo "WORKSPACE: ${_WORKSPACE}"
echo
# コンテナ ID をスペース区切りでまとめておく
CONTAINER_IDS="${CONTAINER_IDS} ${CONTAINER_ID}"
done
# docker wait は複数のコンテナ ID を受け取り、全てのコンテナが終了ステータスを返すまで待つことができ、コンテナ ID の引数順に終了ステータスを出力する。
EXIT_STATUSES="$(docker wait ${CONTAINER_IDS})"
ジョブスクリプトが少し複雑になりましたが、流れはスクリプトのコメントを参照していただければ把握できると思います。言語のバージョンごとにコンテナをバッググランドで実行し、docker wait
で作成した全てのコンテナの終了を待ちます。コンテナごとの終了ステータスを取得できるのでコンテナごとにテストが成功、失敗がわかります。実行したテストのログはスクリプト例が長くなってしまうためここでは出力していませんが、docker logs <CONTAINER-ID>
で出力できます。
travis-ci のように複数バージョンの同時テストが Docker を利用することで簡単に実現できました。実際の動作はデモコードで試すことができますのでぜひ触ってみてください。
まとめ
ここでは Jenkins と Docker を組み合わせると常にクリーンな環境でジョブが実行できるということを紹介しました。スレーブノードを多く用意できない環境においてもジョブごとに Docker イメージを用意しておくことでそのジョブ専用のスレーブノードを用意しているのと同じことが少ないノードで実現が可能です。
継続的デリバリー方面でも Docker を使った方法を考えているのでそちらの話もどこかでできるかもしれません。
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました