docker-syncを使わないホスト・コンテナ間での爆速ファイル同期

#追記 20170407 Docker for Macでconsistencyを指定することでパフォーマンスを改善できるようになりました。

unisonを使わなくても本体の機能である程度解決できるようになりました。
下記の計測してるので参照ください。
http://qiita.com/kai-zoa/items/5f38909d37e0e4e36a28

#元記事
この記事は[WHITEPLUS Advent Calendar 2016](http://qiita.com/advent-calendar/2016/whiteplus) 24日目になります。

開発環境整備担当兼クリーニング師の kai-zoa です。

20日目に書いた[Docker for Macのファイル共有が遅いので計測してみる](http://qiita.com/kai-zoa/items/d2d2f0cb5ff1b51e65c7)でdocker-for-mac公式は問題を認識しているが優先度低いという旨のコメントを頂いたので、なにかしら対策を講じたい気持ちになり調査しました。

すでにdocker-syncを使う方法は他の方が紹介されているので、今回は使わない方法について書きたいと思います。

実際に開発環境に組み込んでデザイナー・エンジニアの両チームに共有するのにあらかじめrubyを入れてもらったり、gemを使えるようにしてもらうのが面倒くさい方にオススメです。（というのが私の都合です）

環境はmacOSです。

#unisonを使います

いきなりですが、これが答えです。

docker-syncでも使われていますが、同じようにunisonを使ってホストとコンテナのファイルシステムを相互同期します。

DockerHubにalpineベースのシンプルなunisonサーバーのDockerイメージがあったので参考にしました。
[onnimonni/unison](https://github.com/onnimonni/docker-unison)

このようにunisonサーバーをデータコンテナーにして、同期するディレクトリはアプリケーション側のボリュームにマウントします。

<img width="497" alt="Screen Shot 2016-12-24 at 3.47.47 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/27339/2a45ef70-5f39-1a37-315d-e4604658f988.png">

docker-composeの定義はこんな感じです。

```yaml:docker-compose.yml
version: '2'

services:
  data:
    image: whiteplus/unison
    container_name: piyo_data
    volumes:
      - /var/www/piyo
    ports:
      - "5000:5000"
    environment:
      - UNISON_DIR=/var/www/piyo
  web:
    container_name: piyo_web
    build: .
    depends_on:
      - data
    volumes_from:
      - data
    ports:
      - "80:80"
      - "443:443"
    environment:
      - NGINX_PATH_PREFIX=/var/www/piyo
```

※ データコンテナー側にinotify-toolsを入れたかったのでonnimonni/unisonに手を入れてwhiteplus/unisonにしてます。

全ソースはこちら
[github.com/WHITEPLUS/advent-calendar2016-docker-unison-example](https://github.com/WHITEPLUS/advent-calendar2016-docker-unison-example)

#動作確認

起動します。

```bash
$ docker-compose up -d
```

起動直後はプロジェクトのファイルを同期してないのでwebにアクセスすると404になります。

```bash
$ open http://localhost
```

<img width="444" alt="207bc9ba-c3e3-4e30-9acc-c68fb75eb666.png" src="https://qiita-image-store.s3.amazonaws.com/0/27339/812c8ab9-7d45-9d96-7c02-50cd06f904c5.png">


##ホストのファイルをデータコンテナに同期する
それではunisonクライアントをインストールして、./webappのソースをデータコンテナに同期してみます。

```bash
$ brew install unison
$ unison ./webapp socket://127.0.0.1:5000/ -auto -batch
Contacting server...
Connected
〜
  Waiting for changes from server
Reconciling changes
dir      ---->            html
Propagating updates
〜
Synchronization complete at 21:12:35  (2 items transferred, 0 skipped, 0 failed)

$ open http://localhost #開いてみる
```

<img width="261" alt="3dbec3ee-5c0a-4b9f-a489-f4ea29eabcaf.png" src="https://qiita-image-store.s3.amazonaws.com/0/27339/e29167ca-35ce-8a53-8aad-fbee3174ff90.png">


WAO! ちゃんと表示されました

##データコンテナからファイルを編集して同期する

それでは逆にデータコンテナからファイルを編集して同期してみましょう

```bash
$ docker exec -it piyo_data sh # データコンテナに入る
$ cd /var/www/piyo/html/
$ echo PIYOPIYO > index.html #データコンテナからファイルの内容を変更
$ exit

$ unison ./webapp socket://127.0.0.1:5000/ -auto -batch #同期
Contacting server...
Connected
〜
Looking for changes
  Waiting for changes from server
Reconciling changes
         <---- changed    html/index.html
Propagating updates
〜
Synchronization complete at 21:21:53  (1 item transferred, 0 skipped, 0 failed)

$ cat ./webapp/html/index.html #変更がホストに反映されてることを確認する
PIYOPIYO
```

WAO! うまくいきました

#ファイル変更を監視して同期する

ここまでできたら、ホストとコンテナでお互いのファイル変更イベントを検知して、unisonを動かすようにすれば完璧ですね。

そこでお互いのinotifyイベントを監視して、ホスト側にログ出力し、tailからxargsでunisonに繋ぐスクリプトを書いてみました。（githubの方にもコミットしています）

unison-fsmonitorをmacOSに入れればこんなもの必要ないかもしれないですがまだ試してません

```bash:sync.sh
#! /usr/bin/env bash

LOCAL_DIR=./webapp
REMOTE_DIR=/var/www/piyo
REMOTE_CID=piyo_data

recv_local() {
  while read -r event; do
    test -n "${event}" && \
    echo "LOCAL: ${event}" >> events.log
  done
}

recv_remote() {
  while read -r event; do
    event=$(echo -n $event \
    | grep -E '(CREATE|MOVE|MOVED_TO|MOVED_FROM|MODIFY|ATTRIB|DELETE|DELETE_SELF)[,:]' \
    | sed -e 's/^[A-Z,_]*://g' \
    | tr -d '\n')
    test -n "${event}" && \
    echo "REMOTE: ${event}" >> events.log
  done
}

UNISON="unison ${LOCAL_DIR} socket://127.0.0.1:5000/ -auto -batch"

${UNISON}

rm -f events.log
touch events.log

fswatch ${LOCAL_DIR} 2>&1 | recv_local &
CHILDREN=$!

docker exec -it ${REMOTE_CID} inotifywait -m -r --format '%e:%w%f' ${REMOTE_DIR} 2>&1 | recv_remote &
CHILDREN="${CHILDREN} $!"

trap "kill -KILL ${CHILDREN} > /dev/null 2>&1" INT TERM

tail -f ./events.log | xargs -I{} ${UNISON}
```

実行するとホストとコンテナ双方のファイル監視と同期をし続けてくれます。

```bash
$ ./sync.sh
〜
```

#積み残し

上記スクリプトだとunisonが同期した結果のファイル変更イベントを検知してunisonが起動するという無駄な動きもしてしまうので改良の余地があるなと思いました。

これ以上、並行処理でなんやかんややるならGoで書きたいですね‥

その前にfsmonitorを使った場合の動作確認をしよう！

#明日
exmeatのmeatの話ですっ

#ホワイトプラスではエンジニアを募集しています
ホワイトプラスでは、[新しい技術にどんどん挑戦したい！という技術で事業に貢献したいエンジニアを募集](https://www.wantedly.com/projects/69257)しております。!