Docker上のMySQLのデータをVolumeでホストのディレクトリにマウントすると権限周りで面倒なことになる

Dockerの `volumes` についてあまり知らなかったゆえにハマったのだが……

次のようにdocker-composeでホスト上の任意のディレクトリにマウントさせるようなVolumeを設定すると、環境によっては **コンテナ内のユーザーが持っている権限とマウントされたディレクトリの権限が一致せずコンテナが立ち上がらないことがあります。**

```yml:docker-compose.yml
version: "3"
services:
  db:
    image: mysql:8.0
    volumes:
      - ./data:/var/lib/mysql
      - ./logs:/var/log/mysql
...

```

特にDocker for Macで作った`docker-compose.yml`をLinuxホストのDockerで実行しようとしたときに「コンテナ内の`/var/lib/mysql`にアクセスできない」旨のエラーが発生します。



## 解決法1: データの永続化は名前付きボリュームにする

ホストのディレクトリにマウントするのを諦めて、名前付きボリュームを用いる方法です。次の例では `/var/lib/mysql` の部分を名前付きボリュームにしました。
MySQLのデータのバックアップなど「データの移動」は別の方法を考えましょう。

```yml:docker-compose.yml
version: "3"
services:
  db:
    image: mysql:8.0
    volumes:
      - db-store:/var/lib/mysql
      - ./logs:/var/log/mysql
    environment:

...

volumes:
  db-store:
```


### ちなみに名前付きボリュームはどこかというと

ホストのディレクトリにコンテナのVolumeをマウントする理由の一つとして「コンテナで使ったデータは自分が把握しやすい場所で管理したい」というのがあると思います。
先程の名前付きボリュームにデータを永続化させるとデータはDockerによる管理になり、一見するとどこに行ったかわからなくなります。

そこで、`docker volume ls` でボリュームの名前を調べて、`docker volume inspect`の`Mountpoint`に記載されているpathで名前付きボリュームの保存先を調べることができます。

```
$ docker volume ls
DRIVER              VOLUME NAME
local               1fff58055cdf5d6d088b79ca40970b7cc7b74103647cd2d5cd47292ebc51037b
local               09d44f5c50e868ee1420322768a4bf5de5c6573d48eb9e534c6b2be464542f83
local               sample-docker-compose-api-flask_db-store

$ docker volume inspect sample-docker-compose-api-flask_db-store
[
    {
        "CreatedAt": "2020-02-20T16:00:20Z",
        "Driver": "local",
        "Labels": {
            "com.docker.compose.project": "sample-docker-compose-api-flask",
            "com.docker.compose.version": "1.25.4",
            "com.docker.compose.volume": "db-store"
        },
        "Mountpoint": "/var/lib/docker/volumes/sample-docker-compose-api-flask_db-store/_data",
        "Name": "sample-docker-compose-api-flask_db-store",
        "Options": null,
        "Scope": "local"
    }
]
```

### Docker for Macで名前付きボリュームにアクセスできない

LinuxホストでDockerを動作させている場合では`Mountpoint` にそのままアクセスできますが、Docker for Macの場合は「存在しない」とエラーになります。
これはmacOS上でDockerを動作させてvolumeを作成しているのではなく、Dockerが自動的にLinuxKitと呼ばれるVMを立ち上げてその中にVolumeを作成しているからです。

Docker for MacでVolume内のファイルを確認するにはVMの中に入る必要があります。

```bash
$ ls /var/lib/docker/volumes/sample-docker-compose-api-flask_db-store/_data
ls: /var/lib/docker/volumes/sample-docker-compose-api-flask_db-store/_data: No such file or directory
```

次のコマンドでVMの中に入り、 `Ctrl + D` を入力します。**`Ctrl + D` を入力するまでの間画面には何も表示されません。**

"Welcome to LinuxKit"と表示され、操作可能になると `Mountpoint` で示されたpathにアクセスできます。

