🐳

Podman in Podmanの仕組みを深掘り:Rootlessネストコンテナとnewuidmapの秘密

に公開1

はじめに

CI/CDパイプライン(GitLab RunnerやGitHub Actionsなど)のコンテナ内で、さらにコンテナイメージをビルドしたい場面はよくあります。これを実現する技術としてDocker in Docker(DinD)が有名ですが、セキュリティの観点から特権モード(--privileged)が必須となるなど、運用上の課題も少なくありません。

そこで近年注目されているのがPodmanです。Podmanはデーモンレスアーキテクチャを採用しており、ルートレス(一般ユーザー権限)での実行を得意としています。

この記事では、ホストマシンの特権を一切使わずに、一般ユーザー権限のコンテナ内でさらに一般ユーザー権限のコンテナを動かす「Rootless Podman in Rootless Podman」の具体的な構築手順と、それを支えるLinuxカーネルのディープな仕組み(newuidmap/etc/subuidなど)について徹底解説します。

1. なぜ「Rootless in Rootless」なのか?

Docker in Docker(DinD)を実行する場合、通常は外側のDockerコンテナを起動する際に --privileged オプションを付与し、Dockerデーモンを起動させる必要があります。しかし、特権モードのコンテナはホストOSのほぼすべてのデバイスにアクセスできるため、マルチテナント環境やセキュリティ要件の厳しい本番環境では使用が制限されることがほとんどです。

Podmanを使えば、外側のコンテナも、内側のコンテナも、すべて一般ユーザー権限(ルートレス)で実行できます。これにより、ホストOSを汚染したり、特権昇格の脆弱性を突かれたりするリスクを極限まで減らしつつ、CI/CD環境でのコンテナビルドを安全に実現できます。

2. 最もシンプルな「Rootless in Rootless」の実践

まずは論より証拠、実際に動かしてみましょう。
Podmanプロジェクトが公式に提供しているイメージ(quay.io/podman/stable)を使用すると、環境構築のハードルがグッと下がります。

外側のコンテナを起動する(ホストOS上)

ホストOSの一般ユーザーで、以下のコマンドを実行します。ここでは --privileged のような特権は使いません。

podman run -it \\
--security-opt label=disable \\
--user podman \\
--device /dev/fuse \\
quay.io/podman/stable /bin/bash

内側のコンテナを起動する(外側のコンテナ内)

外側のコンテナのシェルに入ると、プロンプトが [podman@<コンテナID> /]$ となり、podmanという一般ユーザーでログインしていることがわかります。
この状態で、内側のPodmanコマンドを実行してみます。

podman run --rm alpine echo "Hello from Rootless in Rootless!"

無事に Hello from Rootless in Rootless! と出力されれば成功です。ホストOSにいかなる特権も要求せず、完全にネストされたコンテナ環境が動作しています。

3. なぜこのコマンドで動くのか? 必須オプションの解説

先ほどの起動コマンドには、ルートレス環境をネストさせるための重要な要素が詰まっています。それぞれのオプションが何をしているのかを紐解いてみましょう。

--device /dev/fuse (ファイルシステムのマジック)

コンテナは、複数のイメージレイヤーを重ねて1つのファイルシステムとして見せる技術(OverlayFS)を使っています。通常、このマウント操作にはroot権限が必要ですが、ルートレスPodmanでは FUSE (Filesystem in Userspace) を利用した fuse-overlayfs という技術を使い、一般ユーザー空間でレイヤーをマウントします。

外側のコンテナ内でこの fuse-overlayfs を動作させるためには、ホストのFUSEデバイスへのアクセス権が必要不可欠です。そのため、--device /dev/fuse を明示的に渡しています。

--security-opt label=disable (SELinuxの壁を越える)

SELinuxが有効な環境(RHELやFedoraなど)では、コンテナに対して「外の世界へのアクセスを制限するラベル」が強制的に付与されます。
しかし、内側のPodmanは新しい名前空間を作ったり、ネットワークを分離したりと、システムレベルの複雑な操作を行います。SELinuxはこれを「不審な挙動」としてブロックしてしまうため、label=disable でこのコンテナに対するラベル付けを無効化しています。

--user podman (実行ユーザーの指定)

ルートレスコンテナの中で、さらにルートレスコンテナを動かすためには、外側のコンテナ自体を「コンテナ内の一般ユーザー」として実行させる必要があります。公式イメージにはあらかじめ podman というユーザーが用意されており、このユーザーを指定して実行しています。

4. ルートレスコンテナの核心:IDマッピングの謎

ここからが、コンテナ技術の最も面白い部分です。
ルートレスコンテナを理解する上で最大の壁となるのが、「UID(ユーザーID)のマッピング(変換)」です。

一般ユーザーの限界

コンテナの中には1つの独立したLinuxが入っているように見えるため、中では root (UID 0)daemon (UID 1)www-data (UID 33) など、様々なユーザーが動いています。
しかし、ホストOSから見ると、外側のPodmanを実行しているのはただの「一般ユーザー(例:UID 1000)」です。

