Podman rootlessでハマった XDG_RUNTIME_DIR と DBUS_SESSION_BUS_ADDRESS のズレ:/run/user/1000 を参照してpermission deniedになった話
はじめに
AlmaLinux / RHEL系の環境で、一般ユーザーから Podman rootless を使おうとしたところ、コンテナ起動時に以下のようなエラーが出ました。
XDG_RUNTIME_DIR directory "/run/user/1000" is not owned by the current user
また、別のタイミングでは以下のエラーも発生しました。
ERRO[0000] failed to move the rootless netns slirp4netns process to the systemd user.slice: dial unix /run/user/1000/bus: connect: permission denied
rootではコンテナを起動できるのに、一般ユーザー user02 ではPodmanが失敗する状態です。
最初はPodman側の問題かと思いましたが、調べていくと原因は、Linuxのユーザーセッションまわりの環境変数である以下2つのズレでした。
XDG_RUNTIME_DIR
DBUS_SESSION_BUS_ADDRESS
特に今回のポイントは次です。
user02 の UID は 1001
しかし一部の環境変数が user01 の UID 1000 用の /run/user/1000 を参照していた
この記事では、/run/user/$UID とは何か、XDG_RUNTIME_DIR と DBUS_SESSION_BUS_ADDRESS が何を意味するのか、そしてPodman rootlessでなぜエラーになったのかを整理します。
環境
今回の環境は以下です。
OS: AlmaLinux / RHEL系
コンテナ: Podman rootless
SSHログインユーザー: user01
Podmanを動かしたいユーザー: user02
ユーザーのUIDは以下の通りです。
id -u user01
# 1000
id -u user02
# 1001
つまり、対応関係はこうです。
/run/user/1000 = user01 用
/run/user/1001 = user02 用
発生していたエラー
user02 ユーザーでPodmanを実行すると、次のようなエラーが出ました。
XDG_RUNTIME_DIR directory "/run/user/1000" is not owned by the current user
また、XDG_RUNTIME_DIR を修正した後にも、次のエラーが残りました。
ERRO[0000] failed to move the rootless netns slirp4netns process to the systemd user.slice: dial unix /run/user/1000/bus: connect: permission denied
/run/user/$UID とは何か
/run/user/$UID は、Linuxがユーザーごとに作る一時的な実行用ディレクトリです。
たとえば、以下のようなディレクトリが作られます。
/run/user/1000
/run/user/1001
この 1000 や 1001 は、ユーザーID、つまりUIDです。
今回の場合は以下の関係です。
user01 -> UID 1000 -> /run/user/1000
user02 -> UID 1001 -> /run/user/1001
/run はOS起動中だけ存在する一時領域です。
そのため、サーバを再起動すると /run/user/1000 や /run/user/1001 は一度消えます。
その後、ユーザーがログインしたり、systemd user managerが起動したりすると、再生成されます。
なので、再起動後に /run/user/1000 が user01 所有で作られるのは正常です。
問題は、user02 で作業しているのに、環境変数が /run/user/1000 を参照していたことです。
XDG_RUNTIME_DIR とは何か
XDG_RUNTIME_DIR は、そのユーザーの一時実行用ディレクトリを表す環境変数です。
user02 のUIDが1001なら、本来はこうなっている必要があります。
echo "$XDG_RUNTIME_DIR"
# /run/user/1001
Podman rootlessは、このディレクトリを使って一時ファイルやソケットなどを扱います。
そのため、以下の状態はNGです。
whoami
# user02
id -u
# 1001
echo "$XDG_RUNTIME_DIR"
# /run/user/1000
これは、user02 で作業しているのに、user01 用の /run/user/1000 を使おうとしている状態です。
この場合、Podmanは以下のように怒ります。
XDG_RUNTIME_DIR directory "/run/user/1000" is not owned by the current user
DBUS_SESSION_BUS_ADDRESS とは何か
今回もう一つ重要だったのが、DBUS_SESSION_BUS_ADDRESS です。
これは、ユーザーセッションのD-Busに接続するためのアドレスを表す環境変数です。
今回のエラーでは、ここが重要です。
dial unix /run/user/1000/bus: connect: permission denied
Podmanが /run/user/1000/bus に接続しようとして失敗しています。
実際に確認すると、以下のようになっていました。
echo "$DBUS_SESSION_BUS_ADDRESS"
# unix:path=/run/user/1000/bus
しかし、user02 のUIDは1001です。
本来は以下であるべきです。
echo "$DBUS_SESSION_BUS_ADDRESS"
# unix:path=/run/user/1001/bus
つまり、XDG_RUNTIME_DIR だけ直しても不十分でした。
正しい状態は以下です。
XDG_RUNTIME_DIR=/run/user/1001
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1001/bus
なぜ /run/user/1000 を参照していたのか
今回のログインの流れは以下でした。
サーバ起動
↓
user01 でSSHログイン
↓
その後、su user02 でユーザー切り替え
ここが原因でした。
user01 でSSHログインすると、user01 用のユーザーセッションが作られます。
user01
UID 1000
/run/user/1000
このとき、環境変数も以下のようになります。
XDG_RUNTIME_DIR=/run/user/1000
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
その後、以下のように user02 に切り替えていました。
su user02
この場合、ユーザー自体は user02 に変わります。
しかし、元の user01 の環境変数を一部引き継ぐことがあります。
その結果、以下のような中途半端な状態になっていました。
whoami = user02
UID = 1001
XDG_RUNTIME_DIR = /run/user/1001
DBUS_SESSION_BUS_ADDRESS = unix:path=/run/user/1000/bus
ユーザーは user02 なのに、D-Busの接続先だけ user01 のまま、という状態です。
su user02 と su - user02 の違い
su user02
su user02
これは「ユーザーだけ切り替える」に近い動きです。
元ユーザーの環境変数を引き継ぎやすいため、今回のように user01 のセッション情報が残ることがあります。
su - user02
su - user02
これは「user02 としてログインし直す」に近い動きです。
~/.bash_profile なども読み込まれ、環境が比較的きれいになります。
ただし、SSHで直接 user02 にログインするのと完全に同じとは限りません。
Podman rootlessを安定して使うなら、理想は以下です。
ssh user02@サーバIP
つまり、Podmanを使うユーザーで直接ログインするのが一番きれいです。
状態確認に使ったコマンド
まず、現在のユーザーとUIDを確認します。
whoami
id
id -u
次に、問題の環境変数を確認します。
echo "$XDG_RUNTIME_DIR"
echo "$DBUS_SESSION_BUS_ADDRESS"
/run/user 配下の所有者も確認します。
ls -ld /run/user/1000 /run/user/1001
今回の例では、以下のようになっていました。
drwx------ 5 user01 user01 140 May 6 21:56 /run/user/1000
drwx------ 9 user02 user02 220 May 6 21:58 /run/user/1001
これは正常です。
/run/user/1000 は user01 用、/run/user/1001 は user02 用です。
次に、D-Busのソケットを確認します。
ls -l /run/user/1000/bus /run/user/1001/bus 2>/dev/null
今回、user02 用の /run/user/1001/bus は存在していました。
srw-rw-rw- 1 user02 user02 0 May 6 21:55 /run/user/1001/bus
しかし、環境変数は /run/user/1000/bus を向いていました。
一時的な解決方法
一時的に直すだけなら、user02 で以下を実行します。
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
export DBUS_SESSION_BUS_ADDRESS="unix:path=${XDG_RUNTIME_DIR}/bus"
確認します。
echo "$XDG_RUNTIME_DIR"
echo "$DBUS_SESSION_BUS_ADDRESS"
期待値は以下です。
/run/user/1001
unix:path=/run/user/1001/bus
その後、Podmanを実行します。
podman info
podman-compose up --build
または環境によっては以下です。
podman compose up --build
.bashrc で強制補正する
今回は、user02 の .bashrc で XDG_RUNTIME_DIR と DBUS_SESSION_BUS_ADDRESS を現在のUIDに合わせて強制補正することで解決しました。
~/.bashrc の末尾に以下を追加します。
# for rootless Podman
# Fix XDG_RUNTIME_DIR and DBUS_SESSION_BUS_ADDRESS for the current user
_my_uid="$(id -u)"
_my_runtime_dir="/run/user/${_my_uid}"
if [ -d "$_my_runtime_dir" ] && [ "$(stat -c '%u' "$_my_runtime_dir" 2>/dev/null)" = "$_my_uid" ]; then
export XDG_RUNTIME_DIR="$_my_runtime_dir"
if [ -S "${_my_runtime_dir}/bus" ]; then
export DBUS_SESSION_BUS_ADDRESS="unix:path=${_my_runtime_dir}/bus"
else
unset DBUS_SESSION_BUS_ADDRESS
fi
else
unset XDG_RUNTIME_DIR
unset DBUS_SESSION_BUS_ADDRESS
fi
unset _my_uid
unset _my_runtime_dir
ポイントは、以下の2つをセットで補正することです。
XDG_RUNTIME_DIR
DBUS_SESSION_BUS_ADDRESS
XDG_RUNTIME_DIR だけ直しても、DBUS_SESSION_BUS_ADDRESS が /run/user/1000/bus のままだと、Podmanがそちらに接続しに行って失敗します。
.bash_profile と .bashrc の使い分け
.bash_profile はログインシェルで読まれます。
su - user02
のような入り方では、.bash_profile が読み込まれます。
一方、.bashrc は対話型シェルで読まれます。
今回のような補正は、実用上 .bashrc に置くのが無難です。
.bash_profile では .bashrc を読み込むだけにしておくと分かりやすいです。
# ~/.bash_profile
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
これにより、su - user02 でログインした場合も、最終的に .bashrc の補正が効きます。
/run/user/1001 が存在しない場合
user02 用の /run/user/1001 が存在しない場合は、user02 の systemd user manager が起動していない可能性があります。
rootで以下を実行します。
loginctl enable-linger user02
systemctl start user@$(id -u user02).service
確認します。
loginctl show-user user02 -p Linger -p RuntimePath
ls -ld /run/user/1001
期待値は以下です。
Linger=yes
RuntimePath=/run/user/1001
/run/user/1001 の所有者は user02 である必要があります。
drwx------ ... user02 user02 ... /run/user/1001
systemctl enable user@1001.service は必要か
通常、以下は不要です。
systemctl enable user@1001.service
user@UID.service は、通常の自作サービスのように自分でenableするものというより、systemd-logindがユーザーセッションやlinger設定に応じて管理するuser managerです。
自動起動やログアウト後の維持が目的なら、基本的には以下を使います。
loginctl enable-linger user02
その場ですぐ起動したい場合だけ、以下を実行します。
systemctl start user@$(id -u user02).service
/run/user/1000 が設定ファイルに固定されていないか確認する
念のため、どこかの設定ファイルに /run/user/1000 が固定で書かれていないか探します。
grep -RIn "XDG_RUNTIME_DIR\|DBUS_SESSION_BUS_ADDRESS\|/run/user/1000" \
/etc/profile /etc/bashrc /etc/profile.d \
/home/user01 /home/user02 2>/dev/null
もし以下のような行が出てきたら、削除またはコメントアウトします。
export XDG_RUNTIME_DIR=/run/user/1000
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
ただし、今回の場合は設定ファイルに固定値があったというより、user01 でSSHログイン後に su user02 していたことで、環境変数を引き継いだ可能性が高いです。
今回の流れを図で整理する
問題が起きていた流れは以下です。
正しい状態は以下です。
やってはいけないこと
/run/user/1000 の所有者を変えない
以下はやってはいけません。
chown user02:user02 /run/user/1000
/run/user/1000 はUID 1000のユーザー用です。
今回であれば、user01 用です。
user02 のUIDが1001なら、必要なのは /run/user/1001 です。
XDG_RUNTIME_DIR=/run/user/1000 を固定しない
以下もNGです。
export XDG_RUNTIME_DIR=/run/user/1000
user02 で動かすなら、以下である必要があります。
export XDG_RUNTIME_DIR=/run/user/1001
ただし、手動で固定するより、現在のUIDを使って補正する方が安全です。
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
DBUS_SESSION_BUS_ADDRESS=/run/user/1000/bus を残さない
XDG_RUNTIME_DIR だけ直っていても、以下の状態ではまだダメです。
XDG_RUNTIME_DIR=/run/user/1001
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
この場合、Podmanが /run/user/1000/bus に接続しに行って失敗します。
正しくは以下です。
XDG_RUNTIME_DIR=/run/user/1001
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1001/bus
/run/user/1001 を手動作成して終わりにしない
一時的には以下のように作成することもできます。
mkdir -p /run/user/1001
chown user02:user02 /run/user/1001
chmod 700 /run/user/1001
しかし、/run は再起動で消えます。
本来 /run/user/$UID は、systemd-logindやPAMによって管理されるものです。
そのため、根本的には以下のような方法で、systemd側に正しく用意させる方が自然です。
loginctl enable-linger user02
systemctl start user@$(id -u user02).service
一番きれいな運用
一番きれいなのは、Podman rootlessを使うユーザーで直接SSHログインすることです。
ssh user02@サーバIP
これなら、最初から以下の状態になりやすいです。
whoami = user02
id -u = 1001
XDG_RUNTIME_DIR = /run/user/1001
DBUS_SESSION_BUS_ADDRESS = unix:path=/run/user/1001/bus
user01 でSSHログインしてから切り替える場合は、少なくとも以下を使います。
su - user02
ただし、環境によってはDBus関連の環境変数が残ることがあるため、今回のように .bashrc で補正するのは実用上有効です。
最終的に目指す状態
user02 で以下の状態になっていればOKです。
whoami
# user02
id -u
# 1001
echo "$XDG_RUNTIME_DIR"
# /run/user/1001
echo "$DBUS_SESSION_BUS_ADDRESS"
# unix:path=/run/user/1001/bus
ls -ld "$XDG_RUNTIME_DIR"
# drwx------ ... user02 user02 ... /run/user/1001
そのうえで、Podmanを確認します。
podman info
podman ps -a
podman-compose up --build
または、
podman compose up --build
が動けば、user02 のrootless Podman環境としては正常な状態です。
まとめ
今回の原因は、Podmanそのものではなく、ユーザーセッション由来の環境変数のズレでした。
特に重要なのは次の2つです。
XDG_RUNTIME_DIR
DBUS_SESSION_BUS_ADDRESS
user02 のUIDが1001なら、正しい状態は以下です。
XDG_RUNTIME_DIR=/run/user/1001
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1001/bus
一方、以下のような状態はNGです。
XDG_RUNTIME_DIR=/run/user/1000
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
また、途中で XDG_RUNTIME_DIR だけ直っていても、以下の状態ではまだ不完全です。
XDG_RUNTIME_DIR=/run/user/1001
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
XDG_RUNTIME_DIR はPodman rootlessの実行用ディレクトリに関係し、DBUS_SESSION_BUS_ADDRESS はsystemd user session / D-Busとの接続先に関係します。
そのため、Podman rootlessで /run/user/1000 や /run/user/1000/bus に関するエラーが出た場合は、片方だけでなく、必ず両方を確認するのが重要です。
今回の教訓は以下です。
Podman rootless のエラーでは、XDG_RUNTIME_DIR と DBUS_SESSION_BUS_ADDRESS をセットで確認する
特に、user01 でSSHログイン後に su user02 するような運用では、元ユーザーの環境変数を引き継ぐことがあります。
できれば、Podman rootlessを使うユーザーで直接SSHログインするのが望ましいです。
ssh user02@サーバIP
それが難しい場合でも、最低限 su - user02 を使い、必要に応じて .bashrc で XDG_RUNTIME_DIR と DBUS_SESSION_BUS_ADDRESS を現在のUIDに合わせて補正すると、今回のような問題を回避できます。