テストやビルドスクリプトをDockerコンテナで実行させるための便利ツールを作った
ある程度複雑なプログラムだと、ローカル環境やCIサーバ環境など、どこでも動くようにテストを書くのが難しくなったりします。また、昨今のWebアプリケーションだと、デプロイ前に何らかのビルドプロセスが必要になることがほとんどで、依存物をインストールしたり、Webpackを動かしたり、Goのビルドを走らせたりします。こういうタスクもnodeやらGoに依存していて、複数の環境で差異なく動かすことを考えると、いろいろツライものがあります。
そこで、こういったテストやビルド処理はシェルスクリプトを書いて、Dockerコンテナ内で動かすことによって環境に対しての依存を解消していました。
たとえばGo言語の場合以下のようなテスト実行スクリプトtest_run.sh
を書いておきます。
#!/usr/bin/env bash set -eu go test $GOTEST_FLAGS $(go list ./... | grep -v vendor)
そしてこれをDockerコンテナで実行するためのスクリプトとして以下のようなtest.sh
を書きます。
#!/usr/bin/env bash set -eu DOCKER_IMAGE=${DOCKER_IMAGE:-'kohkimakimoto/golang:centos7'} GOTEST_FLAGS=${GOTEST_FLAGS:--cover -timeout=360s} repo_dir=$(cd $(dirname $0); pwd) docker run \ --env GOTEST_FLAGS="${GOTEST_FLAGS}" \ -v $repo_dir:/build/src/github.com/username/repo \ -w /build/src/github.com/username/repo \ --rm \ ${DOCKER_IMAGE} \ bash ./test_run.sh
goコマンドなど、テストに必要な環境は全てDockerイメージとして用意しておきます。あとはリポジトリのルートにいる状態で./test.sh
を実行すれば、テストが実行されます。TravisCIのようなCIサーバはDockerに対応しているので、同様に./test.sh
を実行するだけで、ローカル環境と同じようにテストが実行できます。テストコード側で環境の違いを意識する必要はなくなります。
ところで、しばらくこの方式でやっていたところ、以下のような課題が浮き彫りになってきました。
- 新しいプロジェクトごとにシェルスクリプトをコピペ、修正して使いまわしている。
- これらのスクリプトはだいたい似たようなコードになりがちだが、利用するDockerイメージや
docker run
のオプションなどが微妙にちがう。 - テストやビルドなど個々のタスクごとに「実際のタスクを実行するスクリプト」と「それをDockerコンテナで実行するスクリプト」の2つのファイルを作る必要があり、数が増えるとファイルがごちゃごちゃしてきた。
ベタに書いたシェルスクリプトによる実装なので、プロジェクトが増えると、共通化などができずコードが冗長になりがちでした。そこで、大雑把に処理を整理してみると
などが共通な処理であったので、これらをまとめて、いい感じにDockerコンテナを起動してその中でスクリプトを実行するコマンドラインツールを作りました。
Goで実装したシングルバイナリなので、Githubのリリースページからバイナリをダウンロードして、パスの通ったディレクトリに配置すればすぐに使えます。
使い方
まずは単純に
$ buildsh
を実行してみます。これだけで、デフォルトでDockerイメージkohkimakimoto/buildsh:latest
(2Gくらいあります)をダウンロードして、カレントディレクトリをコンテナ内の/build
にマウントした状態でコンテナを起動します。そのままbashでログインした状態になるので、たとえばPHPのテストだったら
$ php phpunit
などを実行すればテストができます。PHPやPython,nodeなど主なLLのランタイムを入れてあるので、ホストマシンの環境に関係なくすぐにテストやビルドができます。作業が終わったら
$ exit
すれば、ホストマシンにもどってコンテナが破棄されます。特定の環境でちょっとしたことを動作確認したい場合などに便利に使えます。
そしてテストやビルドのシェルスクリプトを実行させたい場合は
$ buildsh test.sh
のようにスクリプトファイルを指定して実行すれば、そのスクリプトがDockerコンテナ内で実行されます。これで用意するスクリプトはテストを実行する部分のみでよくなり、「それをDockerコンテナで実行するスクリプト」を書く必要がなくなりました。
設定ファイル
使用するDockerイメージを変更したい場合などは、設定ファイルを利用することができます。.buildsh.yml
をカレントディレクトリに配置してください。以下のような設定ができます。
use_cache: true docker_image: kohkimakimoto/buildsh:latest additional_docker_options: --net=host -v=/var/run/docker.sock:/var/run/docker.sock environment: FOO: bar FOO2: bar2 home_in_container: /build/src/github.com/kohkimakimoto/buildsh
詳細はREADME.mdを参照していただくとして、いくつかピックアップして概要を説明します。
use_cache
use_cache
はtrueにするとカレントディレクトリ配下に.buildsh/cache
ディレクトリを作り、コンテナ内でパスを環境変数BUILDSH_CACHEDIR
に設定します。これはスクリプト実行ごとに破棄されてしまうコンテナ内のデータを保存するときに使うことができます。たとえばyarn install
を以下のようにすれば、キャッシュを保持できて、次回以降の処理の高速化が望めます。
$ yarn install --cache-folder=$BUILDSH_CACHEDIR/yarn
.gitignore
に.buildsh
を追加するのを忘れずに。。。
docker_image
docker_image
は利用するDockerイメージです。自分の用途に合わせて好きなイメージを使えます。
additional_docker_options
additional_docker_options
は内部で実行しているdocker run
に付け加えるオプションを指定できます。例のように-v=/var/run/docker.sock:/var/run/docker.sock
を利用すればDockerコンテナ内から新しくDockerコンテナを立ち上げることもできたりします。
home_in_container
home_in_container
はコンテナ内でホストのカレントディレクトリがマウントされるパスを変更できます。デフォルトは/build
ですが、例えばGoのテストやビルドをおこなうとき、GOPATHの関係上/build/src/github.com/kohkimakimoto/buildsh
のようにGOの流儀にそったパスに配置したいことがあります。この設定を使うことでそのようなケースに対応できます。
実装について
このツールはつまるところ、テストやビルド目的の使い捨てコンテナである場合docker run
にセットするオプションや引数がおおむね共通化できるので、それをまとめたラッパーコマンドというわけです。
もともとはbashのスクリプトで書いていたこともあって、実装は単純なdocker run
のラッパーコマンドになっているので、メインのコードはbuildsh.goのみです。なのでをこれを見れば何をやっているのか大体わかりますので、細かいことはソースを見たほうが早いかもしれません。
epelにdockerがキタ━(゚∀゚)━!ので、CentOSにインストールした。
ローカルのVirtualBox上のCentOS6で動かしました。epelリポジトリは事前に設定してあるものとします。
インストール
# yum install --enablerepo=epel docker-io
サービスの起動
# /etc/init.d/docker start
doceker runする!
# docker run -t -i centos /bin/bash
コンテナ内でシェルが起動します。echoとか打ってみる。
bash-4.1# echo hello world!
hello world!
bash-4.1# exit
うごく~( ´∀`)♪
Dockerfileからimageつくってみる
Dockerfile
FROM centos
RUN yum clean all
RUN yum install -y openssh-server
RUN yum install -y passwd
RUN echo d0cker | passwd --stdin root
## https://github.com/dotcloud/docker/issues/1240#issuecomment-21807183
RUN echo "NETWORKING=yes" > /etc/sysconfig/network
## http://gaijin-nippon.blogspot.com/2013/07/audit-on-lxc-host.html
RUN sed -i -e '/pam_loginuid\.so/ d' /etc/pam.d/sshd
EXPOSE 22
CMD /sbin/init
docker buildする!
# docker build -t test .
Uploading context 10240 bytes
Step 1 : FROM centos
---> 539c0211cd76
Step 2 : RUN yum clean all
---> Using cache
---> f738a5199bab
Step 3 : RUN yum install -y openssh-server
---> Running in 033f90a21823
Loaded plugins: fastestmirror
Determining fastest mirrors
Error: Cannot find a valid baseurl for repo: base
Could not retrieve mirrorlist http://mirrorlist.centos.org/?release=6&arch=x86_64&repo=os error was
14: PYCURL ERROR 6 - "Couldn't resolve host 'mirrorlist.centos.org'"
Error build: The command [/bin/sh -c yum install -y openssh-server] returned a non-zero code: 1
おちた~(T_T)!!なんでかコンテナからインターネットに接続できん。のおおお!
2013-12-07追記
:とおもったらVirtulaBoxのVMから作りなおしたら出来た。何がわるかったかわからんす。
2013-12-10追記
:ネット接続できない問題は/etc/sysctl.conf
でnet.ipv4.ip_forward = 1
にしたら治ったのかもしれない。でもCMD /sbin/init
は結局うまく動いてくれない。Dockerで/sbin/init
を動かすのはGithubのissuseにもあがっていて、みんな試行錯誤してるようだけど、まだ解決していないっぽいですな。