Linuxの厳格なルールとして、「一般ユーザー(UID 1000)は、自分自身のIDしか使ってはいけない」 というものがあります。つまり、一般ユーザーのままでは、コンテナ内の www-data (UID 33) としてファイルを作成することはできません。

解決策:「サブUID」というお小遣い

この問題を解決するために、システム管理者は一般ユーザーに対して「自分以外のIDも使っていいよ」という許可枠を与えます。これが /etc/subuid というファイルです。

podman:100000:65536

これは、「podman というユーザーには、特別に 100000番から65536個のUID の使用を許可する」という意味になります。

いざ変換:newuidmap の登場

許可を得たからといって、一般ユーザーが勝手にLinuxカーネルの「ID変換テーブル」を書き換えることはできません。カーネルの設定変更にはroot権限が必要だからです。

そこで登場するのが newuidmapnewgidmap です。これは、「一般ユーザーの代わりに、一瞬だけroot権限を使って変換テーブルをカーネルに登録してくれる、特別な橋渡し役」 です。

Podmanがコンテナを起動する際、裏側で newuidmap を呼び出します。すると newuidmap/etc/subuid の許可リストを確認し、以下のような変換テーブルをカーネルに登録します。

コンテナの中(仮想の姿) 変換 ホストOS(本当の姿)
UID 0 (コンテナのroot) UID 1000 (一般ユーザー自身)
UID 1 (daemon) UID 100000 (許可枠の1番目)
UID 2 (bin) UID 100001 (許可枠の2番目)
... ... ...
UID 65535 UID 165534 (許可枠の最後)

これにより、コンテナ内のプロセスは「自分はrootだ!」と思って動いていても、ホストOSから見ればただの一般ユーザー(または許可されたサブUID)として安全に実行されるわけです。

5. newuidmap はなぜ安全なのか? (SetUIDの秘密)

ここで一つ、鋭い疑問が浮かぶはずです。

「一般ユーザーが呼び出したコマンドがシステムの設定を書き換えられるなんて、悪用されたら危険じゃないの?」

実は、newuidmap は一時的にroot権限で動くように SetUID (SUID) という特別な権限が付与されています。

$ ls -l /usr/bin/newuidmap
-rwsr-xr-x 1 root root 34888 /usr/bin/newuidmap

所有者の実行権限が s になっているのが分かります。これにより、誰が実行してもroot権限で動作します。

しかし、これは決して危険ではありません。その理由は以下の2点です。

  1. ルールの決定権はrootにある:
     誰にどのIDの利用を許可するかを定義した /etc/subuid は、root権限がないと編集できません。

  2. newuidmap は決められたことしかしない:
     このプログラムは「/etc/subuid を読んで、書かれている通りのマッピングをカーネルに登録する」という単一のタスクしか実行できないように厳格にプログラミングされています。
    ユーザーが不正なIDの登録を要求しても、ファイルに記述がなければ即座に弾かれます。

つまり、newuidmap は 「rootが事前に設定した安全なルールの範囲内でだけ、rootの代行をしてくれる信頼できるロボット」 なのです。

6. おまけ:コンテナの正体はLinuxの標準機能

ここまで解説した「ユーザー名前空間(User Namespace)」の仕組みは、DockerやPodmanのためだけの独自技術ではなく、Linuxカーネルの標準機能です。

コンテナエンジンを使わなくても、Linux標準の unshare コマンドを使えば、この名前空間の分離を体験できます。

# 一般ユーザーのまま、自分だけの新しい「名前空間」を作る
$ unshare --user --map-root-user /bin/bash

# 新しい空間の中に入った後、自分が誰か聞いてみる
# whoami
root

ホスト上では一般ユーザーなのに、隔離されたシェルの中では自分が「root」として振る舞える空間が誕生します。
コンテナの正体は、こうしたLinuxの標準機能(名前空間、Cgroupsなど)を便利に組み合わせただけのものなのです。

まとめ

Podmanを使えば、ホストの特権を一切使用せずに、コンテナの中でコンテナを動かす「Rootless in Rootless」環境を構築できます。

  • 外側のコンテナには --device /dev/fuse などを渡し、FUSEベースのファイルシステムマウントを許可する。
  • ルートレスコンテナの心臓部は、ユーザー名前空間によるIDマッピングである。
  • 一般ユーザーが複数のIDを扱うために /etc/subuid という許可枠があり、それをカーネルに安全に登録する役割を newuidmap が担っている。

仕組みを知ることで、トラブルシューティングの解像度も上がり、よりセキュアなコンテナ運用が可能になります。ぜひ皆様のCI/CD環境や開発環境でも、Podman in Podmanを試してみてください!

ヘッドウォータース

Discussion

妹尾悠真妹尾悠真

めっちゃAIっぽい記事になりましたが、内容は分かりやすいのでそのまま投稿しちゃいました。