CentOS7でのDockerコンテナのファイル永続化

戦後レジュームから復活したアラサー土方がはじめてCentOS7でDockerを利用しようとして、ファイル回りで少し嵌ったのでメモしておきます。
*2018.10.08追記: --privilegedオプションなしで実行する方法を追記しました。*

## まずは普通にインストール
yumから。

```bash:
$sudo yum -y update
$sudo yum -y install docker-io
```
インストール出来たらdockerのserviceを起動します。

```bash:
$sudo systemctl start docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
   Active: active (running) since 日 2018-10-07 14:05:46 JST; 13s ago
     Docs: http://docs.docker.com
 Main PID: 24300 (dockerd-current)
    Tasks: 22
   CGroup: /system.slice/docker.service
           ├─24300 /usr/bin/dockerd-current --add-runtime docker-runc=/usr/libexec/docke...
           └─24307 /usr/bin/docker-containerd-current -l unix:///var/run/docker/libconta...

$sudo docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```
中に誰もいませんね。適当なDockerイメージを作って動かしてみます。
buildは時間が掛かるので作業しているフリしてサボりましょう。

```bash:ホストOS側
$mkdir ~/my-docker
$cd my-docker
$vi Dockerfile                      # イメージを作成する
$sudo docker build -t mydocker .    # -t <タグ名>
```
```bash:Dockerfile
FROM ubuntu
RUN \
    apt-get update -y \
    apt-get install -y vim-common \
    apt-get clean
```
作成したイメージを実行してみませう。

```bash:
$sudo docker run -it mydocker /bin/bash    # -i インタラクティブモード
root@a7317ef36825:/#
```
コンテナ内のターミナルに入れました。Dockerでは何も指定しないと、コンテナ内でrootユーザになります。また、コンテナ内で"exit"と打つと実行を終了します。
このままではコンテナ内で作成したファイルは終了時に消滅してしまいます。
さらに、ホストOS上に存在するファイルを参照することも出来ません。

ではどうするかというと、`-v|--volume`オプションによりホストOSのディレクトリをコンテナのディスクイメージにマウントします。

```bash:ホストOS側
$mkdir data
$echo "[$HOSTNAME]" `date` >> data/A             # コンテナ内で利用したいファイル
$sudo docker run --privileged=true -v $PWD/data:/tmp/data -it mydocker /bin/bash
```
volumeオプションの書式は`-v <ホスト側のパス>:<マウント先>`です。ホスト側のパスはフルパスで記載してください。そうしないと変な処([^1])に作成されます。また、`./`などの相対パスはエラーになります。
~~`--privileged=true`はデバイスにアクセスする権限などをコンテナに与えます。通常、CentOS7ではこれを指定しないとコンテナ側からマウント先にアクセスできません。([^2])~~
ボリューム・ラベルを適切に設定することでも解決できます。(後述)

```bash:コンテナ側
#cat /tmp/data/A
[hostos] 2018年 10月 7日 日曜日 15:20:05 JST
#echo "[$HOSTNAME]" `date` >> /tmp/data/logfile
```
```bash:ホストOS側
$cat data/A
[hostos] 2018年 10月 7日 日曜日 15:20:05 JST
[ef3844bdb907] Sun Oct 7 06:20:40 UTC 2018
```
はい。双方向にファイルのやり取りができますね。

## 実行ユーザと所有権
上述の通り、Dockerでは何も指定しないと、コンテナ内でrootユーザとなります。
つまり、コンテナ内で作成したファイルも所有者はrootになっているということです。

```bash:コンテナ側
$touch /tmp/data/B
```
```bash:ホストOS側
$ls -l data/B
-rw-r--r--. 1 root root 0 10月  7 13:40 B
```

`-u|-user`オプションでコンテナ内で実行するユーザID:グループIDを指定出来ます。
下のようなコマンドをシェル化しておくと良いでしょう。

```bash:ホストOS側
export $runuser=`id | sed -E 's/uid=([0-9]+).+gid=([0-9]+).+/\1:\2/'`
$sudo docker run --privileged=true -u $runuser -v $PWD/data:/tmp/data -it mydocker /bin/bash
```
上記のように実行するとコンテナ内のユーザが指定したuid,gidになります。

```bash:コンテナ側
$touch /tmp/data/C
```
```bash:ホストOS側
$ls -l data/C
-rw-r--r--. 1 user  group 0 10月  7 13:40 C
```

## docker-composeを利用する場合
`docker run`に指定していたオプションをymlで指定します。
こちらはホスト側のパスとして相対パスが使えます。

```yml:docker-compose.yml
hoge:
    ...
    user: "${runuser}"
    volumes:
        - ./data:/tmp/data
    privileged: true
    ...
```
なお、上の例で環境変数を使っていますが、予めexportしておくのを忘れないように。

