Dockerで開発環境を作る

# はじめに
Dockerでの開発環境構築については色々と情報が転がっていましたが、情報が古かったり構成が違ったりで試行錯誤ありましたのでひとまずまとめました。ちなみに本番環境は今のところDocker全く使っておらず、開発環境を手軽に整えるという目的のみで使っています。

開発中のサービスは現在下記のコンポーネントで構成されています。
- Ruby on Rails 4
- MySQL
- memcached
- redis
- nginx

# Docker導入前と後での変化
弊社では各個人のPCに環境を構築して開発しています。

mysqldやmemcached、ImageMagickなど依存しているモジュールを入れて環境をセットアップするのですが、まぁビルドは時間かかるしバージョンが違ったりでなんだかんだ丸一日費やすことが多かったです。

Dockerで開発環境をセットアップできるようにした結果、PCがまっさらな状態であっても30分もあればセットアップが完了して開発がスタートできるようになりました。

まだ導入したばかりなのですが直近で実感した効果としては、インターン生が入ってきた時に最初のモチベーションのまますぐ開発に入ってもらえるのがだいぶ大きいなというのがありました。

# やったこと

## 1. 必要なツール群の導入
VirtualBoxなども全て入った Docker Toolbox がリリースされているので、それだけでおｋでした。
https://www.docker.com/docker-toolbox

brew cask入れてる人は
`$ brew cask install dockertoolbox`
で入ります。

## 2. 元となるイメージの構築

