GoSuda

ルートユーザーやユーザー名前空間を使わずに Docker、docker build、docker compose を実行する

By Lemon Mint views ...

クイックソリューション: 3つのステップで特権なしの Docker を安全に実行する

root、sudo、ユーザー名前空間(unshare(CLONE_NEWUSER))、mount(2)、/dev/fuse が禁止されている、ロックダウンされた Linux コンテナ、CI ランナー、Kubernetes Pod、AI エージェントのサンドボックス内で作業している場合、gvx (rungv) は、ユーザー空間で完全な docker:dind エンジンを実行するための安全で多層防御型のソリューションです。

gvx が設計上安全である理由 (権限昇格の回避策ではない): ホストの /var/run/docker.sock をマウントする場合や docker run --privileged を実行する場合(これらはホストカーネルに対する root 相当の制御を与えます)と異なり、gvx はいかなるホストのセキュリティポリシー(PR_SET_NO_NEW_PRIVS、seccomp、AppArmor、非 root の UID 制限)も回避または弱体化させません。代わりに、rungv は自身のホストプロセスをロックダウンし(PR_SET_NO_NEW_PRIVS と厳格な BPF seccomp 許可リスト)、Google のメモリ安全な gVisor (Sentry) ユーザー空間カーネル内で dockerd を実行します。ゲスト内の UID 0 の権限、overlayfs マウント(ext4lite)、iptables ルールはすべて、サンドボックスプロセス内に厳密に隔離されたままです。

 1# 1. ユーザー空間に rungv をインストールする(ビルド済みの SLSA 証明付きリリースまたは go install)
 2# オプション A: GitHub Releases から SLSA 証明付きのビルド済みバイナリを検証して展開する
 3gh attestation verify rungv-v0.2.0-linux-amd64.tar.gz --repo gfire-sigs/gvx \
 4  --signer-workflow gfire-sigs/gvx/.github/workflows/release.yml \
 5  --source-ref refs/tags/v0.2.0 --deny-self-hosted-runners
 6tar -xzf rungv-v0.2.0-linux-amd64.tar.gz && install -m 0755 rungv-v0.2.0-linux-amd64/rungv ~/.local/bin/rungv
 7
 8# オプション B: Go でソースから直接インストールする
 9CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest
10
11# 2. 単一ファイルの ext4lite ボリュームを使用して gvx 内で docker:dind を起動する
12#    ~/.gvx/docker.sock (モード 0600) をゲストのポート 2375 に転送する
13mkdir -p ~/.gvx
14~/.local/bin/rungv run \
15  -p unix:~/.gvx/docker.sock:2375 \
16  -p 127.0.0.1:9000-9019:9000-9019 \
17  -v gvx-docker:/var/lib/docker \
18  -e DOCKER_TLS_CERTDIR= \
19  docker:dind --tls=false &
20
21# 3. 標準の Docker CLI と Docker Compose をユーザー空間のソケットに向ける
22export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
23until docker info >/dev/null 2>&1; do sleep 0.5; done
24docker info --format '{{.Driver}} {{.OperatingSystem}}'
25# 出力: overlayfs Alpine Linux ... (containerized)
26
27docker build -t app:test .
28docker run --rm app:test
29docker compose up -d

制限された環境で標準のロootレスツールが失敗する理由

エンジニアや自律型コーディングエージェントが、ハードニングされたコンテナやマルチテナントランナー内で docker build、docker run、docker compose を実行する必要がある場合、一般的な回避策は5つの異なるカーネルの障壁で失敗します。

1. ホストの Docker デーモンソケットが存在しない

ホストの /var/run/docker.sock が環境にマウントされていない場合、docker ps や docker build は即座に失敗します:

1Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

2. PR_SET_NO_NEW_PRIVS が sudo と setuid ヘルパーをブロックする

ハードニングされたコンテナ(Kubernetes の securityContext.allowPrivilegeEscalation: false、Docker の --security-opt=no-new-privileges、または exebox / bwrap サンドボックス)は、プロセスツリーに Linux の PR_SET_NO_NEW_PRIVS ビットを設定します。sudo や setuid バイナリ(newuidmap、newgidmap、fusermount3)による権限昇格を試みると失敗します:

1sudo: The "no new privileges" flag is set, which prevents sudo from running as root.
2sudo: effective uid is not 0, is /usr/bin/sudo on a file system with the 'nosuid' option set or an NFS file system without root privileges?

3. seccomp とsysctl ポリシーが unshare(CLONE_NEWUSER) をブロックする

