Vagrant+Dockerでローカルとコンテナ内が同期する開発環境の構築

※2014/10/19追記
  figを使ってもっとお手軽に同じようなことをする構成を別記事にしました！
  http://qiita.com/toritori0318/items/190fd2dad2bf3ce38b88


<br><br>


Mac上でVagrantを起動し、その中にDockerコンテナを立ちあげ、
コンテナ内のソースとローカルのソースを同期させる開発環境を構築してみます。

# そもそも何が目的なのか？
開発環境の配布です。
今までは setup.sh のようなもので
プログラム言語／関連モジュール／ミドルウェアなどをローカルにインストールして
開発環境を整えていましたが
共有ライブラリのバージョン違い／元環境とのコンフリクト／エンジニアのスキルセットなどにより
上手くいかないことも多々ありました。

そこで、すべての環境はVagrant(Docker)に置いて環境依存を無くし、
かつローカルのソースコードと同期することによって
あたかも全ての環境がローカルに存在するかのごとく振る舞うことが可能なVagrant環境を構築してみました。
つまり **Vagrantだけあればすぐに開発できる環境が入手できる** ということになります。

また、Vagrant直でやらないのは全部入りの環境を提供して作業させようとすると
VM起動が遅くそれがストレスになるからです。
アプリケーションだけでなくミドルウェアも含めてコンテナ化することで
軽量かつ汎用的な環境を配布できると考えました。


# サンプルソースコード
こちらに置いてあります。
Vagrantは1.6以上が必要です。
https://github.com/toritori0318/flask-vagrant-docker-sample

  
<br>

# サンプルの概要／ポートマッピング図
３つのコンテナを用意しています。

- nginx
- python-app
- redis

