blog(unstable)

Dockerボリュームをマウントしたときの所有者問題を回避したい

June 1, 2023

category : 技術

tags :

description : ENTRYPOINTの中でボリュームの所有権をがちゃがちゃやる


この記事は以前Qiitaにて公開したものになります。
元記事はこちら : Dockerボリュームをマウントしたときの所有者問題を回避したい


先日、ローカル開発環境用の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

Mailhogmailhog ユーザによって実行されています。
しかし、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を活用することでボリュームマウント時にコンテナでの所有者問題を回避する

では、検討の結果ボリュームマウントを使いたくなった場合にどうするか、という話です。

コンテナの起動は下記の流れになっています。

  1. コンテナの起動
    • Volumeのマウントはここで実行
  2. EntrypointがDockerfileかcompose.yamlに設定されていればそれを実行
  3. CMDがDockerfileかcompose.yamlに設定されていれば実行

上記フローの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を確認する

コンテナの起動処理は EntrypointCMD 、どちらに書いてもOKになっています。 今回のようにEntrypointを上書きしたとき、元々の起動処理がEntrypointに書かれていた場合、「起動するための前処理は動いたが肝心の動かしたいプロセスを起動していない」なんてことが起きるかもしれません。 それを避けるため、ベースイメージのEntrypointとCMDの記述を事前に確認しておきましょう。 下記のコマンドで確認できます。

$ docker inspect {コンテナイメージ名}

注意点2: イメージそのままだとrootでコマンドが実行できないことがある

コンテナの実行ユーザはデフォルトではrootが指定されています。基本的にさらに一段上の privileged なコンテナでなければ問題は起きにくいのですが、CVE-2022-0492のように、常に安全というわけでもありません。

安全や開発時の利便性のため、rootで実行することを避ける動きもあり、配布されているイメージには時々rootでないユーザで実行されるように設定されているものがあります(今回例として挙げたmailhogコンテナもこれに該当します)。 このようなコンテナでは、ベースイメージにもよりますが、sudo コマンドが入っていないことがあり、Entrypoint内で一時的にrootとして振る舞うことが難しくなります。 この場合は、/tmp にマウントする、リスクを把握した上でrootユーザを使うなどして問題を回避することなります。

まとめ

Dockerボリュームをマウントしたとき、ボリュームの所有者と実行ユーザが異なって問題が発生した場合、

  1. そもそもボリュームマウントが必要かどうかを確認する
  2. 必要な場合はEntrypointの上書きを使い、所有者を変更して回避する
  3. 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