「rootless」として宣伝されているツール(Rootless Docker、Podman、Buildah、nerdctl、bubblewrap)は、依然として Linux のユーザー名前空間 (CLONE_NEWUSER) に依存しています。これらは次の場合に失敗します:

  • 外側のコンテナのデフォルト seccomp プロファイルが unshare(2) または clone(CLONE_NEWUSER) をブロックしている。
  • Ubuntu 24.04 以降で kernel.apparmor_restrict_unprivileged_userns = 1 が有効になっている。
  • Debian または RHEL のノードで kernel.unprivileged_userns_clone = 0 または user.max_user_namespaces = 0 が設定されている。
  • PR_SET_NO_NEW_PRIVS が有効なため、newuidmap が /proc/self/uid_map に書き込めない。

発生するエラーは次のようになります:

1unshare: unshare failed: Operation not permitted
2cannot clone: Operation not permitted
3Error: cannot re-exec process to join the existing user namespace
4newuidmap: write to uid_map failed: Operation not permitted
5RootlessKit: parent: failed to start the child: fork/exec /proc/self/exe: operation not permitted

4. host の CAP_SYS_ADMIN がない場合、mount(2) がブロックされる

環境が CLONE_NEWUSER を許可していても、外側のコンテナが /proc のサブツリー(/proc/acpi、/proc/kcore、/proc/keys)をマスクしているか、seccomp で mount(2) を削除している場合、入れ子になったマウント名前空間内で新しい /proc をマウントすることは失敗します:

