Dockerで開発環境を構築する


#はじめに
この文章では、Dockerを用いて開発環境を構築する手順について記述する。

#モチベーション
* 開発環境を持ち運びたい
 * 自分の場合: 家と職場でそれぞれローカルな開発環境を構築している。zsh、vimの設定ファイルだけgithubで共通化しているが、OSなどはローカル環境ごとに構築していて手間。
* Dockerを触っておきたい
 * Dockerの主要な使い方は開発環境としてではないと思うが、一通り触る練習の機会として。
* 開発環境が壊れているので直したいが、これを期にDockerにすることでこれ以降壊れてもすぐに復旧できるようにしたい。

#環境
| | Version |
|:---|:---:|
| Host OS | 10.10.2(Yosemite)|

#Install Docker on Mac OS X

[Install Docker on Mac OS X](https://docs.docker.com/installation/mac/) を参考に進める。

##Boot2Dockerをインストール
Docker DaemonはLinuxの特定のkernelに依存しているので、OS X上ではそのままでは動かない。そのために、VirtualBox、Docker、Boot2Docker Management Toolからなるboot2dockerをインストールする必要がある。OS X上で、boot2docker VMが起動して、そのVM上でDocker Daemonが実行される(参考リンク先の図参照のこと)。
 
公式ドキュメントでは、Githubのレポジトリから実行ファイルをダウンロードしてインストールしているが、Homebrewも対応しているので、今後の管理負荷軽減のためにHomebrewでインストールする。

```
% brew install docker boot2docker
% boot2docker
Usage: boot2docker [<options>] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|delete|destroy|download|upgrade|version} [<args>]
% docker -v
Docker version 1.1.2, build d84a070
```

##Boot2Docker Applicationを起動する
VirtualBoxにboot2docker-vmを作成する。私の場合は過去に試したためか、既に存在していた。

```
% boot2docker init
2015/03/23 18:19:24 Virtual machine boot2docker-vm already exists
```

起動する

```
% boot2docker up
2015/03/23 18:19:48 Waiting for VM to be started...
.......
2015/03/23 18:20:10 Started.
2015/03/23 18:20:10   Trying to get IP one more time
2015/03/23 18:20:10 Your DOCKER_HOST env variable is already set correctly.
% boot2docker status
running
```

DOCKER_HOSTの環境変数をシェルのプロファイルに追加する

```
% echo 'export DOCKER_HOST=tcp://192.168.59.103:2375' >> .zprofile
```

設定を確認する

```
% docker version
Client version: 1.1.2
Client API version: 1.13
Go version (client): go1.3
Git commit (client): d84a070
2015/03/24 20:25:31 Error response from daemon: client and server don't have same version (client : 1.13, server: 1.12)
```

clientとserverのバージョンの不一致が起こっている。Server側(boot2dockerのVM)を最新にupdateする。

```
% boot2docker download
2015/03/24 20:27:58 Downloading boot2docker ISO image...
2015/03/24 20:27:59 Latest release is v1.5.0
2015/03/24 20:28:13 Success: downloaded https://github.com/boot2docker/boot2docker/releases/download/v1.5.0/boot2docker.iso
	to /Users/your_name/.boot2docker/boot2docker.iso
% boot2docker init
2015/03/24 20:28:22 Creating VM boot2docker-vm...
2015/03/24 20:28:22 Apply interim patch to VM boot2docker-vm (https://www.virtualbox.org/ticket/12748)
2015/03/24 20:28:22 Setting NIC #1 to use NAT network...
2015/03/24 20:28:22 Port forwarding [ssh] tcp://127.0.0.1:2022 --> :22
2015/03/24 20:28:22 Port forwarding [docker] tcp://127.0.0.1:2375 --> :2375
2015/03/24 20:28:22 Setting NIC #2 to use host-only network "vboxnet1"...
2015/03/24 20:28:22 Setting VM storage...
2015/03/24 20:28:28 Done. Type `boot2docker up` to start the VM.
% boot2docker up
2015/03/24 20:28:35 Waiting for VM to be started...
...........
2015/03/24 20:29:08 Started.
2015/03/24 20:29:08 Your DOCKER_HOST env variable is already set correctly.
% docker version
Client version: 1.1.2
Client API version: 1.13
Go version (client): go1.3
Git commit (client): d84a070
2015/03/24 20:29:27 Cannot connect to the Docker daemon. Is 'docker -d' running on this host?
```

brewで入れた、boot2dockerが古い。

```
% brew update
% brew upgrade boot2docker
==> Upgrading 1 outdated package, with result:
boot2docker 1.5.0
==> Upgrading boot2docker
==> Installing boot2docker dependency: docker
==> Downloading https://homebrew.bintray.com/bottles/docker-1.5.0.yosemite.bottle.tar.gz
######################################################################## 100.0%
==> Pouring docker-1.5.0.yosemite.bottle.tar.gz
==> Caveats
Bash completion has been installed to:
  /usr/local/etc/bash_completion.d

zsh completion has been installed to:
  /usr/local/share/zsh/site-functions
==> Summary
🍺  /usr/local/Cellar/docker/1.5.0: 9 files, 7.2M
==> Installing boot2docker
==> Downloading https://homebrew.bintray.com/bottles/boot2docker-1.5.0.yosemite.bottle.tar.gz
######################################################################## 100.0%
==> Pouring boot2docker-1.5.0.yosemite.bottle.tar.gz
==> Caveats
To have launchd start boot2docker at login:
    ln -sfv /usr/local/opt/boot2docker/*.plist ~/Library/LaunchAgents
Then to load boot2docker now:
    launchctl load ~/Library/LaunchAgents/homebrew.mxcl.boot2docker.plist
==> Summary
🍺  /usr/local/Cellar/boot2docker/1.5.0: 3 files, 7.3M
``` 
boot2dockerのVMを再更新する。

```
% boot2docker stop
% boot2docker download
Latest release for boot2docker/boot2docker is v1.5.0
Downloading boot2docker ISO image...
Success: downloaded https://github.com/boot2docker/boot2docker/releases/download/v1.5.0/boot2docker.iso
	to /Users/your_name/.boot2docker/boot2docker.iso
% boot2docker init
Virtual machine boot2docker-vm already exists
% boot2docker delete
% boot2docker init
% boot2docker start
Waiting for VM and Docker daemon to start...
........................oooooooooooooooooo
Started.
Writing /Users/your_name/.boot2docker/certs/boot2docker-vm/ca.pem
Writing /Users/your_name/.boot2docker/certs/boot2docker-vm/cert.pem
Writing /Users/your_name/.boot2docker/certs/boot2docker-vm/key.pem

To connect the Docker client to the Docker daemon, please set:
    export DOCKER_HOST=tcp://192.168.59.104:2376
    export DOCKER_CERT_PATH=/Users/your_name/.boot2docker/certs/boot2docker-vm
    export DOCKER_TLS_VERIFY=1

% boot2docker shellinit
Writing /Users/your_name/.boot2docker/certs/boot2docker-vm/ca.pem
Writing /Users/your_name/.boot2docker/certs/boot2docker-vm/cert.pem
Writing /Users/your_name/.boot2docker/certs/boot2docker-vm/key.pem
    export DOCKER_HOST=tcp://192.168.59.104:2376
    export DOCKER_CERT_PATH=/Users/your_name/.boot2docker/certs/boot2docker-vm
    export DOCKER_TLS_VERIFY=1
% eval "$(boot2docker shellinit)"
Writing /Users/your_name/.boot2docker/certs/boot2docker-vm/ca.pem
Writing /Users/your_name/.boot2docker/certs/boot2docker-vm/cert.pem
Writing /Users/your_name/.boot2docker/certs/boot2docker-vm/key.pem
% docker run hello-world
Unable to find image 'hello-world:latest' locally
511136ea3c5a: Pull complete
31cbccb51277: Pull complete
e45a5af57b00: Pull complete
hello-world:latest: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
Status: Downloaded newer image for hello-world:latest
Hello from Docker.
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (Assuming it was not already locally available.)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

For more examples and ideas, visit:
 http://docs.docker.com/userguide/
% boot2docker status
running
% docker version
Client version: 1.5.0
Client API version: 1.17
Go version (client): go1.4.1
Git commit (client): a8a31ef
OS/Arch (client): darwin/amd64
Server version: 1.5.0
Server API version: 1.17
Go version (server): go1.4.1
Git commit (server): a8a31ef
```

##開発用のcontainerを構築する

CentOSで構築することとする。CentOSのイメージをpullする。

```
% docker pull centos:7
5b12ef8fd570: Pull complete
88f9454e60dd: Pull complete
511136ea3c5a: Already exists
centos:7: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
Status: Downloaded newer image for centos:7
```

Dockerfileを使って、開発に必要なアプリ、ライブラリを構築する。
[Dockerfile](https://github.com/osamunmun/docker_centos_base)をGithubからcloneして、buildする。
その後、Containerに接続する。

```
% docker build -t osamunmun/dev:1.0 .
% docker run -itd osamunmun/dev:1.0 /bin/bash
% docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
1fe2ed945c3e        osamunmun/dev:1.0   "/bin/bash"         12 seconds ago      Up 10 seconds       2222/tcp            backstabbing_mcclintock
% docker attach 1fe2ed945c3e
```

sshで接続したいときは下記のようにする。

```
% docker run -p 2222:22 -itd osamunmun/dev:1.0 /usr/sbin/sshd -D
% ssh -p 2222 osamunmun@<DOCKER_HOST>
```

##作業内容の保存
containerを停止するとconainer内での変更内容は失われる。containerをcommitしてimage化すれば変更内容を含んだimageを保存できる。しかし、Dockerでは環境に冪等性をもたせたいのであって、データはDockerの責務範囲外という方針である。
なので、データはVOLUMEをつかってimageの外(Union File Systemの外)にデータを保存する。

``` Dockerfile
RUN mkdir /home/osamunmun/apps
RUN chown -R osamunmun:osamunmun /home/osamunmun/apps
VOLUME /home/osamunmun/apps
```

ポイントは、先にimage内にディレクトリを作ってから、chownするところ。先にVOLUMEを作ると、その時点でimage外にディレクトリが作られ、image外なためimage内のユーザーへの権限変更ができないようだ([understanding-volumes-docker](http://container-solutions.com/2014/12/understanding-volumes-docker/))。先に、image内にディレクトリを作成してからだと、VOLUME作成時に権限の処理をDockerがしてくれる。

VOLUMEに保存したデータを引き継ぎためには、docker runのオプションを以下のように設定する。

初回起動時

```初回起動時
% docker run --name dev -p 2222:22 -itd osamunmun/dev:1.0 /usr/sbin/sshd -D
```

2回目以降起動時

```2回目以降起動時
% docker run --volumes-from dev -p 2222:22 -itd osamunmun/dev:1.0 /usr/sbin/sshd -D
```

参考: https://github.com/boot2docker/boot2docker#folder-sharing

##Railsを起動してHostからHTTPでアクセスする

boot2dockerでssh port forwardingしないとHostから3000にリーチできない。
参考: http://stackoverflow.com/questions/23422540/docker-cant-reach-rails-server-development-from-localhost3000-using-docker

port forwardingしておく。

```
% boot2docker ssh -L 8080:localhost:80
```

80を3000にforwarding

```
% docker run --volumes-from dev -p 2222:22 -p 80:3000 -itd osamunmun/dev:1.1 /usr/sbin/sshd -D
```

これでHost上で、`curl http://localhost:8080`でRails serverにreachできる。


(2015/4/15 追記)
##boot2dockerのリソース設定
- Memoryには4GB(Hostマシンの半分)
- CPUには2つ(Hostマシンはcore i7)
 - デフォルトでは7つ割り当てられていた
これで動作が軽快になった。CPU7つはリソースを圧迫していた模様