JenkinsとDockerを使い、動作環境の異なるアプリをCIする
私が属する組織では受託開発がメインでして、サーバーやミドルウェアなどの要件を我々が自由に決定できないケースもしばしば。
基本はPHPなのですが、案件によってバージョンが異なったり、近頃ではNodeの案件なども出てきました。
長いこと「CIサーバを導入したい」と思いつつもアプリ毎にCIサーバを立ち上げるのもメンドイコスト等の関係で現実的ではないので、頭を悩ませていました。
そんな中、Dockerを使って解決している先人がいましたので、早速、試してみました。
参考サイト
目次
- 前提環境
- 先人の知恵まとめ
- 実際の手順
- 今後の課題など
長いです。。。
先人の知恵まとめ
いろいろと調べていたところ、以下のようなポイントがあることがわかりました。
JenkinsユーザーをDockerグループに所属させる
→JenkinsのジョブでDockerコンテナを操作できるようにするため
JenkinsのworkspaceをDockerにマウントする
→JenkinsとDockerとでシームレスにファイルを利用できるようにするため
Dockerfileはアプリに同梱しておく
→だって開発中にインフラ要件変わるかもでしょ?
アプリ毎に異なる初期設定やテストの手順はスクリプトにまとめてアプリに同梱しておく
→Jenkinsのビルド手順を共通化するため
実際の手順
1. VagrantでJenkins + Dockerのサーバーを用意する
JenkinsとDockerを導入したCentOS6.5をVagrantで構築します。
$ vagrant box add centos65-x86_64-20140116 https://github.com/2creatives/vagrant-centos/releases/download/v6.5.3/centos65-x86_64-20140116.box $ vagrant init centos65-x86_64-20140116
作成されたVagrantfileを編集します。
- ホストオンリーネットワークを有効にする
config.vm.network "private_network", ip: "192.168.33.10"
- 初期設定およびJenkinsとDockerを導入するプロビジョニングコードを記述する
config.vm.provision "shell", inline: <<-SHELL sudo cp -p /usr/share/zoneinfo/Asia/Tokyo /etc/localtime sudo sh -c 'echo ZONE=\"Asia/Tokyo\" > /etc/sysconfig/clock' sudo sh -c 'echo UTC=\"false\" >> /etc/sysconfig/clock' sudo sh -c "source /etc/sysconfig/clock" sudo yum update -y sudo yum install -y wget sudo rpm -ivh http://ftp.jaist.ac.jp/pub/Linux/Fedora/epel/6/i386/epel-release-6-8.noarch.rpm sudo yum install -y docker-io sudo chkconfig docker on sudo yum install -y java-1.7.0-openjdk sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo sudo rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key sudo yum install -y jenkins sudo chkconfig jenkins on sudo sed -i -e 's@JENKINS_ARGS=""@JENKINS_ARGS="--prefix=/jenkins"@' /etc/sysconfig/jenkins sudo sed -i -e 's@JENKINS_JAVA_OPTIONS="-Djava.awt.headless=true"@JENKINS_JAVA_OPTIONS="-Djava.awt.headless=true -Dhudson.util.ProcessTree.disable=true"@' /etc/sysconfig/jenkins sudo usermod -G docker jenkins sudo yum clean all SHELL
VMを作成します。
$ vagrant up
成功したらVMを再起動し、ブラウザで、http://192.168.33.10:8080/jenkins
にアクセス、jenkinsが正常に動作していることを確認します。
jenkinsが正常に動作していたら、以下を行っておきます。
2. デモアプリ(PHP, Node)を用意する
3. Dockerファイルを作成する
$APPROOT/.docker/Dockerfile
をそれぞれに作成しました。
PHP
FROM centos:6 MAINTAINER @piccagliani RUN cp -p /usr/share/zoneinfo/Asia/Tokyo /etc/localtime RUN echo ZONE="Asia/Tokyo" > /etc/sysconfig/clock RUN echo UTC="false" >> /etc/sysconfig/clock RUN source /etc/sysconfig/clock RUN yum update -y RUN rpm -Uvh http://ftp.jaist.ac.jp/pub/Linux/Fedora/epel/6/i386/epel-release-6-8.noarch.rpm RUN rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm RUN yum install -y --enablerepo=remi --enablerepo=remi-php56 php php-mbstring php-mcrypt RUN curl -sS https://getcomposer.org/installer | php RUN mv composer.phar /usr/local/bin/composer CMD ["/bin/bash"]
Node
Ubuntu + NodeJS + npmとします。
FROM ubuntu:14.04 MAINTAINER @piccagliani RUN cp -p /usr/share/zoneinfo/Asia/Tokyo /etc/localtime RUN apt-get update RUN apt-get -y upgrade RUN apt-get install -y nodejs npm RUN update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10 CMD ["/bin/bash"]
4. テスト実行用のスクリプトを作成する
それぞれのアプリに$APPROOT/.docker/run-tests.sh
を作成し、テスト実行用のスクリプトを記述しました。
PHP
#!/bin/bash cd /opt/php-fizzbuzz composer install vendor/bin/codecept run
Node
#!/bin/bash cd /opt/node-fizzbuzz npm install npm test
/opt/php-fizzbuzz
、/opt/node-fizzbuzz
には、Jenkinsのワークスペースがマウントされます。
5. Jenkinsにジョブを作成し、ビルドする
それぞれのアプリ用のジョブを以下のように作成しました。
ジョブ名
リポジトリの名前に合わせました。
ジョブの形式
「フリースタイル・プロジェクトのビルド」を選択。
ビルド手順
「シェルの実行」を選択し、以下を記述。
docker build -t $JOB_NAME $WORKSPACE/.docker/ docker run -v $WORKSPACE:/opt/$JOB_NAME $JOB_NAME sh /opt/$JOB_NAME/.docker/run-tests.sh
ジョブが作成できたら、それぞれビルドを実行してみます。
初回はコンテナの作成に時間がかかると思いますが、2度目からはキャッシュが使われるようになるため、ササッといくはずです。
あとは、GitのHookなり、ポーリングなりを利用すればCIできますね!
最後に. 今後の課題など
より複雑なテストはどうなる?
今回は単純なアプリのテストなので、Dockerfileもテスト手順もシンプルでした。
より複雑な手順が必要となる、Seleniumを利用したWEBアプリのE2Eテストなどもこの方式でできるのかどうか、引き続き実験してみます。
パーミッションまわり
今回はユーザー権限まわりを気にしていないので、すべてrootで実行されます。
このあたりも問題になる可能性がありますね。どうするのがBest Practiceなのでしょう?
テスト実行後にコンテナを破棄したい
何度もビルドしていると、以下のように古いコンテナが残ってしまいます。
$ sudo docker ps -a CONTAINER ID IMAGE a858a6348358 php-fizzbuzz:latest 876216e216ac node-fizzbuzz:latest 4f93cfe9961a node-fizzbuzz:latest 582b56657583 node-fizzbuzz:latest a1ef176f0497 php-fizzbuzz:latest
これら、破棄した方がいいと思いますが、jenkinsでどうやるんでしょう?
以上となります。
Docker、「foregroundプロセスがないとダメ」とかいろいろ躓いたところはありましたが、使いこなせるようになると強力ですね。
これでようやくCIサーバーを導入できそうです。