boot2dockerで公式のmysqlやmongoが起動しない問題(boot2dockerの999問題をちゃんと理解しよう)

boot2dockerで公式のmysqlイメージやmongoイメージを起動するときに、`run -v`オプションの左辺で`/Users`以下の場所を指定すると起動に失敗します。

数日間うんうん悩んで解決しなかったのですが、Twitterでぼやいてたら答えを教えてもらいました。

<blockquote class="twitter-tweet" lang="en"><p lang="ja" dir="ltr">boot2dockerでmysqlコンテナを起動しようとしたら、volumeのマウントでこける...ぐぬぬ&#10;InnoDB: The error means mysqld does not have the access rights to&#10;Ithe directory.</p>&mdash; docker run DQNEO (@DQNEO) <a href="https://twitter.com/DQNEO/status/591245558691471360">April 23, 2015</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>


<blockquote class="twitter-tweet" lang="en"><p lang="ja" dir="ltr"><a href="https://twitter.com/DQNEO">@DQNEO</a> 突然失礼します。boot2docker の環境で、-v /Users/...:/var/lib/mysql とすると、/var/lib/mysql が id 1000 の所有になるので mysql ユーザー(id 999)では起動できなくなります</p>&mdash; mapk0y (@mapk0y) <a href="https://twitter.com/mapk0y/status/592375521184391169">April 26, 2015</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

# tl;dr

* boot2dockerは当初は-vが使えなかったが、Docker1.3から使えるようになった。
* ところが、vboxsf(=Virtualbox Guest Additions)の仕組みを使っているので、制約がある。
* 公式のmysqlイメージやmongoイメージを起動するときに`run -v $(pwd):`とかするとその制約にひっかかる。

## docker run -v ~/tmp/mysql:/var/lib/mysql でこける

```
$ docker run -v ~/tmp/mysql:/var/lib/mysql  --rm --name tmp -it  -e MYSQL_ROOT_PASSWORD=foo mysql
Running mysql_install_db ...
Installing MySQL system tables...2015-05-07 10:30:39 0 [Note] /usr/sbin/mysqld (mysqld 5.6.24) starting as process 14 ...
2015-05-07 10:30:39 14 [Note] InnoDB: Using atomics to ref count buffer pool pages
2015-05-07 10:30:39 14 [Note] InnoDB: The InnoDB memory heap is disabled
2015-05-07 10:30:39 14 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins
2015-05-07 10:30:39 14 [Note] InnoDB: Memory barrier is not used
2015-05-07 10:30:39 14 [Note] InnoDB: Compressed tables use zlib 1.2.7
2015-05-07 10:30:39 14 [Note] InnoDB: Using Linux native AIO
2015-05-07 10:30:39 14 [Note] InnoDB: Not using CPU crc32 instructions
2015-05-07 10:30:39 14 [Note] InnoDB: Initializing buffer pool, size = 128.0M
2015-05-07 10:30:39 14 [Note] InnoDB: Completed initialization of buffer pool
2015-05-07 10:30:39 7f0175bcc720  InnoDB: Operating system error number 13 in a file operation.
InnoDB: The error means mysqld does not have the access rights to
InnoDB: the directory.
2015-05-07 10:30:39 7f0175bcc720  InnoDB: Operating system error number 13 in a file operation.
InnoDB: The error means mysqld does not have the access rights to
InnoDB: the directory.
2015-05-07 10:30:39 14 [ERROR] InnoDB: Creating or opening ./ibdata1 failed!
2015-05-07 10:30:39 14 [ERROR] InnoDB: Could not open or create the system tablespace. If you tried to add new data files to the system tablespace, and it failed here, you should now edit innodb_data_file_path in my.cnf back to what it was, and remove the new ibdata files InnoDB created in this failed attempt. InnoDB only wrote those files full of zeros, but did not yet use them in any way. But be careful: do not remove old data files which contain your precious data!
2015-05-07 10:30:39 14 [ERROR] Plugin 'InnoDB' init function returned error.
2015-05-07 10:30:39 14 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed.
2015-05-07 10:30:39 14 [ERROR] Unknown/unsupported storage engine: InnoDB
2015-05-07 10:30:39 14 [ERROR] Aborting

2015-05-07 10:30:39 14 [Note] Binlog end
2015-05-07 10:30:39 14 [Note] /usr/sbin/mysqld: Shutdown complete
```
こけました(´・ω・`)

## docker run -v ~/tmp/db:/data/db mongo でこける

```
$ docker run --rm -it -v ~/tmp/db:/data/db mongo
2015-05-07T10:31:09.234+0000 I STORAGE  [initandlisten] exception in initAndListen: 98 Unable to create/open lock file: /data/db/mongod.lock errno:13 Permission denied Is a mongod instance already running?, terminating
2015-05-07T10:31:09.234+0000 I CONTROL  [initandlisten] dbexit:  rc: 100
```

こけました(´・ω・`)


