経験は何よりも饒舌

10年後に真価を発揮するかもしれないブログ 

PodmanのrootlessコンテナでAMD GPUにアクセスする

Podmanのrootlessコンテナ内からGPUにアクセスする方法。 rocm.docs.amd.com

環境

項目 値
OS Ubuntu 24.04.4 LTS
GPU Radeon 890M (gfx1150, RDNA 3.5)
ROCm 10.0.0
Podman 4.9.3

Podmanのrootlessコンテナとは

Podmanはデフォルトでrootlessモードで動作する。(sudo podman runで実行した場合はrootfulモードになる。) rootlessモードでは、root権限なしでコンテナを実行できるためセキュリティ上の利点がある。

rootlessを実現するためにユーザーネームスペースが使われている。これはLinuxカーネルの機能で、コンテナ内のUID/GIDをホスト上の別のUID/GIDにマッピングする。

マッピングの定義は/etc/subuidと/etc/subgidにある:

$ cat /etc/subuid
hirotaka:100000:65536

$ cat /etc/subgid
hirotaka:100000:65536

これは「hirotakaユーザーは、ホストUID 100000〜165535の範囲をコンテナ内のUIDにマッピングできる」という意味。

ホスト上でのGPUアクセス

まず、コンテナを介さずホスト上で直接GPUにアクセスできることを確認する。

ROCmのGPUコンピュートに必要なデバイスは2つ。

/dev/kfd(Kernel Fusion Driver): GPUのコンピュートインターフェース。

$ ls -la /dev/kfd
crw-rw----  1 root render 511, 0  9月 15 16:28 /dev/kfd

/dev/dri/renderD128: GPUのDirect Rendering Interfaceノード。

$ ls -la /dev/dri/renderD128
crw-rw----+ 1 root render 226, 128  9月 15 16:28 /dev/dri/renderD128

どちらもグループがrender(GID 110)。自分のユーザーがこのグループに所属しているか確認する。

$ id
uid=1000(hirotaka) gid=1000(hirotaka) groups=...,44(video),110(render),...

render(110)グループに所属しているので、ホスト上ではrocminfoも問題なく動く。

問題

ROCm Dockerドキュメントに記載されているコマンドをsudoなしのPodman(rootlessモード)で実行すると、Permission deniedになる。

$ podman run --rm \
  --device /dev/kfd --device /dev/dri \
  --security-opt seccomp=unconfined \
  docker.io/rocm/rocm-terminal:latest \
  rocminfo
ROCk module is loaded
Unable to open /dev/kfd read-write: Permission denied
root is not member of "nogroup" group, the default DRM access group.
  • ROCk module is loaded: カーネルのROCkモジュール(amdgpu)は読み込まれている
  • Unable to open /dev/kfd read-write: Permission denied: /dev/kfdを開こうとしたが権限がない
  • root is not member of "nogroup" group: rocminfoのエラーメッセージでは"root"と表示されているが、実際のコンテナ内ユーザーはrocm-user(UID 1000)。いずれにしても、/dev/kfdの所有グループ(コンテナ内では"nogroup"に見える)にはどのユーザーも所属していないためアクセスできない

コンテナ内のユーザーを確認:

$ podman run --rm --device /dev/kfd --device /dev/dri \
  docker.io/rocm/rocm-terminal:latest \
  id
uid=1000(rocm-user) gid=1000(rocm-user) groups=1000(rocm-user),27(sudo),44(video)

rocm-terminalイメージのデフォルトユーザーはrocm-user(UID 1000)で、rootではない。

原因

2つの原因がある。

1. /dev/kfdにACLがない

$ getfacl /dev/kfd
user::rw-
group::rw-
other::---

$ getfacl /dev/dri/renderD128
user::rw-
user:hirotaka:rw-
group::rw-
mask::rw-
other::---

/dev/dri/renderD128にはuser:hirotaka:rw-のACLが設定されているが、/dev/kfdにはない。

/dev/dri/renderD128のACLは、udevルール(70-uaccess.rules)のTAG+="uaccess"によりログイン時に自動設定される。/dev/kfdにはこのルールが適用されていないため、手動で追加する。

