Rootless Podmanを使っていると、ホスト側でファイルを見たときに、次のような所有者になっていることがあります。
100999 100999
/etc/passwd を見ても、そんなユーザーはいない。
その結果、ホスト側の通常ユーザーでファイルを削除しようとして、
rm: Permission denied
のようなエラーになることがあります。
これは、Rootless Podmanで使われる ユーザー名前空間 と UID/GIDマッピング が関係しています。
ユーザー名前空間とは
一言でいうと、ユーザー名前空間とは、
UID/GIDの見え方を変えるLinuxの仕組み
です。
Linuxでは、ユーザーやグループは名前ではなく、内部的にはUID/GIDという数値で管理されています。
例えば、通常は次のように扱われます。
UID 0 = root
UID 1000 = 一般ユーザー
コンテナ内でもUID/GIDは使われます。
ただし、ユーザー名前空間を使うと、
コンテナ内で見えているUID/GID
と、
ホスト側で実際に使われているUID/GID
を別の値にマッピングできます。
コンテナ内のrootは、ホストのrootとは限らない
ユーザー名前空間を使うと、コンテナ内ではrootに見えていても、ホスト側では本物のrootではない、という状態を作れます。
つまり、
コンテナ内のroot ≠ ホストのroot
にできます。
これが重要です。
コンテナ内でrootとして動いているプロセスがあっても、それがホストOS全体を自由に操作できるrootとは限りません。
Rootless Podmanでは、この仕組みによって、一般ユーザーでも比較的安全にコンテナを実行できるようになっています。
Rootless PodmanでのUID/GIDマッピング
Rootless Podmanでは、基本的にユーザー名前空間が使われます。
標準的なrootless環境では、ざっくりいうと次のような対応になります。
コンテナ内 UID 0
→ ホスト側ではPodmanを実行した一般ユーザーのUID
コンテナ内 UID 1以降
→ ホスト側では /etc/subuid に割り当てられた範囲
例えば、ホスト側のユーザー user02 のUIDが 1000 だとします。
さらに、/etc/subuid に次のような設定があるとします。
user02:100000:65536
この場合、Podmanはコンテナ内のUIDを、ホスト側のUIDに次のように対応させることがあります。
コンテナ内 UID 0 → ホスト UID 1000
コンテナ内 UID 1 → ホスト UID 100000
コンテナ内 UID 2 → ホスト UID 100001
...
コンテナ内 UID 1000 → ホスト UID 100999
つまり、コンテナ内では UID 1000 のユーザーが作ったファイルでも、ホスト側から見ると UID 100999 が所有しているように見えることがあります。
なぜホスト側で「謎のUID」に見えるのか
ホスト側で次のように確認したとします。
ls -ln
すると、次のように表示されることがあります。
-rw-r--r-- 1 100999 100999 1234 Jun 14 file.txt
これは、ホストの /etc/passwd に 100999 というUIDのユーザーがいる、という意味ではありません。
実際には、
コンテナ内のUID 1000
が、
ホスト側のUID 100999
にマッピングされて見えている状態です。
そのため、ホスト側から見ると、
知らないUIDが所有しているファイル
のように見えます。
ホスト側で削除できないことがある
このようなファイルは、ホスト側の通常ユーザーから見ると、自分の所有物ではないように見えることがあります。
そのため、次のように削除しようとしても、
rm -rf ./data
次のようなエラーになることがあります。
rm: cannot remove './data/file.txt': Permission denied
これは、Rootless Podmanでvolumeやbind mountを使っているときによく遭遇する現象です。
そこで podman unshare
このようなときに使えるのが podman unshare です。
例えば、次のように実行します。
podman unshare rm -rf ./data
podman unshare は、Podman rootless用のユーザー名前空間に入ってコマンドを実行するためのコマンドです。
- podman unshareで実行したコマンドは、Podmanのuser namespace内ではroot、つまりUID 0として実行される。それはホストのrootではない
- 特定のコンテナ内に入って実行するわけではない。
通常のホスト側から見ると、
所有者: 100999
のように見えていたファイルでも、Podmanのユーザー名前空間の中から見ると、
これはコンテナ内のUID 1000に対応するファイル
のように、Podmanのマッピングに従った見え方になります。
その結果、ホスト側からは削除できなかったファイルでも、podman unshare の中では削除できる場合があります。
UID/GIDマッピングを確認する
現在のRootless Podmanで、どのようにUID/GIDがマッピングされているかは、次のように確認できます。
podman unshare cat /proc/self/uid_map
podman unshare cat /proc/self/gid_map
container_id host_id size
- container_id: 名前空間の中で見える UID/GID の開始番号
- host_id: ホスト側の実 UID/GID の開始番号
- size: 連続して対応づけられる個数
出力例は環境によって異なりますが、例えば次のような形になります。
0 1000 1
1 100000 65536
これは、ざっくりいうと次の意味です。
名前空間内 UID 0 → ホスト UID 1000
名前空間内 UID 1以降 → ホスト UID 100000以降
つまり、Rootless Podmanの中ではrootに見えていても、ホスト側では通常ユーザーやsubuid範囲にマッピングされている、ということです。
subuid とは
subuid は subordinate UID の略です。
直訳すると「従属UID」や「補助UID」のような意味になります。
Rootless Podmanの文脈では、次のように考えると分かりやすいです。
subuid = 一般ユーザーに割り当てられた、コンテナ用の追加UID範囲
設定は /etc/subuid に書かれています。
例えば、次のような行があるとします。
user02:100000:65536
これは、
ユーザー user02 は、
ホスト上の UID 100000 から 65536個分を、
ユーザー名前空間内で使ってよい
という意味です。
範囲でいうと、次のようになります。
100000 ~ 165535
同じように、GID版が /etc/subgid です。
/etc/subuid = 追加で使えるUID範囲
/etc/subgid = 追加で使えるGID範囲
確認するには、次のようにします。
cat /etc/subuid
cat /etc/subgid
Rootless Podmanは、この /etc/subuid と /etc/subgid の範囲を使って、コンテナ内のUID/GIDをホスト側のUID/GIDに対応させます。
subuid があるから謎のUIDに見える
例えば、/etc/subuid が次の内容だったとします。
user02:100000:65536
このとき、コンテナ内のUID 1000がホスト側ではUID 100999として見えることがあります。
コンテナ内 UID 1000
→ ホスト側 UID 100999
そのため、ホスト側で ls -ln すると、
100999 100999 file.txt
のように表示されることがあります。
これは、ホストに 100999 というユーザーが存在するという意味ではありません。
コンテナ内のUIDが、ホスト側のsubuid範囲に変換されて見えているだけです。
volumeの :U オプション
podman unshare は、すでに作られてしまったファイルやディレクトリを後から操作したいときに便利です。
一方で、コンテナにディレクトリをマウントするときに、最初から所有者を調整する方法もあります。
それが、volumeオプションの :U です。
例えば、次のように使います。
podman run --rm \
-v ./data:/data:U \
イメージ名
この例は、ホスト側の ./data をコンテナ内の /data にマウントしています。
厳密には、これはホスト上のディレクトリをコンテナに見せる bind mount です。
ただし、-v で指定するマウント全般を、ざっくり「volumeマウント」と呼ぶこともあります。
:U は何をするのか
:U を付けると、Podmanはマウント元のファイルやディレクトリに対して、所有者とグループを調整します。
簡単にいうと、
コンテナ内のユーザーが書き込めるように、
ホスト側の所有者を変更してからマウントする
という動きです。
例えば、コンテナ内のアプリケーションが /data に書き込みたいとします。
しかし、ホスト側の ./data の所有者が、コンテナ内のユーザーに対応していない場合、コンテナ内で次のようなエラーになることがあります。
Permission denied
このような場合に、
podman run --rm \
-v ./data:/data:U \
イメージ名
とすると、Podmanが所有者を調整してくれるため、コンテナ内から書き込めるようになる場合があります。
SELinux環境では :Z と組み合わせることもある
RHEL系、Fedora、AlmaLinux、Rocky Linuxなど、SELinuxが有効な環境では、UID/GIDだけでなくSELinuxラベルが原因で書き込めないこともあります。
その場合は、:Z と :U を組み合わせることがあります。
podman run --rm \
-v ./data:/data:Z,U \
イメージ名
:Z はSELinuxラベルをコンテナ用に調整します。
:U はUID/GIDの所有者を調整します。
つまり、
:Z = SELinuxラベルの調整
:U = UID/GID所有者の調整
です。
SELinuxが有効な環境では、所有者が合っていてもラベルが合っていないと書き込めないことがあるため、両方が関係する場合があります。
:U の注意点
:U は便利ですが、かなり強い操作です。
理由は、:U がホスト側のファイルやディレクトリに対して、実際に chown を行うからです。
しかも、対象ディレクトリ配下に対して再帰的に所有者変更が行われます。
そのため、次のようなディレクトリには安易に使わない方がよいです。
ホームディレクトリ全体
重要な設定ファイルがあるディレクトリ
他のアプリケーションも使っているディレクトリ
大量のファイルがあるディレクトリ
例えば、次のような指定は危険です。
podman run --rm \
-v $HOME:/home/user:U \
イメージ名
ホームディレクトリ配下の所有者が変わってしまうと、ホスト側の通常ユーザーから扱いにくくなる可能性があります。
:U を使うなら、コンテナ専用に作ったディレクトリに限定するのが安全です。
mkdir ./data
podman run --rm \
-v ./data:/data:U \
イメージ名
podman unshare chown で手動調整する
:U を使わず、手動で所有者を調整したい場合は、podman unshare chown を使う方法もあります。
例えば、Podmanのユーザー名前空間内で、./data の所有者を変更したい場合は、次のようにします。
podman unshare chown -R 0:0 ./data
この場合の 0:0 は、ホストのrootという意味ではなく、Podmanのユーザー名前空間内での UID 0 / GID 0 です。
つまり、ホスト側から見ると別のUID/GIDに見えることがあります。
ここでも重要なのは、
podman unshare の中で見えているUID/GID
と、
ホスト側で見えているUID/GID
は同じとは限らない、ということです。
--userns=keep-id という選択肢
Rootless Podmanでは、--userns=keep-id を使う方法もあります。
(Composeならuserns_mode: keep-id)
podman run --rm \
--userns=keep-id \
-v ./data:/data \
イメージ名
--userns=keep-id は、ホスト側の自分のUID/GIDを、コンテナ内でも同じUID/GIDとして使うためのオプションです。
例えば、ホスト側の自分のUIDが 1000 の場合、コンテナ内でもUID 1000 として扱いやすくなります。
これにより、bind mountしたディレクトリの権限問題を避けやすくなる場合があります。
ただし、すべてのケースで --userns=keep-id が最適とは限りません。
コンテナイメージ側が特定のUIDで動く前提になっている場合や、rootとして初期化処理を行う前提になっている場合は、別の調整が必要になることもあります。
podman unshare、:U、--userns=keep-id の使い分け
ざっくり分けると、次のようになります。
| 方法 | 用途 |
|---|---|
podman unshare rm -rf |
すでにできてしまったファイルやディレクトリを削除する |
podman unshare chown |
Podmanのユーザー名前空間内の視点で所有者を手動調整する |
-v ./data:/data:U |
マウント時に所有者を自動調整する |
--userns=keep-id |
ホスト側の自分のUID/GIDをコンテナ内でも維持したい場合に使う |
:Z |
SELinuxラベルをコンテナ用に調整する |
例えば、すでに消せないディレクトリができている場合は、次のようにします。
podman unshare rm -rf ./data
これから新しくマウントする専用ディレクトリを作る場合は、次のように :U を使う方法があります。
mkdir ./data
podman run --rm \
-v ./data:/data:U \
イメージ名
ホスト側の自分のUID/GIDのまま扱いたい場合は、--userns=keep-id を検討できます。
podman run --rm \
--userns=keep-id \
-v ./data:/data \
イメージ名
SELinuxが有効な環境でラベルも調整したい場合は、:Z を組み合わせます。
podman run --rm \
-v ./data:/data:Z,U \
イメージ名
Dockerとの違い
通常のrootful Dockerでは、Docker daemonがroot権限で動いています。
そのため、ユーザー名前空間のリマップを明示的に有効化していない限り、Rootless PodmanのようなUID/GIDのずれは起きにくいです。
一方、Rootless Podmanでは、一般ユーザーで安全にコンテナを動かすために、ユーザー名前空間が基本的に使われます。
| 項目 | Rootless Podman | 通常のrootful Docker |
|---|---|---|
| 実行主体 | 一般ユーザー | root権限のDocker daemon |
| ユーザー名前空間 | 基本的に使う | 通常は明示設定しない限り使わない |
| UID/GIDのずれ | 起きやすい | 通常は起きにくい |
| 謎のUIDのファイル | 発生することがある | userns-remap等を使うと発生することがある |
| 消せないファイルへの対応 |
podman unshare が使える |
root所有なら sudo rm や sudo chown が必要になることがある |
| volume所有者の自動調整 |
:U が使える |
同じ意味の :U は通常使わない |
ただし、Dockerでも userns-remap や rootless mode を使う場合は、同じようにUID/GIDマッピングが関係します。
そのため、
DockerだからUID/GIDマッピングは関係ない
というわけではありません。
あくまで、
通常のrootful Dockerでは意識する機会が少ない
Rootless Podmanでは意識する機会が多い
という違いです。
まとめ
Rootless Podmanで、ホスト側に次のような所有者のファイルが見えることがあります。
100999 100999
これは、ホストに謎のユーザーが作られたという意味ではありません。
コンテナ内のUID/GIDが、ホスト側のsubuid/subgid範囲にマッピングされて見えているだけです。
重要なポイントは次のとおりです。
ユーザー名前空間
= UID/GIDの見え方を変える仕組み
subuid / subgid
= rootlessコンテナ用に一般ユーザーへ割り当てられた追加UID/GID範囲
podman unshare
= Podmanのユーザー名前空間に入ってコマンドを実行する方法
:U
= マウント元ディレクトリの所有者を、コンテナ側で扱いやすいように変更するオプション
:Z
= SELinuxラベルをコンテナ用に調整するオプション
--userns=keep-id
= ホスト側の自分のUID/GIDをコンテナ内でも維持したい場合に使うオプション
消せないファイルがすでにできている場合は、次のようにします。
podman unshare rm -rf ./data
これから新しく専用ディレクトリをマウントする場合は、次のように :U を使う方法があります。
podman run --rm \
-v ./data:/data:U \
イメージ名
ただし、:U はホスト側のファイルに対して実際に再帰的な chown を行います。
そのため、ホームディレクトリ全体や重要な設定ディレクトリには安易に使わず、コンテナ専用のディレクトリに限定して使うのが安全です。
参考
-
subuid(5) - Linux manual page
https://man7.org/linux/man-pages/man5/subuid.5.html -
podman-unshare(1) - Podman documentation
https://docs.podman.io/en/latest/markdown/podman-unshare.1.html -
podman-run(1) - Podman documentation
https://docs.podman.io/en/latest/markdown/podman-run.1.html -
Podman rootless tutorial
https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md -
Docker userns-remap
https://docs.docker.com/engine/security/userns-remap/