Perlのテスト内で、Dockerを使ってMySQLとかMiddlewareを使い回す

## 背景と動機

### Webアプリで複数バージョンのMySQLを使い分けたい

* 古いアプリは5.1系
* 新しいアプリは5.6系
 * iPhone絵文字に対応させたいけど、5.1だと[BMP](http://ja.wikipedia.org/wiki/%E5%9F%BA%E6%9C%AC%E5%A4%9A%E8%A8%80%E8%AA%9E%E9%9D%A2)しか扱えないお(´・ω・`)
* あるテストでは、MySQL5.1に繋ぎにいって、別のテストでは5.6に繋ぎにいくようにさせたい
 * [mysqlenv](https://github.com/xaicron/mysqlenv)で複数バージョンのMySQLをテスト毎にswitchする？
  * ローカル環境構築大変
  * Jenkins Slaveにもmysqlenv入れるの？
  * いずれにしても、5.1と5.6両方に繋ぐアプリの場合はうまくいかない

### 仮想環境を用意する

開発環境を容易に構築するために、VMWareとかの仮想イメージを用意していた。mysqldやQ4M、必要なPerlモジュールなどが一通り入っているくん。

* 開発環境の更新に追従させていくのが難しい
 * いざ使おうと思ったら、用意した仮想イメージにまたなにか追加でインストールしないと使えない
* イメージの共有が難しい
* 仮想マシン起動すると重い、起動に時間かかる

ってことで、VMメンドイし重いので、ローカルのMacOSXで開発したい。MacVim使えるし、MacVim (ry

### 開発環境にMiddlewareを一式用意しないと

MySQL, memcached, redisとか自力で入れないとねー

* ローカルにmysqldを入れて、[Test::mysqld](https://metacpan.org/pod/Test::mysqld)使おうとした場合、ローカルに[Q4M](http://q4m.github.io/)や[HandlerSocket](https://github.com/DeNA/HandlerSocket-Plugin-for-MySQL)を入れないといけない
* OSXのバージョンが上がるたびに、結構苦労して自力コンパイル&インストールしないといけない
* [mysql-build](https://github.com/kamipo/mysql-build)使えば、比較的簡単にインストールできるけども…

## Docker imageにM/W構築して再利用

アプリ自体をDocker imageにするのはちょっとハードル高そう(？)なので、M/WだけでもDockerImageにして共有できればいいんじゃね？

DockerImage作って、docker hubに上げた

* [CentOS6.7+MySQL5.1+Q4M+HandlerSocker](https://registry.hub.docker.com/u/iwata/centos6-mysql51-q4m-hs/)
* [CentOS6.7+MySQL5.6+Q4M+HandlerSocker](https://registry.hub.docker.com/u/iwata/centos6-mysql56-q4m-hs/)

(memcachedも入れてもいいかも。別imageでもいいかと思うんでいれてない)

```sh
boot2docker start
docker pull iwata/centos6-mysql51-q4m-hs
docker pull iwata/centos6-mysql56-q4m-hs
docker run -d -t --name=mysql51 -p 3306 iwata/centos6-mysql51-q4m-hs
docker run -d -t --name=mysql56 -p 3306 iwata/centos6-mysql56-q4m-hs
docker ps

boot2docker ip
# The VM's Host only interface IP address is: 192.168.59.103

docker port mysql51 3306
# 0.0.0.0:49223
mysql -uroot -h192.168.59.103 -P49223 -e 'show plugins;select @@version;'

docker port mysql56 3306
# 0.0.0.0:49224
mysql -uroot -h192.168.59.103 -P49224 -e 'show plugins;select @@version;'

docker kill mysql51 && docker rm mysql51
docker kill mysql56 && docker rm mysql56
```

Testの中でこのimageを`docker run`して繋ぎにいって、test終わったら、`docker kill`&&`docker rm`してくれればいい。
ってことで、そんな感じのことをやってくれるCPAN Module書いた。

[Test::Docker::Image](https://metacpan.org/pod/Test::Docker::Image)を使えば、よしなにやってくれるYO!

### Test::Docker::Imageを使ってみる

```perl
use Test::Docker::Image;

my $mysql_image_guard = Test::Docker::Image->new(
    container_ports => [3306],
    tag             => 'iwata/centos6-mysql51-q4m-hs',
);

my $port = $mysql_image_guard->port(3306);
my $host = $mysql_image_guard->host; # Docker's host IP

`mysql -uroot -h$host -P$port -e 'show plugins'`;
undef $mysql_image_guard; # destroy a guard object and execute docker kill and rm the container.
```

docker imageの任意のtagを指定して`new`すると、guard objectが返ってくるので、このobject経由で接続情報を取得できます。
`new`した時点で、`docker run`が実行されてcontainerが立ち上がった状態になります。
guard objectが破棄されるタイミングで、`docker kill`と`docker rm`を呼んで不要になったcontainerも破棄してくれます。
この辺は、`Test::mysqld`が`DESTROY`でmysqldをkillしているのと似たような感じです。

[Harriet](https://metacpan.org/release/Harriet)を使うと以下のような感じになります。

```perl:t/harriet/mysqld.pl
$ENV{TEST_MYSQLD} ||= do {
    require Test::Docker::Image;

    my $mysql_image = Test::Docker::Image->new(
        container_ports => [3306],
        tag             => 'iwata/centos6-mysql51-q4m-hs',
    );

    $HARRIET_GUARDS::MYSQLD = $mysql_image;

    my $port = $mysql_image->port(3306);
    my $host = $mysql_image->host;

    "dbi:mysql:dbname=test;host=$host;user=root;port=$port";
};
```

上記のコードでは、MySQL用のimageを使いましたが、Redisやmemcached用のimageが用意されていれば、ローカルにはクライアントだけ入っていれば、色んなM/W使い回せるようになります。

将来的には[App::Prove::Plugin::MySQLPool](https://metacpan.org/pod/App::Prove::Plugin::MySQLPool)のように、docker containerをpoolingするModuleを作ることで、並列実行できるようにできればと思います。
(誰か作って＞＜)