orangain flavor

じっくりコトコト煮込んだみかん2。知らないことを知りたい。

Dockerを使ってJenkinsのジョブごとにテスト実行環境を分離する

はじめに

JenkinsでJVM上で動かない言語(PythonRubyなど*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_NAMEWORKSPACE は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 builddocker runでエラーが発生しても終了コードが0になるため、エラーを検知できませんでした。

そのうち直るようですが、現時点ではコンソールのログをスキャンするなど、コンソールの出力に気をつけたほうがよいでしょう。

まとめ

Dockerを使うと、C拡張ライブラリを使っている場合にも、Jenkinsでジョブごとに環境を分離することが簡単にできました。

多数のユーザーが利用する前提の Travis CI なんかでは普通にできることですが、Jenkinsでも気軽にできるのは喜ばしいことです。

プラグインを作って、ビルド手順で「Dockerのコンテナ内で実行する」みたいに設定できるといいかもしれませんね。そのような動きはあるようですが、現時点ではJenkins公式のプラグインページにDockerのプラグインは存在しません。

なお、説明に利用したソースコードorangain/jenkins-docker-sample に置いてあります。

参考サイト

*1:JVM上で動く実装ではないやつです

*2:Jenkinsが元々ターゲットとしているJavaなどのJVM上で動く言語では、ライブラリをダウンロードして配置するだけで利用できるため、このような問題は起きません。