1mount("/proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL): Operation not permitted
2OCI runtime create failed: runc create failed: unable to start container process: error during container init: error mounting "proc" to rootfs at "/proc": mount proc:/proc (via /proc/self/fd/6), flags: 0xe: operation not permitted
3bwrap: Creating new namespace failed: Operation not permitted

5. /dev/fuse が存在しないと fuse-overlayfs が壊れる

カーネルの overlayfs をマウントできない rootless コンテナエンジンは、fuse-overlayfs にフォールバックします。最小構成のコンテナや、/dev/fuse デバイスがパススルーされていない Kubernetes Pod では、fuse-overlayfs は即座に失敗します:

1fuse: device not found, try 'modprobe fuse' first
2fusermount3: failed to open /dev/fuse: No such file or directory
3error initializing graphdriver: driver not supported: fuse-overlayfs

gvx が docker:dind を完全にユーザー空間で実行する仕組み

gvx は、ホストの Linux カーネルに名前空間の作成、ファイルシステムのマウント、ネットワークインターフェースの設定を要求しないため、5つのカーネルの障壁すべてを回避します。代わりに、rungv は1つの通常の非特権プロセス内で、内部のユーザー空間 Linux カーネル(gVisor の Sentry)とユーザー空間のネットワークスタック(netstack)を実行します。

gvx による特権なし Docker-in-Docker の実行

1. インメモリの名前空間とマウントテーブル (VFS2)

dockerd、containerd、runc が rungv 内で実行されると、systrap がそれらのシステムコールを傍受し、ホストカーネルではなくプロセス内の Sentry カーネルにルーティングします:

  • unshare(2)、clone(2)、pivot_root(2)、chroot(2)、mount(2) は、Sentry のインメモリのタスク階層と VFS2 マウントテーブル(procfs、sysfs、tmpfs、devpts、cgroupfs v2)に対して動作し、ホストの rungv プロセスは利用者の非特権ホスト UID で実行されます。

2. 単一ファイルの ext4lite ボリュームにおけるネイティブな overlayfs

-v gvx-docker:/var/lib/docker(または --mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker)を渡すと、gvx は組み込みの ext4lite ファイルシステムを /var/lib/docker にマウントします。

ext4lite は、単一のホストファイル(gvx-docker.ext4)内に JBD2 ジャーナリングされた ext4 ファイルシステムを実装しており、trusted.overlay.* 拡張属性、文字型ホワイトアウトデバイス(0:0)、d_type、RENAME_EXCHANGE をサポートしているため、dockerd はゲスト内でネイティブの overlayfs ストレージドライバを使用します。マルチステージの docker build 処理は、/dev/fuse を必要とせず、Docker の vfs フォールバックドライバによるディスク容量の爆発的増加も起こさずに、Copy-on-Write レイヤーを共有します。

3. ユーザー空間の docker0 ブリッジと iptables-legacy

docker:dind が gvx 内で起動すると:

  • ゲストの dockerd は docker0 ブリッジ(172.17.0.1/16)を作成し、入れ子コンテナ用の仮想 veth ペアを接続して、gvx のユーザー空間 netstack 内で addrtype および conntrack の一致条件を持つ NAT(DOCKER、POSTROUTING)および FILTER チェーンを設定します。
  • -p unix:~/.gvx/docker.sock:2375 を渡すと、ホストの Unix ドメインソケット(モード 0600)がバインドされ、接続はサンドボックス内の dockerd のポート 2375 に直接転送されます。

既存のコンテナツールとの比較

機能 / 制約標準 Docker (dockerd)Rootless Docker / PodmanKaniko / Buildah (非特権)gvx (rungv + docker:dind)
ホストの root または sudo が必要はいいいえいいえいいえ
PR_SET_NO_NEW_PRIVS=1 で動作するいいえいいえ(newuidmap が失敗)部分的はい
unshare(CLONE_NEWUSER) がブロックされている場合(EPERM)で動作する該当なしいいえBuildah: いいえ / Kaniko: はいはい
/dev/fuse なしで動作するはい(カーネルの overlay2 を使用)遅い vfs コピーのみはいはい(ext4lite 上の overlayfs)
docker build (BuildKit) をサポートするはいはい(userns が許可されている場合)イメージビルドのみ(ランタイムなし)はい
docker run と docker compose up をサポートするはいはい(userns が許可されている場合)いいえはい
Linux VM なしで Windows および macOS で動作するいいえ(WSL2 / VM が必要)いいえ(Linux VM が必要)いいえはい(whpx、hvf、systrap)

Linux ホストにおける正確なカーネル要件

gvx は、特権機能をまったく必要とせず(CapEff: 0000000000000000)、ユーザー名前空間も必要としません。Linux ホストでは、rungv は次の3つの標準的な非特権カーネルプリミティブのみに依存します:

  1. 初期化時に一度だけ、自身の子スレッドに対して行う ptrace(PTRACE_ATTACH): systrap は起動時にローカルのスタブヘルパースレッドを生成し、そこにアタッチして共有メモリコンテキストを初期化した後、デタッチします。ホストが kernel.yama.ptrace_scope = 3(システム全体で ptrace を無効化する)を設定している場合、または外側の seccomp フィルタで ptrace をブロックしている場合、systrap の初期化は完了できません。(Ubuntu および Debian のデフォルトである yama.ptrace_scope = 1 の場合、親から子へのトレースが許可されるため、gvx は成功します。)
  2. seccomp(SECCOMP_SET_MODE_FILTER): コンテナのスタブスレッドからのゲストシステムコールを捕捉するために使用されます。このシリーズの第3部で説明されているとおり、gvx は SECCOMP_FILTER_FLAG_NEW_LISTENER が利用可能な場合と、入れ子のサンドボックス内で EBUSY を返す場合の両方で動作します。
  3. mmap(..., PROT_EXEC, ...): ゲストの ELF セグメントと systrap の共有メモリトランポリンをマップするために使用されます。

エンドツーエンドの例: root なしでのマルチコンテナアプリのビルドとテスト

次のスクリプトは、非特権のユーザーセッション内で、完全な docker build と2コンテナの統合テスト(redis + アプリケーションコンテナ)を実行します:

 1mkdir -p ~/.gvx
 2rungv run \
 3  -p unix:~/.gvx/docker.sock:2375 \
 4  -v gvx-docker:/var/lib/docker \
 5  -e DOCKER_TLS_CERTDIR= \
 6  docker:dind --tls=false &
 7DIND_PID=$!
 8
 9export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
10until docker info >/dev/null 2>&1; do sleep 0.5; done
11
12mkdir -p /tmp/demo-app && cd /tmp/demo-app
13cat <<'EOF' > Dockerfile
14FROM alpine:3.20
15RUN apk add --no-cache redis
16CMD ["redis-cli", "-h", "cache", "ping"]
17EOF
18
19# ext4lite 上の overlayfs を使用してイメージをビルドする
20docker build -t demo-ping:latest .
21
22# ブリッジネットワークを作成し、Redis を起動して、コンテナ間の接続を検証する
23docker network create demo-net
24docker run -d --name cache --network demo-net redis:7-alpine
25docker run --rm --network demo-net demo-ping:latest
26# 出力: PONG
27
28# ワークロードコンテナをクリーンアップし、サンドボックスを停止する
29docker rm -f cache
30docker network rm demo-net
31kill -INT $DIND_PID

本シリーズの関連記事

  1. gvx: 1つの特権なしプロセスで Linux コンテナ、Docker、Kubernetes を実行する: gvx のアーキテクチャの概要、そのクロスプラットフォーム実行エンジン(whpx、hvf、systrap)、VFS2(erofs、ext4lite、hostfs v2)、および netstack について。
  2. root またはユーザー名前空間なしで Docker、docker build、docker compose を実行する(本記事)
  3. gvx による gVisor および入れ子サンドボックス内での Docker-in-Docker の実行: gvx が modprobe、iptables-nft、gofer 上の overlay2、および gVisor(runsc)内の入れ子の SECCOMP_FILTER_FLAG_NEW_LISTENER(EBUSY)をどのように解決するか。
  4. gvx による AI エージェント RL ロールアウト向けの高速な隔離 Docker 環境: 100 ミリ秒の Copy-on-Write .ext4 ボリュームリセットを用いて、並列の SWE-bench および RL ロールアウト環境をスケールさせる方法。