[python-app](https://github.com/toritori0318/flask-vagrant-docker-sample/blob/master/app/server.py)は
**アクセスする毎にカウントをインクリメントして表示**
するだけの簡単なアプリです。Redisに情報を保存しています。

![スクリーンショット 2014-09-15 3.08.52.png](https://qiita-image-store.s3.amazonaws.com/0/9099/90ed85a0-b68f-91f8-70d9-a4615998a805.png)

nginxはpython-appの5000番ポートにリバースプロキシしており、
python-appはredisの6379番ポートにアクセスしています。

各コンテナはそれぞれ
nginx(10080)/python-app(15000)/redis(16379)
に[ポートフォワード](https://github.com/toritori0318/flask-vagrant-docker-sample/blob/master/vagrant-docker.proxy#L34)させ、ローカルMacから直接アクセスできるように設定しています。

<br>

# ディレクトリ同期概要図

![スクリーンショット 2014-09-15 3.09.06.png](https://qiita-image-store.s3.amazonaws.com/0/9099/cc2d8bae-7c37-fbd6-61aa-4c68d8c2969d.png)

Mac⇔Vagrant間は[synced_folder(NFS)](https://github.com/toritori0318/flask-vagrant-docker-sample/blob/master/vagrant-docker.proxy#L26)で、
Vagrant⇔Dockerコンテナ間は[volume(正確にはdocker providerのvolumes)](https://github.com/toritori0318/flask-vagrant-docker-sample/blob/master/Vagrantfile#L13)で
実現しています。

Mac上の./logディレクトリは nginxコンテナのログ全般と、appサーバのデバッグログが同期されます。
./appディレクトリはpython-appコンテナのアプリケーションディレクトリと同期させています。

## コンテナ間の通信について
linkを使います。
python-app ⇔ redis間は[このリンク](https://github.com/toritori0318/flask-vagrant-docker-sample/blob/master/Vagrantfile#L30)を、
nginx ⇔ python-app間は[このリンク](https://github.com/toritori0318/flask-vagrant-docker-sample/blob/master/Vagrantfile#L17)を 設定しています。
python-app内でredisを参照しているのは[ここ](https://github.com/toritori0318/flask-vagrant-docker-sample/blob/master/app/server.py#L8)で環境変数を読み込んでいるだけです。
nginxでは、直接nginx.confで環境変数を読み込むのが上手くいかなかったので<sub><a href="#1">1</a></sub>
[run.sh内でsed置換](https://github.com/toritori0318/flask-vagrant-docker-sample/blob/master/docker-container/nginx/run.sh#L5)しています。


## 開発フロー
ローカルMac上のappディレクトリ以下のファイルを修正します。
すると、python-appコンテナ上のappにも修正が反映し
自動で適用されます(自動適用はflaskの機能)。
また、ログファイルはローカルMac上の ./logディレクトリにリアルタイムで反映されるのでデバッグも容易です。


<br>


# やってみよう

1. まずは Dockerコンテナを起動します。  

    vagrant up --provider=docker redis python-app nginx

2. うまく動いているか確認してみます。  

    curl http://localhost:10080/

    ![スクリーンショット 2014-09-15 4.32.36.png](https://qiita-image-store.s3.amazonaws.com/0/9099/453e3772-130c-26c9-086a-d6e0054582bb.png)

3. ./app/server.pyを修正してみます。

    ![スクリーンショット 2014-09-15 4.36.14.png](https://qiita-image-store.s3.amazonaws.com/0/9099/8e61eeb6-8052-01c4-e02f-a360bd3c68f3.png)

4. しばらくすると自動で修正が反映された！

    ![スクリーンショット 2014-09-15 4.36.24.png](https://qiita-image-store.s3.amazonaws.com/0/9099/3c67d6ca-6e9a-f9f4-0a6f-3ad683cab0f5.png)

ローカルファイルだけでなく、
Dockerコンテナ内のファイルが修正されているのがポイントですね。

<br>

# その他Tips
### Dockerホストにログインするには？
vagrant sshでログインしたいところですが、
このVagrantfileで起動するとカレントホストには表示されません<sub><a href="#1">2</a></sub>
ですが、vagrant global-statusでidを探してログインすることは可能です。

    vagrant ssh <id>

しかし、いちいちid指定するの面倒なので ssh-config に登録してしまいましょう。
これならvmを作りなおしたとしてもsshコマンドでそのままログインできます。

    vagrant ssh-config <id>
    （ホスト名が「default」になっているのでわかりやすいものに変えたほうが良いでしょう）

### コンテナにログインしたい場合は？
サンプルでホストイメージとして使っている yungsang/boot2docker は
nsenter入りなので、それを使うと良いでしょう。
さらに docker-enterという便利コマンドもついているので
お手軽にログイン可能です

    # docker-host上からログイン
    docker-enter <コンテナID>

    # Macから直接最新コンテナにログイン
    ssh <ssh-configで設定したホスト名> 'docker-enter $(docker ps -l -q)'

### ローカルPCからDockerコマンド実行したいんだけど
dockerコマンドインストール後にDOCKER_HOSTを指定すればOKです。

    export DOCKER_HOST=tcp://localhost:2375

というかdockerコマンドはあったほうが便利なので
brewでインストールしておきましょう。

### 再度Docker Buildしたい場合は？
vagrant reload <vm_name> すると、Docker buildを再実行します。

<br>

# 問題点
Macの電源を落として再度Mac起動した後でDockerコンテナ起動しようとすると
volumeの適用がうまく行かずにエラーが出てしまいます。

![スクリーンショット 2014-09-17 2.28.16.png](https://qiita-image-store.s3.amazonaws.com/0/9099/b7c882d0-9c39-6a0e-1191-f9446eb1bfe4.png)


これは、一度コンテナを削除して再度作りなおすとうまくいくのですが
このオペレーションを開発者に強制するのも酷ですし、
ここはどうにかして回避したいと考えています。
もしかしたらDocker-providerを使わずにfigなどで自前起動すれば問題ないのかな〜と思っているので
今後検証してみる予定です。

<br>

# まとめ
コンテナ上のファイルと同期して、
ローカルPCに直接環境構築することなく開発できる環境を作ってみました。
この構成、まだいくつか問題があって絶賛検証中なので
もっと良い方法があればご教授頂ければと思います！

<br>
<br>

<a name="1">1.</a> うまいやり方があれば知りたい
<a name="2">2.</a> もしかして表示する方法あるんでしょうか？