# "-v /tmp"なら動く

ところが、"-v ~/tmp/" としていたものを"-v /tmp/" に変えてみると、あーら不思議！ちゃんと起動するじゃありませんか。

mongo

```
docker run -it  -v /tmp/db:/data/db mongo
```

mysql

```
docker run -it  -v /tmp/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=foo mysql
```

これはいったいどういうことなのでしょうか？？

## 最小構成で実験してみる
このエラーを再現する最小のDockerイメージを作ってみましょう。

```Dockerfile
FROM debian:wheezy

RUN groupadd -r mysql && useradd -r -g mysql mysql

VOLUME /var/lib/mysql
```
`mysql`という名前のユーザを作ってVOLUME設定するだけのDockerfileを作りました。

これをビルドします。

```
$ docker build -t mysqltest .
```

ビルドできたらrunしてコンテナ内に入ってみましょう。

```
$ docker run --rm  -it -v ~/tmp/mysql:/var/lib/mysql mysqltest bash
root@7eef14174f04:/#
````

mysqlという名のユーザにスイッチして、ファイル作成を試みます。

```
root@7eef14174f04:/# su - mysql
No directory, logging in with HOME=/
$ touch /var/lib/mysql/file
touch: cannot touch `/var/lib/mysql/file': Permission denied
```

おぉ！失敗しました。
ディレクトリに対するパーミッションがないからファイルを作れないというエラーのようです。
公式のmysqlやmongoイメージが起動しなかったのはどうもこれが原因のようです。

パーミッション関係の情報を見てみましょう。

```
$ id
uid=999(mysql) gid=999(mysql) groups=999(mysql)
$ stat /var/lib/mysql
  File: `/var/lib/mysql'
  Size: 68              Blocks: 0          IO Block: 4096   directory
Device: 1bh/27d Inode: 42          Links: 1
Access: (0755/drwxr-xr-x)  Uid: ( 1000/ UNKNOWN)   Gid: (   50/   staff)
Access: 2015-05-07 13:48:22.000000000 +0000
Modify: 2015-05-07 13:48:22.000000000 +0000
Change: 2015-05-07 13:48:22.000000000 +0000
```

まず自分はmysql(id=999)という名前のユーザです。
それに対して`/var/lib/mysql`ディレクトリの所有者は名無しさん(uid=1000)であり、所有グループはstaff(gid=50)、ディレクトリのパーミッションは0755です。

たしかにこれではファイル作成ができませんね。

ここで、「じゃあディレクトリのパーミッションを0777にすればいいんじゃね？」と考えて

```
# 一度rootに戻って
$ exit
# パーミッションを変更してみる
root@7eef14174f04:/# chmod 0777 /var/lib/mysql
root@7eef14174f04:/# stat /var/lib/mysql
  File: `/var/lib/mysql'
  Size: 68              Blocks: 0          IO Block: 4096   directory
Device: 1bh/27d Inode: 42          Links: 1
Access: (0755/drwxr-xr-x)  Uid: ( 1000/ UNKNOWN)   Gid: (   50/   staff)
Access: 2015-05-07 13:48:22.000000000 +0000
Modify: 2015-05-07 13:48:22.000000000 +0000
Change: 2015-05-07 13:48:22.000000000 +0000
 Birth: -
```

