はじめに
ある日、「研究室サーバーで起動したコンテナ上から、マウントしたNASに書き込みできない」という報告がありました。具体的にはPermission denied
が出ていて、以前は全く報告されていなかった事例です。
本記事では、ステップ・バイ・ステップでエラー解決の糸口を紹介します。デバッグに自信のある方は1章を読んで原因を推測してみてください。
1. エラーの再現
研究室サーバーでは全ユーザーがprimary グループとしてユーザー名と同じグループ(例:azuma
)とsecondary グループとしてdocker
グループに所属しています。
$ id uid=2401(azuma) gid=2401(azuma) groups=2401(azuma),999(docker)
検証用に以下のようなDockerfileを用意しました。セキュリティには目を瞑ってください。
FROM ubuntu:latest ARG UID=2401 ARG USERNAME=azuma RUN apt update && apt upgrade -y && apt clean RUN echo root:root | chpasswd && \ useradd -m -s /bin/bash --uid ${UID} --groups sudo ${USERNAME} && \ echo ${USERNAME}:${USERNAME} | chpasswd RUN mkdir -p /tmp/nas/azuma
コンテナをビルドして起動します。
-u
オプションで実行ユーザーとして自身を指定しています。
$ docker build -t azuma-ubuntu . $ NAS_MOUNT_PATH=/mnt/nas/azuma $ mkdir -p $NAS_MOUNT_PATH $ docker run -it --rm \ --name nas-write-test \ -u $(id -u):$(id -g) \ -v $NAS_MOUNT_PATH:/tmp/nas/azuma \ azuma-ubuntu:latest /bin/bash
マウントしたディレクトリに対してコンテナ上から書き込みを行い、Permission denied
と表示されたのでエラーの確認は完了です。
azuma@479d704b616b:/$ echo "Hello World!" > /tmp/nas/azuma/hello.txt bash: /tmp/nas/azuma/hello.txt: Permission denied
一方でrootユーザーからは対象のディレクトリに書き込むことができます。
azuma@308b7e25d385:/$ su Password: root@308b7e25d385:/# echo "Hello World!" > /tmp/nas/azuma/hello.txt root@308b7e25d385:/# ls -ld /tmp/nas/azuma/ drwxrwxr-x 2 root 999 0 Oct 15 09:00 /tmp/nas/azuma/
最後に、ホストマシン上での対象ディレクトリの権限をチェックします。
$ ls -ld /mnt/nas/azuma drwxrwxr-x 2 root docker 0 10月 7 18:20 /mnt/nas/azuma
この時点で大まかな原因の予想がついたので、次章からは敢えて実行したコマンドと関連知識を掘り下げていきます。
2. コンテナの実行ユーザー指定
Dockerコンテナのデフォルトの実行ユーザーはroot
(uid=0 gid=0
)です。
ほとんどの人が、DockerfileのUSER
命令を使用してイメージビルド中に実行ユーザーを切り替えた経験があると思います。一方、docker run
コマンドの-u
オプションを使用することで、以下に示すようにコンテナを起動(し、コマンドを実行)する際のUSER
命令を上書きできます。
--user=[ user | user:group | uid | uid:gid | user:gid | uid:group ]
どのユーザーでコンテナが起動されるのか理解できたので、改めてエラーの再現に利用したDockerfileとdocker run
コマンドを確認します。
DockerfileのRUN
命令の一部として、以下のコマンドが実行されています。
useradd -m -s /bin/bash --uid 2401 --groups sudo azuma
これはユーザーazuma
をUID2401
で作成し、管理者権限を持つsudo
グループに追加し、デフォルトシェルを/bin/bash
に設定して、ホームディレクトリを作成するという意味です。-g
オプションを指定しない場合はデフォルトで新しいユーザーと同じ名前のグループが作成されてプライマリグループになります。
次に、docker run
コマンドの-u
オプションのみ抜粋します。
$ docker run -u 2401:2401 azuma-ubuntu:latest /bin/bash
これは、コンテナ内のUID2401
、GID2401
のユーザーでコンテナを起動することを意味しています。
ちなみに、-u 2401
のようにユーザーIDを指定した場合は0-2147483647
の整数値でなければならず、-u azuma
のようにユーザー名を指定した場合は予めコンテナ内にユーザーが存在しなければならないそうです。
裏を返せば、Dockerfileでuseradd
コマンドをコメントアウトしてユーザーを作成しない場合でも、-u
オプションでユーザーIDを指定すれば、以下のようにユーザーが作成されるということです。
$ docker run -it -u 2401:2401 azuma-ubuntu:latest /bin/bash groups: cannot find name for group ID 2401 I have no name!@a715efc4b574:/$ id uid=2401 gid=2401 groups=2401
Dockerfileでuseradd
コマンドを実行していましたが、これは必須ではなく、ユーザー名やホームディレクトリ、デフォルトシェルを作成・指定して便利にするためであると分かりました。
3. ファイルのアクセス権
今回のエラーはPermission denied
なので、ファイルのアクセス権について復習します。
まずは、ホストマシンから1章でNAS_MOUNT_PATH
として指定していたパスの詳細を表示します。
$ ls -la /mnt/nas/azuma 合計 4 drwxrwxr-x 2 root docker 0 10月 16 12:16 . drwxrwxr-x 2 root docker 0 10月 15 10:33 .. -rwxrwxr-x 1 root docker 13 10月 15 18:39 hello.txt
表記は左から、モード(ユーザー、グループ、その他のユーザー)、リンクの数、所有者、グループ、サイズ (バイト単位)、最終変更時刻、および名前です。
例えば、カレントディレクトリ.
についてはroot
ユーザーとdocker
グループが読み取り(r
)/書き込み(w
)/実行(x
)可能で、その他のユーザーは読み取り(r
)と実行(x
)のみ可能です。
1章で確認した通り、全研究室メンバーのユーザーがdocker
グループに所属しているため、ホストマシンからは問題なくディレクトリ内のファイルに書き込めます。
コンテナ内からもマウントした対象ディレクトリの詳細を取得してみます。
azuma@2e65c0193e67:/$ ls -la /tmp/nas/azuma/ total 8 drwxrwxr-x 2 root 999 0 Oct 16 03:16 . drwxr-xr-x 3 root root 4096 Oct 16 03:43 .. -rwxrwxr-x 1 root 999 13 Oct 15 09:39 hello.txt
GID999
はホストマシン上でのdocker
グループに該当していました。ホストマシンと同様、ユーザーがdocker
グループに所属していれば書き込み(w
)は可能なはずです。
4. NASのマウント
最後に、NASのマウント方法を確認します。
実例として研究室サーバーでは、/etc/fstab
に以下のように追記することで起動時に自動でNASをマウントしています。
//192.168.1.xxx/data /mnt/nas cifs username=YOUR_USERNAME,password=YOUR_PASSWORD,gid=999,dir_mode=0775,file_mode=0775 0 0
オプションの内容は以下の通りです。詳細はmount.cifsを参照してください。
- IPアドレス
192.168.1.xxx
にある共有ディレクトリ/data
に接続し、ローカルディレクトリ/mnt/nas
にマウント - 認証情報として
username
とpassword
を指定 - マウントされたファイルシステム上の全てのファイル・ディレクトリを所有するグループとして
gid=999
を指定 - ファイル・ディレクトリのデフォルトモードを
0775
で上書き
今回、ホストマシン上のdocker
グループのGIDが999
である前提で、全てのマシンの/etc/fstab
に全く同じ内容を追記していました。しかし、3台のマシンを調査したところ、docker
グループのGIDはそれぞれ997
/998
/999
でした。999
の場合でもコンテナ内からの書き込み時にエラーが起きるのは事実ですが、997
や998
の場合はホストマシンからの書き込みすらできません。
解決策としては
gid=arg sets the gid that will own all files or directories on the mounted filesystem when the server does not provide ownership information. It may be specified as either a groupname or a numeric gid. When not specified, the default is gid 0. The mount.cifs helper must be at version 1.10 or higher to support specifying the gid in non-numeric form. See the section on FILE AND DIRECTORY OWNERSHIP AND PERMISSIONS below for more information. https://linux.die.net/man/8/mount.cifs
から分かる通り、グループIDではなくgid=docker
のようにグループ名を指定するのがベターだと考えました。
5. エラーの解決
調査を通して分かったことをまとめます。
【2章】コンテナ起動時に指定するオプションによって、コンテナ内に既に存在しているユーザーを実行ユーザーとして指定できる
【3章】コンテナにマウントしたディレクトリが、コンテナ内でもホストマシンと同じ所有者・所有グループとして表示される
【4章】NASマウント時に所有グループとしてグループIDだけでなくグループ名も指定できる
最もシンプルな解決方法として「全てのユーザーをprimaryグループとしてdocker
グループに所属させ、docker
グループがNASをマウントしたディレクトリを所有する」という方法があります。この方法であれば今まで通りの方法でユーザーがNASを利用できるようになりますが、Dockerに対する権限とNASに対する権限の切り分けが不可能です。また、あるユーザーが作成したディレクトリはdocker
グループに所属する一般ユーザーからデフォルトで書き込み可能であることからも好ましくありません。実は、Ansibleによる構成管理を導入する以前はこの状態でした。
今回の解決策を下図に示します。
結論から言うと今回は、全ユーザーをサブグループとしてdocker
グループとnas_access
グループ(NASにアクセス可能なグループ)の2つに所属させ、docker run
コマンドで--group-add
オプション*1を指定してもらう解決策を取ることにしました。
以下は、nas_access
グループのGIDが996
である場合のコマンド実行例です。
$ docker run -it --rm \ --name nas-write-test \ -u $(id -u):$(id -g) \ --group-add $(getent group nas_access | awk -F: '{print $3}') \ -v $NAS_MOUNT_PATH:/tmp/nas/azuma \ azuma-ubuntu:latest /bin/bash groups: cannot find name for group ID 996 azuma@1556ffc1cf80:/$ id uid=2401(azuma) gid=2401(azuma) groups=2401(azuma),996 azuma@1556ffc1cf80:/$ echo "Hello World!" > /tmp/nas/azuma/hello.txt azuma@1556ffc1cf80:/$ cat !$ cat /tmp/nas/azuma/hello.txt Hello World!
コンテナ内にGID996
のグループは実際には存在しないため、groups: cannot find name for group ID 996
というエラーが一瞬表示されます。エラーが気になる場合は、以下のようにDockerfile内でnas_access
グループを作成することで、--group-add
オプションを使わずNASへの書き込みが可能になります(ただし、イメージビルド時にグループIDを引数として渡すため、同じイメージをグループIDが異なるホストマシン間で使い回すことはできません)。
FROM ubuntu:latest ARG UID=2401 ARG USERNAME=azuma ARG NAS_ACCESS_GID=996 # 追加 RUN apt update && apt upgrade -y && apt clean RUN echo root:root | chpasswd && \ # 追加 groupadd -g ${NAS_ACCESS_GID} nas_access && \ useradd -m -s /bin/bash --uid ${UID} --groups sudo,nas_access ${USERNAME} && \ echo ${USERNAME}:${USERNAME} | chpasswd RUN mkdir -p /tmp/nas/azuma
いずれにせよ、コンテナ上からマウントしたNASに書き込めないエラーは解消しました。nas_access
グループを作成やsecondaryグループの追加については、AnsibleのおかげでPlaybookを数行書き換えるだけで全マシンに適用されました。
4章で登場した/etc/fstab
に関する修正点は、NAS利用者が居ないタイミングを見計らって1台ずつマウントし直しました。
$ vim /etc/fstab # 次回起動時のために変更 $ umount -l /mnt/nas $ mount -t cifs //192.168.1.xxx/data /mnt/nas -o username=YOUR_USERNAME,password=YOUR_PASSWORD,gid=nas_access,dir_mode=0775,file_mode=0775 $ ls -ld /mnt/nas drwxrwxr-x 2 root nas_access 0 10月 15 10:33 /mnt/nas $ echo "Hello World!" > /mnt/nas/azuma/hello.txt # 成功 $ NAS_MOUNT_PATH=/mnt/nas/azuma $ docker run -it --rm --name nas-write-test -u $(id -u):$(id -g) --group-add $(getent group nas_access | awk -F: '{print $3}') -v $NAS_MOUNT_PATH:/tmp/nas/azuma azuma-ubuntu:latest /bin/bash groups: cannot find name for group ID 996 azuma@286205c86ac7:/$ id uid=2401(azuma) gid=2401(azuma) groups=2401(azuma),996 azuma@286205c86ac7:/$ echo "Hello Azuma!" > /tmp/nas/azuma/hello.txt azuma@286205c86ac7:/$ cat !$ # 成功 cat /tmp/nas/azuma/hello.txt Hello Azuma!
まとめ
「コンテナにマウントしたNASにコンテナ内から書き込めない」という問題に対して、丁寧に調査しながら解決策を示すことができました。また、調査する過程でLinuxやDockerの曖昧な知識を再確認できたことは大きな収穫でした。
余談ですが、docker
グループの権限はroot
ユーザーに匹敵するので、ホストのファイルシステム全体をコンテナにマウントすることで、任意のファイルを読み書きできます。セキュリティの観点からRootlessモードやPodmanを導入したい気持ちはありますが、あくまでも研究室サーバーなので衝動を抑えつつ、自分の環境でこれらの技術をキャッチアップする必要があると考えました。
*1:docker composeの場合も同様にgroup_add属性を指定できますhttps://docs.docker.com/reference/compose-file/services/#group_add