```bash
$ screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty

...
__  __

Welcome to LinuxKit

                        ##         .
                  ## ## ##        ==
               ## ## ## ## ##    ===
           /"""""""""""""""""__/ ===
          {                       /  ===-
           _____ O           __/
                        __/
              _________/

docker-desktop login: root (automatic login)

Welcome to LinuxKit!

NOTE: This system is namespaced.
The namespace you are currently in may not be the root.
System services are namespaced; to access, use `ctr -n services.linuxkit ...`
login[5397]: root login on 'ttyS0'
docker-desktop:~# ls /var/lib/docker/volumes/sample-docker-compose-api-flask_db-store/_data/
#innodb_temp        binlog.index        mysql
auto.cnf            ca-key.pem          mysql.ibd
binlog.000001       ca.pem              performance_schema
binlog.000002       client-cert.pem     private_key.pem
binlog.000003       client-key.pem      public_key.pem
binlog.000004       homestead           server-cert.pem
binlog.000005       ib_buffer_pool      server-key.pem
binlog.000006       ib_logfile0         sys
binlog.000007       ib_logfile1         undo_001
binlog.000008       ibdata1             undo_002
docker-desktop:~#

```

`Ctrl + A` `Ctrl + K` と入力し、 `Really kill this window [y/n]` の質問に `y` と入力すると脱出できます。

### 最新のDocker for Macでは `screen` でDockerのVMに入れない

2020/07/31現在のDocker for Macでは、次のように操作が許可されていない旨のエラーが出てVMに入れなくなってます。 `sudo` で昇格してもダメです。

> Cannot exec '~/Library/Containers/com.docker.docker/Data/vms/0/tty': Permission denied

そこで、VMに入るためのコンテナである[nsenter1](https://github.com/justincormack/nsenter1)を使います、

```bash
docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh
```
脱出方法も `exit` と入力するだけで簡単です。

Docker for Windowsでも使えるか試してみます💡

## 解決策2: コンテナ内のユーザーID(uid, gid)を`docker-compose`実行ユーザーのものに揃えておく

ちょっと脱線しましたが、この解決策は **ホスト上の任意のディレクトリを指定して** コンテナのデータを保存することができます。

`docker-compose` を実行してコンテナを立ち上げるユーザーで次のコマンドを実行してuid, gidを調べます、

```bash
id -u $USER
id -g $USER
```

次に、 `docker-compose.yml`でVolumeを使うサービスの`command`に次の順番でコマンドを追加します。

 * コンテナ内のuid, gidを変更するコマンド
 * ホスト上のディレクトリに紐付けたコンテナ内のディレクトリの所有権を変更するコマンド
 * 通常のコンテナ立ち上げ時に実行されるコマンド

**MySQLのコンテナを使う場合は次のとおりです**

```yaml:docker-compose.yml
    command: bash -c 'usermod -o -u <調べたuid> mysql; groupmod -o -g <調べたgid> mysql; chown -R mysql:root /var/run/mysqld/ /var/log/mysql/ /var/lib/mysql/; /entrypoint.sh mysqld --user=mysql --console'
``` 

`docker-compose.yml`に環境固有の値を入れるとGitで管理する際に支障が出るため、`.env` に追記する形に変更します。

```yaml:docker-compose.yml
version: "3"
services:
  db:
    image: mysql:8.0
    volumes:
      - ./data:/var/lib/mysql
      - ./logs:/var/log/mysql

...

    command: bash -c 'usermod -o -u $LINUX_MYSQL_UID mysql; groupmod -o -g $LINUX_MYSQL_GID mysql; chown -R mysql:root /var/run/mysqld/ /var/log/mysql/ /var/lib/mysql/; /entrypoint.sh mysqld --user=mysql --console'
```

```text:.env(例)
LINUX_MYSQL_UID=501
LINUX_MYSQL_GID=501
```


実行環境のuid, gidを取得して`.env`に追記するシェルスクリプトを同梱することで立ち上げも簡単になります(その代わり実行するのを忘れると設定すべきuid, gidが空っぽになります)


```sh:set-environ-uid.sh
#!/bin/sh
echo "LINUX_MYSQL_UID=$(id -u $USER)" >> .env
echo "LINUX_MYSQL_GID=$(id -g $USER)" >> .env
```

```bash:この環境で初めてプロジェクトのdocker-composeを使う場合は
./set-environ-uid.sh
docker-compose up -d db
```


## 参考

docker composeでMySQLのデータ領域をローカルにマウントする | WEB EGG
https://blog.leko.jp/post/how-to-mount-data-volume-to-local-with-docker-compose/

Docker for MacのDisk Imageの場所が変わった - Qiita
https://qiita.com/amuyikam/items/938781ff5898e654fd7c

Dockerのまとめ - コンテナとボリューム編 - Qiita
https://qiita.com/kompiro/items/7474b2ca6efeeb0df80f

Getting a Shell in the Docker for Mac Moby VM · GitHub
https://gist.github.com/BretFisher/5e1a0c7bcca4c735e716abf62afad389