## おまけ：sudoが邪魔
dockerrootというgroupがいるので、自ユーザを追加してやれば`docker run`などのコマンドをスーパーユーザにならなくても実行出来ます。

```bash:
$sudo gpasswd -A hogeuser dockerroot   #　再ログイン or reboot
```

## (2018.10.08追記)  ボリューム・ラベルの設定
`--privileged=true`を指定しないでコンテナからボリュームに書き込もうとした場合、"Permission Denied"のエラーと共に、/var/log/messagesにSELinuxによるアクセス拒否のエラーが出ていました。

```bash:
$docker run -v $PWD/data:/tmp/data -it mydocker /bin/bash
# コンテナ内でread, write
$ls -l /tmp/data
$echo aaaa > /tmp/share/aaaa
# => Permission Denied
```

```text:/var/log/messages
Oct  8 08:24:05 slv-1 journal: I have no name!@b78ac2791ae0:/$ #015#033[KI have no name!@b78ac2791ae0:/$ ls -l /#010 #010/tmp/share/#015
Oct  8 08:24:05 slv-1 journal: ls: cannot open directory '/tmp/share/': Permission denied#015

Oct  8 08:24:20 slv-1 journal: I have no name!@b78ac2791ae0:/$ echo aaaa > /tmp/share/aaaa#015
Oct  8 08:24:20 slv-1 journal: bash: /tmp/share/aaaa: Permission denied#015
Oct  8 08:24:20 slv-1 chronyd[689]: Selected source 210.173.160.87
Oct  8 08:24:23 slv-1 dbus[625]: [system] Activating service name='org.fedoraproject.Setroubleshootd' (using servicehelper)
Oct  8 08:24:23 slv-1 dbus[625]: [system] Successfully activated service 'org.fedoraproject.Setroubleshootd'
Oct  8 08:24:24 slv-1 setroubleshoot: SELinux is preventing bash from write access on the directory share. For complete SELinux messages run: sealert -l 71a31f44-e0b3-47e2-83fc-280190bc430a
Oct  8 08:24:24 slv-1 python: SELinux is preventing bash from write access on the directory share.#012#012*****  Plugin catchall (100. confidence) suggests   **************************#012#012If you believe that bash should be allowed write access on the share directory by default.#012Then you should report this as a bug.#012You can generate a local policy module to allow this access.#012Do#012allow this access for now by executing:#012# ausearch -c 'bash' --raw | audit2allow -M my-bash#012# semodule -i my-bash.pp#012
Oct  8 08:24:24 slv-1 journal: Allocating size to ShellEmbeddedWindow 0x12f0940 without calling gtk_widget_get_preferred_width/height(). How does the code know the size to allocate?
```

以下の記事に詳しく書かれてあるように、原因はディレクトリにSELinuxのラベルが適切に付与されていなかったためのようです。
[dockerで共有ディレクトリを利用したが、「Permission denied」が出てアクセスできない。](https://qiita.com/shuichiro/items/adaaeccebdc55089f1b8)

```bash:ホスト側
$ls -lZ data
drwxrwxr-x. hoge hage unconfined_u:object_r:user_home_t:s0 data
```
```bash:コンテナ側
$ls -Z /tmp/data
drwxrwxr-x. 2 1000 1000 unconfined_u:object_r:user_home_t:s0 6 Oct  7 15:15 /tmp/data
```

`-v`の後ろに`:z`,`:Z`を付与することで正しくラベルが付与されます。

```bash:ホスト側
$docker run -u 1000:1000  -v $PWD/share:/tmp/share:z -v $PWD/share2:/tmp/share2:Z -it mydocker /bin/bash
$ls -Z
drwxrwxr-x. hoge hoge system_u:object_r:container_file_t:s0 share
drwxrwxr-x. hoge hoge system_u:object_r:container_file_t:s0:c462,c792 share2
```
```bash:コンテナ側
$ls -Z /tmp
          system_u:object_r:container_file_t:s0 share
system_u:object_r:container_file_t:s0:c462,c792 share2
```
これでアクセスできます。ただし、`:z`,`:Z`は元のラベルを上書きするので本番環境での運用には注意が必要です。

## 参考

[Docker ドキュメント日本語化プロジェクト]
http://docs.docker.jp/index.html
[dockerで共有ディレクトリを利用したが、「Permission denied」が出てアクセスできない。]
https://qiita.com/shuichiro/items/adaaeccebdc55089f1b8
[SELinux と Docker と OpenShift v3]
https://qiita.com/nak3/items/361b62595601828bd354

[^1]: 具体的には/var/lib/docker/volumes配下にマウント先のパスが作成される。
[^2]: ググってもそう書いてあるページは見当たらないので、自分の環境が特殊なのか、ubuntuやMacだと違うのかもしれませんが