Ruby(rails）が動くイメージが必要です。もとからRubyが入ってるイメージファイルは公式であるのですが、今回はAmazon Linuxと同等の環境を作るためにCentOSをベースに作ろうと思い、自前でイメージを作成することにしました。Ruby以外にもImageMagickなど現在のサービスに必要なものも yum で突っ込んでます。

ちなみに最初は Amazon Linux のAMIからDockerイメージを作成して利用しようと思ったのですが、 yum がAWS環境でしかアクセス出来ないという初歩的な問題に引っかかり諦めました。

ということでイメージの作成ですが、まずはDockerfileを用意します。

```Dockerfile.

FROM centos:centos6

RUN yum -y update
RUN yum -y install gcc git rsync tar openssl openssl-devel readline-devel  zlib-devel libffi-devel gdbm-devel tk tk-devel tcl tcl-devel patch gcc-c++ which sqlite-devel wget openssh-server file
RUN yum -y install http://dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm
RUN yum -y install mysql mysql-devel ImageMagick

# rbenvのインストール
RUN git clone https://github.com/sstephenson/rbenv.git /root/.rbenv
RUN git clone https://github.com/sstephenson/ruby-build.git /root/.rbenv/plugins/ruby-build
RUN ./root/.rbenv/plugins/ruby-build/install.sh
ENV PATH /root/.rbenv/shims:/root/.rbenv/bin:$PATH
RUN echo 'export PATH=/root/.rbenv/shims:/root/.rbenv/bin:$PATH' >> /root/.bashrc
RUN echo 'eval "$(rbenv init -)"' >> /root/.bashrc

# rubyのインストール
ENV CONFIGURE_OPTS --disable-install-doc
RUN rbenv install 2.2.2
RUN rbenv global 2.2.2
RUN rbenv rehash
RUN rbenv exec gem install bundler

ENV APP_HOME /app

RUN mkdir $APP_HOME
WORKDIR $APP_HOME
ADD Gemfile* $APP_HOME/
RUN bundle install
```

最後に `bundle install` してますが、各個人の環境で `docker-compose build` を行うときにやれば良いかなと思ったのですが、諸事情もありinstallしたGemを永続化できる領域にうまく置くことができませんでした。その場合Dockerの性質上、Gemfile更新のたびに `docker-compose build` をする必要があるわけですが、その時に毎回最初から各gemの導入から始まるのは時間がかかりすぎてつらたんだったので、ある時点でのGemが入った状態にしておくことでBuildを短時間で終わらせられるようにしています。

上記ファイルを用意したら `docker build` します。（その前に `docker-machine create` だけやる必要があります。）

```
docker-machine create --virtualbox-disk-size 3000 -d virtualbox dev
eval "$(docker-machine env dev)"
docker build -t my-dockerhub-repository/centos6-ruby2.2.3 .
```

また、作られたイメージを各個人のPCから使えるできるようにするためにDockerhubにアップしました。プライベートリポジトリにする必要もないので無料プランでOKです（たぶん）。

`docker push my-dockerhub-repository/centos6-ruby2.2.3`


## 3. 設定ファイルの準備

解説は後で、まずは設定ファイルをドーンと載せちゃいます。下記はRailsのルート直下に配置しています。
＃公開するとあれなところはいくつか情報書き換えてるのでtypoなどによりそのままだと動かないかも

```Dockerfile.
FROM my-dockerhub-repository/centos6-ruby2.2.3

ENV APP_HOME /app

WORKDIR $APP_HOME
ADD Gemfile* $APP_HOME/
RUN bundle install
ADD . $APP_HOME

EXPOSE 3000

CMD ["bundle", "exec", "rails", "s"]
```

```docker-compose.yml
mysql:
  image: mysql:5.6.23
  environment:
    MYSQL_ROOT_PASSWORD: password
  ports:
    - '3306:3306'
  volumes:
    - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
  volumes_from:
    - datastore

redis:
  image: redis:2.8.19
  ports:
    - '6379:6379'
  volumes_from:
    - datastore

memcached:
  image: memcached:1.4.24
  ports:
    - '11211:11211'
  volumes_from:
    - datastore

datastore:
  build: docker/datastore

web:
  build: .
  ports:
    - '3000:3000'
  environment:
    RAILS_ENV: development
    MYSQL_ROOT_PASSWORD: 'password'
    DATABASE_HOST: mysql
    REDIS_HOST: redis
    MEMCACHED_HOST: memcached
  dns:
    - 8.8.8.8
  volumes:
    - .:/app
  volumes_from:
    - datastore
  links:
    - mysql
    - redis
    - memcached
```


```docker/datastore/Dockerfile.
FROM busybox:latest

VOLUME /var/lib/mysql
VOLUME /data

CMD /bin/sh
```


### 簡単に解説
memcached、mysql、redisなどの依存関係は docker-compose.yml で簡単に定義できます。

mysqlは utf8md4 に関する設定が必要だったので、 ローカルに配置した my.cnf を利用するように定義しています。

DBの情報は永続化しないといけないので、busyboxを使って `/data`、 `/var/lib/mysql` を保存するようにしています。`docker/datastore/Dockerfile` を用意して docker-compose.yml の中で datastore として定義しています。

メインのDockerfileは見ての通り簡単な構成でGemfileをコピーしてbundle installしているだけです。runした時に rails s が実行されて、3000番ポートがコンテナのポートにマッピングされます（この表現が正しいのか不明）。

### rails 側の database.yml などの設定ファイルの書き換え

Dockerで開発環境を構築できるものの、今までローカルに環境を構築していた人はそのままで開発したいケースもありますし、導入後しばらくは問題があった場合に以前の開発スタイルに戻せるように、Dockerがなくても問題なく開発できるようにしました。

具体的には、環境変数が定義されていればそれを使いそうでなければ localhost を見るという設定にしています。

```config/database.yml
development:
  <<: *default
  database: db
  encoding: utf8mb4
  charset: utf8mb4
  collation: utf8mb4_general_ci
  host: <%= ENV["DATABASE_HOST"] || "localhost" %>
  port: 3306
  password: password
```

DATABASE_HOSTという環境変数は docker-compose.yml で定義していますのでご確認ください。
memcachedやredisのホストも同様に設定しています。


## 4. 各個人PCでのセットアップ

初期状態のMacでも git と dockertoolbox （とエディタ）だけ入れて開発がスタートできました。

**セットアップ**

```
$ docker-machine create --driver virtualbox dev
$ eval "$(docker-machine env dev)"
$ docker-compose build
```

**マイグレーション**

```
$ docker-compose run web bundle exec rake db:create db:migrate db:seed RAILS_ENV=development
$ docker-compose run web bundle exec rake db:create db:migrate RAILS_ENV=test

**立ち上げ**
$ docker-compose up
```

**アクセスするURL（デフォルト）**
```
http://192.168.99.100:3000
```

**テスト実行**

```
$ docker-compose run web bundle exec rake spec
```

**注意：Gemfileを更新したら**

```
$ docker-compose build
```
を忘れずにやる。

# FAQ

## ビルド終わってて再起動したあとはどのコマンドを実行すればよいの？

下記3コマンドを実行してください

```
$ docker-machine start
$ eval "$(docker-machine env dev)"
$ docker-compose up
```


## pry使いたい

```
docker-compose run --service-ports app
```
で立ち上げるとできる。
ただこの立ち上げ方だと終了時にプロセスがうまく Kill されずに残って次の起動時にポートが取られてる場合があるのでめんどくさいこともあった。

## なんか docker-compose up で立ち上がらなくなった

たまにあるけど原因がよくわからないことも。

```
$ docker-compose stop
$ docker-compose kill
```

あたりを試してみる。だめなら

```
$ docker-machine restart
$ eval "$(docker-machine env dev)"
```

とか。

それでもだめなら

```
$ docker-machine rm dev
```

をして最初からセットアップする。

## mysqlとかが起動しない

ローカルでmysqld や memcache が動いていないか確認する。ポートがかぶっていると立ちあげられないので。

## vendor/bundle が共有されてしまう問題

.dockerignore を設定してもうまくいかなかったので解決してません。 ローカルで bundle install --path vendor/bundle と言った形でパスを指定してbundle installを行っていると、その .bundle/config がDocker側でも利用されてしまいうまく rails が起動しないことがあった。.bundle/configを削除したら動いた。


# おわりに

Docker側にsshでログインしてそこで直接ファイルを編集したりするスタイルのが良いかなとも思ったのですが、色々試してこの形に落ち着きました。他にも docker-osx-dev を使う構成にしてみたりとか何が最適なのかは難しいですね。

まだ運用始めて間もないので問題も出てくるかなと思ってます。
何かあればコメントいただけると幸いです。