Edited at

Docker for WindowsをWSLから使う時のVolumeの扱い方


TL;DR


  • Docker for WindowsをWSL(旧Bash on Ubuntu on Windows)から利用する際、バインドマウントする場合は注意が必要。

  • Cドライブを共有に設定をしていれば、ホスト側ファイルシステムの/c/Cにマウントされているので、/c/Users/hoge/fugaの形式で参照しよう。

  • WSL内のディレクトリ(/home/hoge/fugaなど)は使えない。


環境


  • Windows 10 Pro Fall Creators Update

  • Docker for Windows Version 17.09.0-ce-win33 (13620)

  • WSL:Ubuntu 16.04 LTS + docker 17.09.0-ce + docker-compose version 1.8.0


注意


  • DockerのVolume周りの知識のある方は読んでも時間の無駄だと思います。

  • 間違ってる箇所があればごめんなさい。


1. Docker for WindowsをWSLで使う方法

このあたりを参考に

WSL(Bash on Windows)でDockerを使用する - Qiita

ひょっとするとこれでも良いかもしれませんがw

【小ネタ】Docker for WindowsをWSLで使う一番楽な方法 - Qiita


2. 【復習】PowerShell(またはコマンドライン)でローカルディレクトリをマウントする場合

まずはWSLでなく単純にDocker for WindowsをコマンドラインやPowerShellから普通に使う場合の確認。

例えばMySQLのデータディレクトリ/var/lib/mysqlC:\hoge\fugaに保存する(Bind mountする)には、以下のように指定する。


2-1. docker run -vオプションの場合

-v (ローカルディレクトリ):(コンテナのディレクトリ)で指定する。


PowerShell

PS C:\hoge> docker run -v C:\hoge\fuga:/var/lib/mysql -e "MYSQL_RANDOM_ROOT_PASSWORD=yes;MYSQL_DATABASE=hogedb;MYSQL_USER=admin;MYSQL_PASSWORD=pass" -p 3306:3306 mysql



2-2. docker-composeの場合

docker-composeを使う場合は、volumes:に記述する。


docker-compose.yml

version: '2'

services:
mysql:
image: mysql
volumes:
- C:\hoge\fuga:/var/lib/mysql
ports:
- 3306:3306
environment:
- MYSQL_RANDOM_ROOT_PASSWORD=yes
- MYSQL_DATABASE=hogedb
- MYSQL_USER=admin
- MYSQL_PASSWORD=pass


PowerShell

PS C:\hoge> docker-compose up


これでC:\hoge\fuga/var/lib/mysqlにマウントされる。

基本ですね。


2-3. 共有設定について

マウントする際の注意点として、マウントしたいディレクトリは、事前に設定画面の[Shared Drives]でチェックを入れてドライブレベルで共有設定をしておく必要がある。

image.png

共有していないドライブのディレクトリをマウントしようとすると、以下の画面が出て設定を促される。(Y:\hoge\fugaを指定してみた)

image.png


3. WSLでローカルディレクトリをマウントする場合

ここから本題。


3-1. docker run -vオプションの場合

WSLのBashで同じことをしようとした場合、Windowsのパス区切り文字である\がBashのエスケープ文字のため、\\にする必要がある。


Bash

$ docker run -v C:\\hoge\\fuga:/var/lib/mysql -e "MYSQL_RANDOM_ROOT_PASSWORD=yes;MYSQL_DATABASE=hogedb;MYSQL_USER=admin;MYSQL_PASSWORD=pass" -p 3306:3306 mysql


これで問題なし。

ただし、エスケープが面倒なら/c/hoge/fugaでもOK。(理由は後述)


3-2. docker-composeの場合

Windowsのパス形式は使えない。

docker-compose.ymlにWindowsのパス形式で記述しているとエラーになる。


Bash

$ docker-compose up

ERROR: Named volume "C:\hoge\fuga:/var/lib/mysql" is used in service "mysql" but no declaration was found in the volumes section.

エラーメッセージを見ると、C:\hoge\fugaNamed volume(docker volume createコマンドで作成するvolume)として認識されてしまっている。

これを回避するには、docker-compose.ymlを以下のように修正する必要がある。


docker-compose.yml

version: '2'

services:
mysql:
image: mysql
volumes:
- - C:\hoge\fuga:/var/lib/mysql
+ - /C/hoge/fuga:/var/lib/mysql
ports:
- 3306:3306
environment:
- MYSQL_RANDOM_ROOT_PASSWORD=yes
- MYSQL_DATABASE=hogedb
- MYSQL_USER=admin
- MYSQL_PASSWORD=pass

