先日、ローカル開発環境用のcompose.yamlのレビューリクエストが届きました。
簡単なメールの送受信をテストするためのmailhogコンテナで受信したメールが保存できない状態になっており、それに対して下記のようにコンテナの実行ユーザを root
に切り替えるような変更を加えるというものです。
services:
mailhog:
image: mailhog/mailhog:latest
+ user: root
volumes:
- type: volume
source: mailhog-storage
target: /mailhog/inbox
environment:
MH_UI_WEB_PATH: mailhog
MH_STORAGE: maildir
MH_MAILDIR_PATH: /mailhog/inbox
volumes:
mailhog-storage:
driver: local
このPRのレビューを通して、ボリュームの取り扱いについての知見を関係者と共有したのでまとめておきます。
お品書き
この記事では
- そもそも何が起きているのか?
- 所有者問題にぶつかったときにどうするか
について説明します
検証環境
Proxmox上に立てたLinuxサーバで検証していきます。
$ uname -r
6.3.5-arch1-1
$ docker -v
Docker version 24.0.2, build cb74dfcd85
$ docker compose version
Docker Compose version 2.18.1
そもそも何が起きているのか?
再現
最小の再現環境は下記で用意できます。
services:
mailhog:
image: mailhog/mailhog:latest
volumes:
- type: volume
source: mailhog-storage
target: /mailhog/inbox
environment:
MH_UI_WEB_PATH: mailhog
MH_STORAGE: maildir
MH_MAILDIR_PATH: /mailhog/inbox
volumes:
mailhog-storage:
driver: local
コンテナは正常に起動しますが、送受信したメールを保存しようとするとコンテナのログにエラーが出力されるはずです。
何が起きているのか?
コンテナの中に入って、実行ユーザや保存ディレクトリの情報を確認してみましょう。
~ $ ps
PID USER TIME COMMAND
1 mailhog 0:00 MailHog
29 mailhog 0:00 sh
35 mailhog 0:00 ps
~ $ whoami
mailhog
~ $ ls -l /mailhog/
total 0
drwxr-xr-x 1 root root 0 May 31 01:01 inbox
Mailhog
は mailhog
ユーザによって実行されていますが、Mailhog
がデータを保存するための /mailhog/inbox
ディレクトリは root
ユーザが所有しており、それ以外のユーザは inbox
に対して書き込みができないことがわかります。
なぜ root
になってしまうかというと、コンテナがマウントするvolumesの置き場所がデフォルトでは /var/lib/docker
にあり、ここの所有者情報を引き継いでしまうからです(詳細は公式ドキュメントを参照)。
$ sudo ls -l /var/lib/docker/volumes
total 24
brw------- 1 root root 0, 24 May 31 09:54 backingFsBlockDev
drwx-----x 1 root root 10 May 31 10:01 mailhog_mailhog-storage
-rw------- 1 root root 32768 May 31 10:01 metadata.db
この情報だけ見ると、コンテナの実行ユーザを root
に切り替えてしまうのも問題解決の方法として選択肢には挙げられるのですが、それは避けたいところです。
そもそもボリュームマウントが必要かどうかを検討する
ボリュームマウントは永続的にデータを保存しておきたい場合に利用する機能です。
そのため、
- データを永続的に保存する必要があるか?
- 保存する必要がある場合、それはコンテナ自身のストレージに持つ必要があるか?
をまず検討し、どちらもYesな場合にのみ、ボリュームマウントを活用するといいです。
(基本的に、スケーラブルなコンテナ運用を検討すると両方Noになっていくはずです)
今回は、ローカル開発環境でのみの利用ということもあって、
- データを永続的に保存する必要があるか?
こちらがNoだったため、最終的にボリュームマウント機能を使わないように変更を加えました。
services:
mailhog:
image: mailhog/mailhog:latest
environment:
MH_UI_WEB_PATH: mailhog
MH_STORAGE: maildir
MH_MAILDIR_PATH: /home/mailhog/inbox
Entrypointを活用することでボリュームマウント時にコンテナでの所有者問題を回避する
では、検討の結果ボリュームマウントを使いたくなった場合にどうするか、という話です。
コンテナの起動は下記の流れになっています。
- コンテナの起動
- Volumeのマウントはここで実行
- EntrypointがDockerfileかcompose.yamlに設定されていればそれを実行
- CMDがDockerfileかcompose.yamlに設定されていれば実行
2のEntrypoint実行のタイミングがボリューム周辺に介入できるポイントになります。
Entrypointに所有者を変更する処理(chown
)を挟むことで、当該ディレクトリの所有者を実行ユーザに渡すことができます。
services:
mailhog:
image: mailhog/mailhog:latest
volumes:
- type: volume
source: mailhog-storage
target: /mailhog/inbox
environment:
MH_UI_WEB_PATH: mailhog
MH_STORAGE: maildir
MH_MAILDIR_PATH: /mailhog/inbox
+ entrypoint: >
+ sh -c "
+ sudo chown -R mailhog:mailhog /mailhog/inbox && \
+ MailHog
+ "
volumes:
mailhog-storage:
driver: local
(注: このcomposeファイルは、mailhogコンテナにsudoが入っていないため、起動しません)
ただし、注意点があります。
注意点1: もともとのEntrypointを確認する
コンテナの起動処理は Entrypoint
と CMD
、どちらに書いてもOKになっています。
今回のようにEntrypointを上書きしたとき、元々の起動処理がEntrypointに書かれていた場合、「起動するための前処理は動いたが肝心の動かしたいプロセスを起動していない」なんてことが起きるかもしれません。
それを避けるため、ベースイメージのEntrypointとCMDの記述を事前に確認しておきましょう。
下記のコマンドで確認することができます。
$ docker inspect {コンテナイメージ名}
注意点2: イメージそのままだとrootでコマンドが実行できないことがある
コンテナの実行ユーザはデフォルトではrootが指定されています。基本的にさらに一段上の privileged
なコンテナでなければ問題は起きにくいのですが、CVE-2022-0492のように、常に安全というわけでもありません。
安全や開発時の利便性のため、rootで実行することを避ける動きもあり、配布されているイメージには時々rootでないユーザで実行されるように設定されているものがあります(今回例として挙げたmailhogコンテナもこれに該当します)。
このようなコンテナでは、ベースイメージにもよりますが、sudo
コマンドが入っていないことがあり、Entrypoint内で一時的にrootとして振る舞うことが難しくなります。
この場合は、/tmp
にマウントする、リスクを把握した上でrootユーザを使うなどして問題を回避することなります。
まとめ
Dockerボリュームをマウントしたとき、ボリュームの所有者と実行ユーザが異なって問題が発生した場合、
- そもそもボリュームマウントが必要かどうかを確認する
- 必要な場合はEntrypointの上書きを使い、所有者を変更して回避する
- 2が難しい場合は、rootユーザで実行することを検討する
の順番で対策を検討していくのがよさそうです。
おまけ
Entrypointでがんばるサンプル
FROM mailhog/mailhog:latest
USER root
RUN apk update && apk add sudo
RUN echo 'mailhog ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers
USER mailhog
WORKDIR /home/mailhog
ENTRYPOINT ["MailHog"]
EXPOSE 1025 8025
services:
mailhog:
build:
context: .
dockerfile: ./Dockerfile
volumes:
- type: volume
source: mailhog-storage
target: /mailhog/inbox
environment:
MH_UI_WEB_PATH: mailhog
MH_STORAGE: maildir
MH_MAILDIR_PATH: /mailhog/inbox
entrypoint: >
sh -c "
sudo chown -R mailhog:mailhog /mailhog/inbox && \
MailHog
"
volumes:
mailhog-storage:
driver: local