$ sudo setfacl -m u:$(whoami):rw /dev/kfd

確認:

$ getfacl /dev/kfd
user::rw-
user:hirotaka:rw-
group::rw-
mask::rw-
other::---

2. ACLを追加してもコンテナ内からアクセスできない

ACLを追加した状態でrootlessコンテナを実行しても、同じPermission deniedになる。原因はPodmanのrootlessコンテナが使うユーザーネームスペースにある。

ユーザーネームスペースはUID/GIDをリマッピングする。コンテナ内の状態を見てみる:

$ podman run --rm --device /dev/kfd --device /dev/dri \
  docker.io/rocm/rocm-terminal:latest \
  bash -c "ls -la /dev/kfd; id"
crw-rw----+ 1 nobody nogroup 511, 0 Sep 15 07:28 /dev/kfd
uid=1000(rocm-user) gid=1000(rocm-user) groups=1000(rocm-user),27(sudo),44(video)

コンテナ内ではデバイスの所有者がnobody:nogroupに変換されている。

ホストとコンテナの比較

ホスト コンテナ(デフォルト)
/dev/kfd 所有者 root:render nobody:nogroup
/dev/dri/renderD128 所有者 root:render nobody:nogroup
ユーザー uid=1000(hirotaka) uid=1000(rocm-user)
renderグループ 所属している(GID 110) なし
ACLによるアクセス あり(user:hirotaka:rw-) UID不一致でアクセス不可

一見するとコンテナ内でもUID 1000で同じに見えるが、ホストのUID 1000(hirotaka)とコンテナのUID 1000(rocm-user)は別物。

コンテナ内からUIDマッピングを確認する:

$ podman run --rm --device /dev/kfd --device /dev/dri \
  docker.io/rocm/rocm-terminal:latest \
  cat /proc/self/uid_map
         0       1000          1
         1     100000      65536

/proc/self/uid_mapの出力は、左から順に3つの数字が1セットになっている。

1列目 2列目 3列目
コンテナ内の開始UID 対応するホストの開始UID 割り当てる個数

1行目: 0 1000 1

  • コンテナのUID 0から、ホストのUID 1000にマッピング、範囲は1個
  • つまり: コンテナのroot(UID 0) = ホストのhirotaka(UID 1000)

2行目: 1 100000 65536

  • コンテナのUID 1から、ホストのUID 100000にマッピング、範囲は65536個
  • つまり: コンテナのUID 1〜65536 = ホストのUID 100000〜165535

この2つのルールを使って、コンテナ内のrocm-user(UID 1000)がホスト上でどのUIDになるか計算する。

UID 1000は2行目のルール(UID 1〜65536の範囲)に当てはまる。2行目は「コンテナのUID 1 = ホストのUID 100000」から始まる。コンテナのUID 1からUID 1000までは999個ずれているので、ホスト側もスタート地点(100000)から999ずらす。

【対応表】
コンテナ UID 1    → ホスト UID 100000
コンテナ UID 2    → ホスト UID 100001
コンテナ UID 3    → ホスト UID 100002
...
コンテナ UID 1000 → ホスト UID 100999

コンテナ内では「UID 1000」で実行していても、ホストOS(Linuxカーネル)からは「UID 100999の知らないユーザーがGPUを触ろうとしてきた」と認識されるため、アクセスが拒否される。

ACLはホストUID 1000(hirotaka)に対して設定されているので、ホストUID 100999とは一致しない。

解決策

--userns=keep-idで実行

--userns=keep-idはホストのUIDをコンテナ内でもそのまま保持するオプション。

docs.podman.io

--userns=keep-idの場合:

$ podman run --rm --userns=keep-id --device /dev/kfd --device /dev/dri \
  docker.io/rocm/rocm-terminal:latest \
  cat /proc/self/uid_map
         0          1       1000
      1000          0          1
      1001       1001      64536

1行目: 0 1 1000

  • コンテナのUID 0から、ホストのUID 1にマッピング、範囲は1000個
  • つまり: コンテナのUID 0〜999 = ホストのUID 1〜1000