Linuxのdocker-composeがWindowsのパス形式を理解できないのが原因。

docker run -vの場合と同様、/c/hoge/fugaの形式なら利用できるので、変換しておく必要がある。

WindowsのDocker ToolboxやDocker Machineなら環境変数COMPOSE_CONVERT_WINDOWS_PATHS=1をセットすれば内部的に/c/hoge/fugaのように変換して解釈してくれたが、残念ながらこの方法も使えなかった。

Docker for Windowsのデーモンはdocker run -vの指定に対してはこの変換を行うものの、docker-composeには対応していないらしい。(たぶん)

なお、C:\\hoge\\fugaのようにエスケープしてみても無理だった。


4. WSL内のディレクトリの扱い


4-1. WSL内のディレクトリは使えない


・WSL内のディレクトリを直接指定した場合

例えばWSL内の/home/hoge/fugaをマウントしたい場合、そのまま指定すると


Bash

$ pwd

/home/hoge
$ docker run -v $PWD/fuga:/var/lib/mysql -e "MYSQL_RANDOM_ROOT_PASSWORD=yes;MYSQL_DATABASE=hogedb;MYSQL_USER=admin;MYSQL_PASSWORD=pass" -p 3306:3306 mysql

で、一見するとちゃんと動くように見える。

docker inspectで確認すると、/home/hoge/fugaがバインドされている。


Bash

$ docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4884a258cd99 mysql "docker-entrypoint..." 12 seconds ago Up 9 seconds 0.0.0.0:3306->3306/tcp nervous_banach
$ docker inspect nervous_banach
[
{
(省略)
"HostConfig": {
"Binds": [
"/home/hoge/fuga:/var/lib/mysql"
],
(以下略)

ただし、この/home/hoge/fugaホスト(仮想マシン)側のディレクトリなので、WSL側の/home/hoge/fugaにはデータは作成されていない。

(MySQLは起動するだけで様々なファイルが作成されるので、起動後に実際にディレクトリ内の確認するとわかるが、空のまま)


・DrvFSの階層を指定した場合

WSLの/home/hoge/fugaは、Windowsのディレクトリ構造上は

C:\Users\hoge\AppData\Local\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\rootfs\home\hoge\fuga

なので、そもそも階層が違う。(Ubuntuの場合)

このパスをWSLから参照する場合、

/mnt/c/Users/hoge/AppData/Local/Packages/CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc/LocalState/rootfs/home/hoge/fuga

になる。

この/mntからWindows上のドライブが参照できる仕組みはDrvFSというファイルシステムで実現されている。

そこで、これを使って


Bash

$ docker run -v /mnt/c/Users/hoge/AppData/Local/Packages/CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc/LocalState/rootfs/home/hoge/fuga:/var/lib/mysql -e "MYSQL_RANDOM_ROOT_PASSWORD=yes;MYSQL_DATABASE=hogedb;MYSQL_USER=admin;MYSQL_PASSWORD=pass" -p 3306:3306 mysql


としてみても、このパスはdocker側には理解できないため、やっぱりコンテナはホスト側のディレクトリを参照してしまう。


・VolFSの階層を指定した場合

WSLの/home/hoge/fuga

C:\Users\hoge\AppData\Local\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\rootfs\home\hoge\fuga

であるなら、

/c/Users/hoge/AppData/Local/Packages/CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc/LocalState/rootfs/home/hoge/fuga

を指定すれば使えるか?と思いきや、これも上手く行かない。

dockerホスト側はちゃんと

/c/Users/hoge/AppData/Local/Packages/CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc/LocalState/rootfs/home/hoge/fuga

に書き込みに行くが、そもそもこの階層はWindowsで直接扱えないため、ネットワーク共有越しにも書き込めない。

そのためか、ホスト上のこの階層にファイルは作られるものの、これらはホスト上にのみ存在することになり、クライアント側には書き込まれない。

正直この辺は調査不足で、具体的にどういう理由でこういう動きをするのかはよくわからない:sweat_smile:

ただ、仮に書き込めたとしても、DockerのサービスがWindows上で上記のフォルダにファイルを作成することになるため、WSL側で読むことができないハズ。

参考:Windows Subsystem for Linuxのファイルシステムにおける注意点 - Qiita

…というか、そもそもこんな深い階層を指定したくないわなw


4-2. docker-composeで相対パスを指定すると意図しないパスに変換される

上記の通りWSLのディレクトリ構造は使えないのであまり意味はないが、docker-compose.ymlで相対パスを指定するとおかしな変換がかかる。


docker-compose.yml

version: '2'

services:
mysql:
image: mysql
volumes:
- ./fuga:/var/lib/mysql
ports:
- 3306:3306
environment:
- MYSQL_RANDOM_ROOT_PASSWORD=yes
- MYSQL_DATABASE=hogedb
- MYSQL_USER=admin
- MYSQL_PASSWORD=pass


Bash

$ pwd

/home/hoge
$ docker-compose up -d
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9872e250c33d mysql "docker-entrypoint..." About a minute ago Up About a minute 0.0.0.0:3306->3306/tcp hoge_mysql_1
$ docker inspect hoge_mysql_1
[
{
(省略)
"HostConfig": {
"Binds": [
"/mnt/c/hoge/fuga:/var/lib/mysql:rw"
],
(以下略)

./mnt/c/hogeに変換されてしまう。

/home/mnt/cと解釈するみたいだが、理由は謎だし調べる気も起きない


5. そもそもマウントとは

ここまで色々試してみたが、マウントの仕組みを理解していれば結構当たり前の内容かもしれない。

Dockerにおけるマウントは、基本的に以下の三種類ある。


  • volume

  • bind mount

  • tmpfs mount

image.png

Manage data in Docker | Docker Documentationより引用)

細かい説明は省くとして、図で明らかなように、Volumeをマウントするという行為は全てホスト側のファイルシステム内の話。

Docker for Windowsでdocker run -v C:\hoge:/var/hoge ...しか使った事がないと、何か不思議な力でローカルのC:\hogeにコンテナが直接読み書きを行ってくれているように感じるが、dockerのデーモン的にはホスト内の指定されたディレクトリを直接読み書きしているだけで、その場所がたまたまネットワーク越しにマウントされていた、と考えるとわかりやすい。

実感を得るためホストである仮想マシンのMobyLinuxVMの中身を確認してみると、2-3. 共有設定についてで共有にしたドライブがホストの/にマウントされていることが確認できる。

(hostenterについてはこちら


Bash

$ docker run -it --privileged --pid=host hostenter sh

/ # ls -l
total 56
drwxr-xr-x 2 root root 8192 Nov 28 01:36 C
drwxr-xr-x 2 root root 16384 Nov 27 13:31 D
drwxrwxrwt 6 root root 140 Nov 27 13:02 Database
drwxr-xr-x 2 root root 1680 Sep 27 00:15 bin
drwxr-xr-x 2 root root 8192 Nov 28 01:36 c
drwxrwxr-x 4 dockrema dockrema 80 Sep 27 00:37 containers
drwxr-xr-x 2 root root 16384 Nov 27 13:31 d
drwxr-xr-x 12 root root 2920 Nov 27 13:02 dev
drwxrwxr-x 25 dockrema dockrema 1320 Nov 27 13:02 etc
drwxr-xr-x 3 root root 60 Nov 28 06:35 home
-rwxrwxr-x 1 root root 878 Sep 27 00:15 init
drwxr-xr-x 7 dockrema dockrema 700 Sep 27 00:15 lib
drwxr-xr-x 5 root root 100 Sep 27 00:15 media
drwxr-xr-x 2 root root 40 Nov 28 08:32 mnt
drwxrwxrwx 1 docker docker 0 May 4 2006 port
dr-xr-xr-x 145 root root 0 Nov 27 13:02 proc
drwx------ 2 root root 60 Nov 28 00:27 root
drwxr-xr-x 9 root root 460 Nov 27 13:02 run
drwxrwxr-x 2 dockrema dockrema 2040 Sep 27 00:15 sbin
drwxr-xr-x 2 root root 40 Dec 26 2016 srv
dr-xr-xr-x 13 root root 0 Nov 27 13:02 sys
drwxrwxrwt 5 root root 100 Nov 29 01:34 tmp
drwxrwxr-x 9 dockrema dockrema 180 Sep 27 00:15 usr
drwxr-xr-x 11 root root 4096 Oct 19 07:05 var
/ # ls -l /c
total 7260145
-rwxr-xr-x 1 root root 1 Jul 16 2016 BOOTNXT
drwxr-xr-x 2 root root 4096 Oct 19 06:11 Documents and Settings
drwxr-xr-x 2 root root 0 Nov 16 2016 Intel
drwxr-xr-x 2 root root 0 Feb 15 2017 OneDriveTemp
drwxr-xr-x 2 root root 0 Sep 29 13:46 PerfLogs
dr-xr-xr-x 2 root root 0 Nov 21 13:02 Program Files
dr-xr-xr-x 2 root root 0 Nov 15 13:09 Program Files (x86)
drwxr-xr-x 2 root root 0 Nov 15 09:09 ProgramData
drwxr-xr-x 2 root root 0 Oct 19 06:24 Recovery
drwxr-xr-x 2 root root 0 Oct 26 01:08 System Volume Information
dr-xr-xr-x 2 root root 0 Oct 19 06:11 Users
drwxr-xr-x 2 root root 0 Nov 27 13:31 Windows
drwxr-xr-x 2 root root 0 Nov 27 13:00 _MEI134002
-r-xr-xr-x 1 root root 384322 Jul 16 2016 bootmgr
-rwxr-xr-x 1 root root 41388 Oct 19 05:44 downlevel_2017_10_19_13_17_17_700.log
-rwxr-xr-x 1 root root 6746087424 Nov 27 12:59 hiberfil.sys
drwxr-xr-x 2 root root 0 Oct 19 05:55 inetpub
-rwxr-xr-x 1 root root 419430400 Oct 19 05:46 pagefile.sys
-rwxr-xr-x 1 root root 268435456 Nov 27 12:59 swapfile.sys
/ # cat /etc/mtab
(略)
//10.0.75.1/C /c cifs rw,relatime,vers=3.02,sec=ntlmssp,cache=strict,username=hoge,domain=MyPC,uid=0,noforceuid,gid=0,noforcegid,addr=10.0.75.1,file_mode=0755,dir_mode=0755,iocharset=utf8,nounix,mapposix,nobrl,mfsymlinks,noperm,rsize=1048576,wsize=1048576,echo_interval=60,actimeo=1 0 0
//10.0.75.1/C /C cifs rw,relatime,vers=3.02,sec=ntlmssp,cache=strict,username=hoge,domain=MyPC,uid=0,noforceuid,gid=0,noforcegid,addr=10.0.75.1,file_mode=0755,dir_mode=0755,iocharset=utf8,nounix,mapposix,nobrl,mfsymlinks,noperm,rsize=1048576,wsize=1048576,echo_interval=60,actimeo=1 0 0
//10.0.75.1/D /d cifs rw,relatime,vers=3.02,sec=ntlmssp,cache=strict,username=hoge,domain=MyPC,uid=0,noforceuid,gid=0,noforcegid,addr=10.0.75.1,file_mode=0755,dir_mode=0755,iocharset=utf8,nounix,serverino,mapposix,nobrl,mfsymlinks,noperm,rsize=1048576,wsize=1048576,echo_interval=60,actimeo=1 0 0
//10.0.75.1/D /D cifs rw,relatime,vers=3.02,sec=ntlmssp,cache=strict,username=hoge,domain=MyPC,uid=0,noforceuid,gid=0,noforcegid,addr=10.0.75.1,file_mode=0755,dir_mode=0755,iocharset=utf8,nounix,serverino,mapposix,nobrl,mfsymlinks,noperm,rsize=1048576,wsize=1048576,echo_interval=60,actimeo=1 0 0

CドライブとDドライブを共有設定しているので、/c,/C,/d,/Dがそれぞれマウントされている。

個人的にはドライブレターを大文字小文字を区別しないのが不思議だったが、割りと単純な話だった…。


Conclusion


  • Docker for WindowsをWSLで使いたい場合、Bind mountできるのは通常のWindowsディレクトリ(VolFSはダメ)


  • C:\hoge\fuga=/c/hoge/fugaだという事を意識して、常に後者の指定をしておけばまぁ間違いない

  • WSL上で作業しているとC:\hoge\fuga=/mnt/c/hoge/fugaなのでややこしい

  • 素直にPowerShell使えば良いんじゃ…


TIL


  • 散々言及してきたdocker run-vオプションは推奨されておらず、今は--mountオプションが推奨らしい…。


  • -vだと存在しないディレクトリ・ファイルは勝手に作るが--mountではエラーにするそうだ。確かにその方が良いな。

(2019/08/21 追記)

 上記の記述はv17.09のドキュメントに書いてあった以下のTipを根拠にしていた。


Tip: New users should use the --mount syntax. Experienced users may be more familiar with the -v or --volume syntax, but are encouraged to use --mount, because research has shown it to be easier to use.


 現時点の最新であるv19.07のドキュメントでは以下の記載に変わっていて、かなりトーンダウンしているみたい。


New users should try --mount syntax which is simpler than --volume syntax.


 そのため、今は「-vオプションは推奨されておらず」とまでは言えない気がする。

 まぁDocker Hubなんかのコンテナの説明文でも-vを使う例ばっかりだし…。