Dockerを使ってJenkinsのジョブごとにテスト実行環境を分離する
はじめに
JenkinsでJVM上で動かない言語(PythonやRubyなど*1)を使っていると、ジョブごとに環境が分離されていないことが問題になる場合があります。
Pythonにおける virtualenv やRubyにおける Bundler を使えば、ジョブごとに利用するライブラリを分離することができます。しかし、C拡張ライブラリをインストールするためには、ジョブが実行されるノードに開発用のファイルが存在している必要があります。例えば、Pythonモジュールの lxml のインストールにはlibxml2やlibxsltの開発用ファイルが必要です。 *2
このようなファイルが必要になるたびにJenkinsのノードにインストールするのはスマートじゃないですし、実行に必要な環境はコードの形で明文化されているべきです。
ジョブでaptやyumを使ってインストールするのもセキュアじゃないですし、ジョブ同士で必要なパッケージが衝突する可能性もあります。ジョブごとにスレーブを立てて、Chefなどで設定するという方法も考えられますが、ジョブの数が増えると大変です。
そこで、今回は Docker を使ってこの問題を解決します。Dockerでジョブごとに仮想環境を作り、その中でテストを実行するのです。
Dockerとは
いろいろなところで紹介されているので詳しくは説明しませんが、 LXC と aufs を組み合わせた仮想化ソフトウェアです。
Dockerfile
にコマンドを書いてビルドすると、コマンドが実行された状態のマシンイメージが作られます。イメージはキャッシュされるため、次回からは一瞬で同じ状態のマシンを起動できます。
このため、ChefやPuppetのように頑張ってあるべき姿を維持するアプローチではなく、デプロイの度に環境を毎回1から作り直すというアプローチが採れるようになっています。
設定手順
1. Dockerを利用できるスレーブを準備する
適当なマシンにDockerをインストールして、このマシンをJenkinsのスレーブにします。
もちろんJenkinsがインストールされた既存のマシンにDockerをインストールしても、新しくマスターのJenkinsを立てても構いません。
Jenkinsのユーザーがsudo
なしでdocker
のコマンドを使えるように、Jenkinsのユーザーをdocker
グループに加えます。
スレーブにはdocker
のようなラベルをつけておくとよいでしょう。
2. ジョブを作成する
Jenkinsでフリースタイルプロジェクトのジョブを作成します。
実行するノードを制限
ラベル式で、このジョブがDockerをインストールしたスレーブで実行されるように設定します。
ビルド手順
ジョブのビルド手順に「シェルの実行」を追加し、以下のスクリプトを実行するようにします。
IMAGE=$JOB_NAME docker build -t $IMAGE $WORKSPACE docker run -v $WORKSPACE:/workspace -w /workspace $IMAGE /bin/sh -ex docker_tests.sh
環境変数 JOB_NAME
と WORKSPACE
はJenkinsから与えられるもので、ジョブの名前とワークスペースのフルパスを表します。
docker build
では、ワークスペースにある Dockerfile
をビルドしたイメージにジョブ名をつけて保存します。
docker run
では、仮想環境の中で後述する docker_tests.sh
を実行し、テストを行います。
オプションの意味は以下の通りです。
-v
: ジョブのワークスペースを/workspace
としてマウントする-r
: カレントディレクトリを/workspace
にする
ビルド後の手順
ビルド後の手順では、通常のジョブと同じようにユニットテストの結果を集計したりします。
3. ビルドするコードに2つのファイルを追加する
Dockerfile
Dockerfile
ではビルドする環境を定義します。この例ではPythonをインストールした後、lxmlをビルドできるようにlibxml2-devとlibxslt-devをインストールしています。
FROM ubuntu:quantal RUN echo "deb http://ja.archive.ubuntu.com/ubuntu/ quantal universe" >> /etc/apt/sources.list RUN echo "deb http://ja.archive.ubuntu.com/ubuntu/ quantal-updates universe" >> /etc/apt/sources.list RUN apt-get -y update RUN apt-get -y install build-essential curl git RUN apt-get -y install python3 python3-pip python3-dev RUN apt-get -y install libxml2-dev libxslt-dev RUN pip-3.2 install virtualenv
docker_tests.sh
docker_tests.sh
ではテストを実行します。この例では、リポジトリに含まれるrequirements.txt
を参照してライブラリをインストールしてからテストを実行しています。
このスクリプトはDockerの仮想環境内で実行されますが、上述のようにJenkinsのワークスペースをマウントしているため、シームレスにファイルを利用・保存することができます。
# Create virtualenv virtualenv venv # Activate virtualenv . venv/bin/activate # Install libraries pip install -r requirements.txt # Remove files generated in the previous build rm -f .coverage coverage.xml nosetests.xml # Execute tests nosetests --with-doctest --with-xunit --with-coverage --cover-xml --cover-package=config_reader
注意点
今回試したDocker 0.6.3では、docker build
やdocker run
でエラーが発生しても終了コードが0になるため、エラーを検知できませんでした。
そのうち直るようですが、現時点ではコンソールのログをスキャンするなど、コンソールの出力に気をつけたほうがよいでしょう。
- Docker commands should return nonzero error codes on failure · Issue #354 · dotcloud/docker
- docker build returns 0 exit code even when build fails · Issue #1150 · dotcloud/docker
まとめ
Dockerを使うと、C拡張ライブラリを使っている場合にも、Jenkinsでジョブごとに環境を分離することが簡単にできました。
多数のユーザーが利用する前提の Travis CI なんかでは普通にできることですが、Jenkinsでも気軽にできるのは喜ばしいことです。
プラグインを作って、ビルド手順で「Dockerのコンテナ内で実行する」みたいに設定できるといいかもしれませんね。そのような動きはあるようですが、現時点ではJenkins公式のプラグインページにDockerのプラグインは存在しません。
なお、説明に利用したソースコードは orangain/jenkins-docker-sample に置いてあります。
参考サイト
- 仮想環境構築に docker を使う - apatheia.info
- Docker (土曜日に podcast します) - naoyaのはてなダイアリー
- Docker の Remote API + serverspec で CI - naoyaのはてなダイアリー
- Creating immutable servers with chef and docker.io - tech.paulcz.net
- Docker + Jenkins + serverspecでpuppetのmanifestをCIする
- MacOSX - OSX, Vagrant, VirtualBoxでdockerを試す - Qiita [キータ]