2行目: 1000 0 1 ※

  • コンテナのUID 1000から、ホストのUID 0にマッピング、範囲は1個
  • つまり: コンテナのUID 1000 = ホストのUID 0(root)

3行目: 1001 1001 64536

  • コンテナのUID 1001から、ホストのUID 1001にマッピング、範囲は64536個
  • つまり: コンテナのUID 1001〜65536 = ホストのUID 1001〜65536

※ rocm-terminalイメージのデフォルトユーザーはUID 1000。2行目のルールによりコンテナのUID 1000はホストのUID 0(root)にマッピングされるが、Podmanは--userns=keep-id時にホストのUID(1000)をコンテナプロセスの実行UIDとして設定するため、カーネルのACLチェックはホストUID 1000で行われる。結果としてACLが効く。

$ podman run --rm \
  --userns=keep-id \
  --device /dev/kfd --device /dev/dri \
  --security-opt seccomp=unconfined \
  docker.io/rocm/rocm-terminal:latest \
  rocminfo 2>&1 | grep -E '(Agent|Name:|Marketing|gfx)'
HSA Agents
Agent 1
  Name:                    AMD Ryzen AI 9 HX 370 w/ Radeon 890M
  Marketing Name:          AMD Ryzen AI 9 HX 370 w/ Radeon 890M
  Vendor Name:             CPU
Agent 2
  Name:                    gfx1150
  Marketing Name:          AMD Radeon Graphics
  Vendor Name:             AMD
      Name:                    amdgcn-amd-amdhsa--gfx1150
      Name:                    amdgcn-amd-amdhsa--gfx11-generic

sudoなしでGPUが認識された。

デフォルトと--userns=keep-idの比較

デフォルト:

$ podman run --rm --device /dev/kfd --device /dev/dri \
  docker.io/rocm/rocm-terminal:latest \
  bash -c "ls -la /dev/kfd /dev/dri/renderD128; echo '---'; id"
crw-rw----+ 1 nobody nogroup 226, 128 Sep 15 07:28 /dev/dri/renderD128
crw-rw----+ 1 nobody nogroup 511,   0 Sep 19 09:49 /dev/kfd
---
uid=1000(rocm-user) gid=1000(rocm-user) groups=1000(rocm-user),27(sudo),44(video)

--userns=keep-id:

$ podman run --rm --userns=keep-id --device /dev/kfd --device /dev/dri \
  docker.io/rocm/rocm-terminal:latest \
  bash -c "ls -la /dev/kfd /dev/dri/renderD128; echo '---'; id"
crw-rw----+ 1 nobody nogroup 226, 128 Sep 15 07:28 /dev/dri/renderD128
crw-rw----+ 1 nobody nogroup 511,   0 Sep 19 09:49 /dev/kfd
---
uid=1000(rocm-user) gid=1000(rocm-user) groups=1000(rocm-user),27(sudo),44(video)

コンテナ内から見たls -laとidは完全に同じ。しかしrocminfoの結果は異なる:

デフォルト --userns=keep-id
rocminfo Permission denied 動作する

ls -laやidの出力はコンテナ内のユーザーネームスペースから見た値なので違いがないように見える。しかしカーネルがデバイスアクセスを判定するときはホスト側の実UIDを使う。デフォルトではホストUID 100999、--userns=keep-idではホストUID 1000(hirotaka)になるため、ACLの判定結果が変わる。

ACLの永続化

setfaclによる設定は再起動時にリセットされる。永続化するにはudevルールを追加する。

$ sudo tee /etc/udev/rules.d/70-kfd-acl.rules << 'EOF'
KERNEL=="kfd", SUBSYSTEM=="kfd", TAG+="uaccess"
EOF

TAG+="uaccess"はログインユーザーに対して自動的にACLを設定する(systemd-logindの機能)。/dev/dri/renderD128に既にACLが付いているのもこの仕組みによるもの。

設定の反映:

$ sudo udevadm control --reload-rules
$ sudo udevadm trigger /dev/kfd

まとめ

PodmanのrootlessコンテナでROCm GPUを使うには:

  1. /dev/kfdにACLを追加(初回のみsudoが必要、udevルールで永続化)
  2. podman runに--userns=keep-idを付ける