あれ？パーミッションを変更できませんでした。(´・ω・`)
つまりこの方法ではどうやってもファイルを作成できないようです。


## `-v /tmp/mysql:`だとどうか

さっきのコンテナを一度破棄して、
ボリュームオプションの左辺を`-v /tmp/mysql:`として起動してみます。

```
$ docker run --rm  -it -v /tmp/mysql:/var/lib/mysql mysqltest bash
root@e95453eda187:/# su - mysql
No directory, logging in with HOME=/
$ touch /var/lib/mysql/file
$ ls /var/lib/mysql/file
/var/lib/mysql/file
```

あれ！今度はあっさり成功しました。
何が違うんでしょうか？
パーミッション周りを見てみましょう。

```
$ id
uid=999(mysql) gid=999(mysql) groups=999(mysql)
$ stat /var/lib/mysql
  File: `/var/lib/mysql'
  Size: 4096            Blocks: 8          IO Block: 4096   directory
Device: 801h/2049d      Inode: 1062117     Links: 4
Access: (0755/drwxr-xr-x)  Uid: (  999/   mysql)   Gid: (  999/   mysql)
Access: 2015-05-07 13:37:55.844312758 +0000
Modify: 2015-05-07 14:01:38.951754264 +0000
Change: 2015-05-07 14:01:38.951754264 +0000
 Birth: -
```

/var/lib/mysqlの所有者が`mysql(id=999)`になっています。
オーナーが自分なのでファイルを作成できたわけです。

<blockquote class="twitter-tweet" lang="en"><p lang="ja" dir="ltr"><a href="https://twitter.com/mapk0y">@mapk0y</a> あ！！なるほど、だから -v /tmp/mysql:/var/lib/mysql だと動いたんですね。そうかそうかー　やっと気づきました。ありがとうございます！！</p>&mdash; docker run DQNEO (@DQNEO) <a href="https://twitter.com/DQNEO/status/592377404313571328">April 26, 2015</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>




## なぜ`-v ~/tmp`と`-v /tmp`で挙動が違うのか？

答えはboot2dockerの仮想マシンの中にあります。
仮想マシンの中に入ってマウント情報を見てみましょう。

```
$ boot2docker ssh

docker@boot2docker:~$ mount
[中略]
none on /Users type vboxsf (rw,nodev,relatime)
```

そうなのです。
boot2dockerでは、ホストマシン(Mac)の/Usersをゲストマシンの/Usersにマウントしているのです。そしてこのマウント方法に`vboxsf`(=Virtualbox Guest Additions)というちょっとややこしい仕組みが使われているのです。

詳しくはこちら：[boot2dockerでのVolume問題が解決しそう](http://deeeet.com/writing/2014/10/08/boot2docker-guest-additions/)

## `-v /tmp/mysql:`の場所について

`-v /tmp/mysql:`だと動くと書きましたが、ここでも注意が必要です。
この`/tmp`はホストマシン(Mac)のディレクトリではなくてboot2dockerの仮想マシン内の`/tmp`を指しているのです。

つまり、mysqlのデータは仮想マシン内の`/tmp/mysql`に書き込まれることになります。

<blockquote class="twitter-tweet" lang="en"><p lang="ja" dir="ltr"><a href="https://twitter.com/DQNEO">@DQNEO</a> そうですね。特に、/Users 以下を何気なく指定してると透過的に見えちゃいますからね...。boot2docker が使いやすくしてくれてる分、少し使い込むと他のところではまりやすくなってるのがちょっと残念です</p>&mdash; mapk0y (@mapk0y) <a href="https://twitter.com/mapk0y/status/592381165194973187">April 26, 2015</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

# まとめ
* boot2dockerが透過的な存在だと思ったらダメです。
* boot2dockerはむしろVagrantと同じようなツールだと考えるべき
* むしろboot2dockerを使わずにVagrantでやったほうがハマりにくいかもしれない。

# 参考
検索するとissueがいっぱい出てきますが根っこは全部同じのようでした。

https://github.com/boot2docker/boot2docker/issues/581
https://github.com/boot2docker/boot2docker/issues/587
https://github.com/docker-library/mysql/issues/44
https://github.com/docker-library/mongo/issues/30

# 謝辞
@mapk0y さんありがとうございます！！