Docker for Mac の NFS Volume sharing のベンチマーク結果(2018-03-29現在)

# Docker for Mac の Volumeマウントの遅さについて

Docker for Mac は正式版になる以前からローカルファイルシステムをマウントした際の、read/writeの遅さが指摘されていた。特に write 側は絶望的なベンチマークがいつも示されていた。

私も、2017年10月に以下の記事でその当時のベンチマークをしている。

- [MacおよびWinにおけるDocker環境のベンチマーク結果まとめ(2017-10-23現在)](https://qiita.com/kunit/items/0c1d4badacbb5414c178)

このまま改善されないまま進むんではないかと悲観的に見ていたのだが、 18.03.0-ce の rc1 のリリースノートに以下のように `Support NFS Volume sharing` という記述があったのに気がついた。

```md
### 2018-02-27 18.03.0-ce-rc1-mac54 "18.03.0-ce-rc1-mac54"

* Upgrades
  - [Docker 18.03.0-ce-rc1](https://github.com/docker/docker-ce/releases/tag/v18.03.0-ce-rc1)

* New
  - VM Swap size can be changed in settings. See [docker/for-mac#2566](https://github.com/docker/for-mac/issues/2566), [docker/for-mac#2389](https://github.com/docker/for-mac/issues/2389)
  - Support NFS Volume sharing. Also works in Kubernetes.
```

Docker for Mac には以前から、 [d4m-nfs](https://github.com/IFSight/d4m-nfs) というものがあり、コンテナ側で nfs-client を動かして、Macのファイルシステムを NFS マウントするというものがあったが、それとどう違うんだ？と情報を探していたんだが、ようやくやり方がわかったので、ついでにベンチマークしてみた。

# Docker for Mac での ローカルファイルシステム NFS マウントのやり方

Docker for Mac でローカルファイルシステムを NFS マウントするには以下の手順が必要となる

- `/etc/exports` に共有するディレクトリの設定をする
- `/etc/nfs.conf` に NFS の設定を追加する
- `sudo nfsd restart` で設定反映
- `docker-compose.yml` に Volume の設定をする

## `/etc/exports` に共有するディレクトリの設定をする

`/etc/exports` には以下のように設定する。

```
"/Users" localhost -alldirs -mapall=501:20
``` 

上記の設定は以下のような意味となる。

- `/Users` 以下のすべてのサブディレクトリの NFS マウントを許可する
    - この例はかなり大雑把な設定をしているので、実際に使う際には自分のホームディレクトリの特定の作業ディレクトリ以下を許可する形になると思う
- localhost からの NFSマウントを許可する
- NFSマウントしたものに対する書き込みは、uid 501、gid 20 でアクセスしたものとする
    - Mac で一人目のアカウントを追加すると uid 501 gid 20 で追加される
    - ターミナルで id コマンドを実行すると作業ユーザの情報が表示されるので、それを確認して 501 でなければそれを設定する

## `/etc/nfs.conf` に NFS の設定を追加する

`/etc/nfs.conf` には以下のように設定する。

```ini
#
# nfs.conf: the NFS configuration file
#
nfs.server.mount.require_resv_port = 0
``` 

最後の１行だけ追加している。この設定をしておけば、1024以上のポートからの接続でもNFSマウント可能となる。

## `sudo nfsd restart` で設定反映

`/etc/exports` と `/etc/nfs.conf` の設定変更を反映するために、 `sudo nfsd restart` を実行して、 nfsd を再起動する。

ここまでは最初に１回やっておけば、次回以降はしないでもOK。

## `docker-compose.yml` に Volume の設定をする

ここまで準備できれば、以下のように設定すれば、 NFS マウントが可能となる。

```yaml
version: '3.6'
services:
    db:
        image: mysql:5.7
        hostname: db
        container_name: db
        environment:
            - MYSQL_ROOT_PASSWORD=root
        ports:
            - "3306:3306"

    web:
        build: .
        image: centos69-apache-php
        hostname: web
        container_name: web
        volumes:
            - nfs-htdocs:/var/www/html
        ports:
            - "8080:80"
        depends_on:
            - db

volumes:
  nfs-htdocs:
    driver: local
    driver_opts:
      type: nfs
      o: addr=host.docker.internal,actimeo=1
      device: ":${HOME}/nfs-test/htdocs"
```

上記の設定は、今までの通常マウントと以下のような違いがある。

- volumes での設定 NFS をつかった volume 設定をしている
    - driver_opts に `type=nfs` を指定することにより、NFSを使ったVolumeになる
    - 接続先ホストに `host.docker.internal` を指定することにより、Mac のローカルIPに接続する
    - device に マウントするローカルディレクトリを指定する。ここではフルパスをきちんと指定しないといけないため、`${HOME}` を使うことにより、ディレクトリ構成だけ合わせておけば、ユーザが変わっても大丈夫にしている
    - この例では `nfs-htdocs` という名前で volume 設定している
- services 側のコンテナ側の volumes には、 volumes セクションで設定した名前を指定する
    - この例では `nfs-htdocs` という volume をコンテナの `/var/www/html` にマウントする

# 実際にはどういう挙動をしているのか？

Docker for Mac の NFS volume share は以下のように実現されている。

- Docker for Mac は moby linux が VM として動作しており、その上で各Dockerコンテナが動いている
- NFS volume share は、この moby linux がローカルファイルシステムを nfs マウントして、さらにコンテナに対する volume share を行っている
- 以下のコマンドで、moby linux に接続できる
    - `screen ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/tty`
    - 内部的には `/var/lib/docker/volumes` ディレクトリ以下で管理されている

通常の volume マウントを行って、コンテナで mount コマンドをうつと以下のように表示される。

```
osxfs on /var/www/html type fuse.osxfs (rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other,max_read=1048576)
```

NFS volume share を行って、コンテナで mount コマンドをうつと以下のように表示される

```
:/Users/kunit/nfs-test/htdocs on /var/www/html type nfs (rw,relatime,vers=3,rsize=65536,wsize=65536,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=192.168.65.2,mountvers=3,mountproto=tcp,local_lock=none,addr=192.168.65.2)
```

# ベンチマーク結果(2018-03-29現在)

以下の記事と同じ条件でベンチマークを再度取ってみた。

- [MacおよびWinにおけるDocker環境のベンチマーク結果まとめ(2017-10-23現在)](https://qiita.com/kunit/items/0c1d4badacbb5414c178)

ベンチマークを取った環境は以下のもの

- MacBook Pro (Retina, 13-inch, Early 2015)
    - 3.1 GHz Intel Core i7
    - 16GB Memory
    - 1TBフラッシュストレージ
- macOS Sierra 10.12.6
- Docker for Mac edge version 18.03.0-ce-mac58 (23607)

|           |   dd      |  symfony-3.0 bench(１回目/２回目/3回目) |
| --------- | --------- | ------------------ |
| Vagrant + Virtualbox | 0m1.861s |   89.29 / 104.96 / 104.86 |
| Vagrant + Virtualbox + CoreOS Docker | 0m1.851s | 66.65 / 98.06 / 97.53 |
| Docker for Mac(通常マウント) | 0m18.311s | 45.49 / 66.20 / 65.72 |
| Docker for Mac(cached mount) | 0m18.942s | 142.55 / 159.52 / 157.64 |
| Docker for Mac(delegated mount) | 0m18.518s | 146.31 / 169.33 / 167.12 |
| Docker for Mac(nfs mount) | 0m2.657s | 175.77 / 198.60 / 204.16 |
| Docker for Mac(docker-sync) | 0m0.256s | 205.92 / 227.85 / 218.98 |

## 結果考察

- Vagrant を使ったものは dd の結果が昨年と10倍遅い。何度やってもこの結果になったのだが理由がわからない・・・
- Docker for Mac の通常マウント/cachedマウント/delegatedマウントは相変わらず dd つかったベンチマークは絶望的
- Docker for Mac の cachedマウントとdelegatedマウントの symfony-3.0 を使ったベンチマークが何故かかなり良くなっている。Docker for Macが地味に改善されていた？
- 今回追加された NFS マウントだが、 dd の結果が大幅に改善されている。今回何故か遅くなってしまった Vagrant の結果とほぼ同じなので大健闘(Vagrant も nfs マウントしてるので同じスピードになるのはある意味納得ではあるんだが)
- docker-sync は相変わらず爆速

# まとめ

18.03 で使えるようになった NFS Volume share は、nfsの設定を少しすれば気軽につかえるし、docker-sync ほどめんどくさくないのでかなりいいと思う。(docker-sync での運用は今までしてないが、よく落ちるという話もちらほら聞くし)

これくらいのスピードがでるならば、もう通常のボリュームマウントは使う必要はないと思います。

## 2018-03-30 追記

以下はもともとまとめに書いた内容だったが、ちょっと用途が特殊だったようだったのでざっくり打ち消し。
(一応、フォローしておくと、Dockerファイル内で VOLUME 指定しているディレクトリをローカル側にマウントする場合は注意が必要かもしれないということかも)

~~これくらいのスピードがでるならば、もう通常のボリュームマウントは使う必要はない・・・と言いたいんだが、今日色々試してみたところ以下のような落とし穴があった。~~

- ~~MySQL コンテナのデータボリューム(/var/lib/mysql)にNFSマウントしたvolumeを使おうとすると、コンテナ起動時(ENTRYPOINT)のデータの初期化時にパーミッションが問題となりうまくコンテナが起動しない~~

~~既に仕組みを説明したとおり、Docker for Mac の NFS マウントは間に入っている moby linux に一旦マウントしてから、各コンテナで利用するんだが、moby linux にマウントした際に、MySQL コンテナのデータボリュームが rootユーザの所有になってしまって、その後の chown コマンドでの mysql ユーザ所有への変更部分で落ちてしまう。~~

~~実際のログは以下の通り。~~

```
chown: changing ownership of '/var/lib/mysql/': Operation not permitted
```

~~この回避方法がわからない限り、MySQLコンテナのデータボリュームは今まで通りのマウントを続けないと行けない。しかし、データベースのデータボリュームは read/write が一番激しい場所のはずなので、ここにこそ NFS マウントでの速度アップを適用したいのだが・・・~~

~~明日以降もいろいろと試してうまく行ったらここに追記するが、うまく行ったぞという方がいらっしゃったらコメントお願いします・・・(本当にお願いします)~~

# 2018/04/04 追記

もともと記述していた NFS Volume の設定ですが、特にオプションを指定してませんでした。

それで少し運用してみたところ、ローカルファイルシステムのファイルの修正が、コンテナ側になかなか反映されなかったので、以下のように修正しています。これでしばらく運用してみようと思います。

driver_opts に actimeo オプションを追加

```yaml
volumes:
  nfs-htdocs:
    driver: local
    driver_opts:
      type: nfs
      o: addr=host.docker.internal,actimeo=1
      device: ":${HOME}/nfs-test/htdocs"
```