オープンソースこねこね

Webプログラミングなどについてあれこれ。

テストやビルドスクリプトを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コンテナの特定の場所にマウントする
  • --rmオプションをつけて、コンテナ停止後自動でコンテナを削除するようにする
  • スクリプトを実行する。

などが共通な処理であったので、これらをまとめて、いい感じにDockerコンテナを起動してその中でスクリプトを実行するコマンドラインツールを作りました。

github.com

Goで実装したシングルバイナリなので、Githubのリリースページからバイナリをダウンロードして、パスの通ったディレクトリに配置すればすぐに使えます。

使い方

まずは単純に

$ buildsh

を実行してみます。これだけで、デフォルトでDockerイメージkohkimakimoto/buildsh:latest(2Gくらいあります)をダウンロードして、カレントディレクトリをコンテナ内の/buildにマウントした状態でコンテナを起動します。そのままbashでログインした状態になるので、たとえばPHPのテストだったら

$ php phpunit

などを実行すればテストができます。PHPPython,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.confnet.ipv4.ip_forward = 1にしたら治ったのかもしれない。でもCMD /sbin/initは結局うまく動いてくれない。Dockerで/sbin/initを動かすのはGithubのissuseにもあがっていて、みんな試行錯誤してるようだけど、まだ解決していないっぽいですな。