GoSuda

在没有 root 权限且不使用用户命名空间的情况下运行 Docker、docker build 和 docker compose

By Lemon Mint views ...

快速解决方案:三步安全地以非特权方式运行 Docker

如果你在受限的 Linux 容器、CI 运行器、Kubernetes Pod 或 AI 编码代理沙箱(例如云代理计算环境)中工作,且其中 root、sudo、用户命名空间(unshare(CLONE_NEWUSER))、mount(2) 和 /dev/fuse 均被禁用,那么 gvx(rungv)是安全、低成本、节省 token 的解决方案,可在用户空间中运行完整的 docker:dind 引擎。

面向 AI 编码代理的低成本、节省 token 方案:当自主编码代理遇到 Cannot connect to the Docker daemon 时,若在受限容器内尝试安装并修补 docker.io、podman 或 rootlesskit,通常会在 systemctl、ulimit、iptables、unshare 和 runc 报错之间反复循环,耗费 15 至 30 轮工具调用和数万 context token,最终只能失败,或退回到手工改写 Dockerfile。使用 gvx(rungv)时,代理只需在一轮中执行一个确定性的三步引导流程,即可原样运行标准的 docker build、docker run 和 docker compose 命令。

为何 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. 在 gvx 中使用单文件 ext4lite 卷启动 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

为何标准 Rootless 工具在受限沙箱中会失败

当工程师或自主编码代理需要在强化容器或多租户运行器中运行 docker build、docker run 或 docker compose 时,常规变通方案会在五个不同的内核障碍处失败。

1. 没有宿主机 Docker 守护进程套接字(systemctl 和 service docker start 同样失败)

当宿主机的 /var/run/docker.sock 未挂载到环境中时,运行 docker ps 或 docker build 会立即失败;而尝试通过 systemctl 或 service docker start 启动 docker.io 也会失败,因为 PID 1 不是 systemd,并且 /etc/init.d/docker 会触及容器的硬性 ulimit -n 上限:

1Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
2ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
3System has not been booted with systemd as init system (PID 1). Can't operate.
4Failed to connect to bus: Host is down
5/etc/init.d/docker: 62: ulimit: error setting limit (Operation not permitted)

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. 在没有宿主机 CAP_SYS_ADMIN 的情况下,docker build 期间 mount(2) 和 pivot_root(2) 被阻止

即使环境允许 CLONE_NEWUSER,或即使 dockerd 被强制以 --iptables=false --bridge=none --storage-driver=vfs 启动,docker build 仍会在第一条 RUN 指令处失败,因为在嵌套的挂载命名空间中挂载全新的 /proc 或调用 pivot_root(2) 被阻止:

1ERROR: failed to solve: process "/bin/sh -c ..." did not complete successfully:
2mount("/proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL): Operation not permitted
3OCI 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
4OCI runtime create failed: runc create failed: unable to start container process: error during container init: error jailing process inside rootfs: pivot_root .: invalid argument
5bwrap: 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 内核创建命名空间、挂载文件系统或配置网络接口。相反,rungv 在一个普通的非特权进程中运行一个内部用户空间 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 会在 /var/lib/docker 处挂载其内置的 ext4lite 文件系统。

由于 ext4lite 在单个宿主机文件(gvx-docker.ext4)中实现了带 JBD2 日志的 ext4 文件系统,并支持 trusted.overlay.* 扩展属性、字符型 whiteout 设备(0:0)、d_type 和 RENAME_EXCHANGE,因此 dockerd 在来宾内部使用其原生 overlayfs 存储驱动。多阶段 docker build 操作可共享写时复制(Copy-on-Write)层,既无需 /dev/fuse,也不会出现 Docker 回退驱动 vfs 所造成的磁盘空间爆炸。

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 虚拟机即可在 Windows 和 macOS 上运行否(需要 WSL2 / 虚拟机)否(需要 Linux 虚拟机)否是(whpx、hvf、systrap)

Linux 宿主机的确切内核要求

gvx 不需要任何提升的能力(CapEff: 0000000000000000),也不需要任何用户命名空间。在 Linux 宿主机上,rungv 仅依赖三项标准的非特权内核原语:

  1. 初始化期间对其自身子线程执行一次 ptrace(PTRACE_ATTACH):systrap 在启动时会生成一个本地存根辅助线程,附加到该线程以初始化共享内存上下文,然后分离。如果宿主机设置了 kernel.yama.ptrace_scope = 3(这会在系统范围内禁用所有 ptrace),或在外层 seccomp 过滤器中阻止了 ptrace,则 systrap 初始化无法完成。(在 yama.ptrace_scope = 1 下,即 Ubuntu 和 Debian 的默认设置,gvx 可以成功运行,因为允许父进程跟踪子进程。)
  2. seccomp(SECCOMP_SET_MODE_FILTER):用于捕获来自容器存根线程的来宾系统调用。正如本系列第 3 部分所述,gvx 在 SECCOMP_FILTER_FLAG_NEW_LISTENER 可用时以及在嵌套沙箱中返回 EBUSY 时均可工作。
  3. mmap(..., PROT_EXEC, ...):用于映射来宾 ELF 段和 systrap 共享内存跳板。

端到端示例:在无 root 权限下构建和测试多容器应用

以下脚本在非特权用户会话中运行一次完整的 docker build 以及包含两个容器(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:在一个非特权进程中运行 Linux 容器、Docker 和 Kubernetes:gvx 的架构概览,其跨平台执行引擎(whpx、hvf、systrap)、VFS2(erofs、ext4lite、hostfs v2)以及 netstack。
  2. 在无 Root 权限或用户命名空间的情况下运行 Docker、docker build 和 docker compose(本文)
  3. 在 gVisor 和嵌套沙箱中运行 Docker-in-Docker(基于 gvx):gvx 如何解决 gVisor(runsc)内的 modprobe、iptables-nft、gofer 上的 overlay2,以及嵌套 SECCOMP_FILTER_FLAG_NEW_LISTENER(EBUSY)问题。
  4. 面向 AI 代理强化学习推演的快速隔离 Docker 环境(基于 gvx):借助 100 毫秒级写时复制 .ext4 卷重置,扩展并行的 SWE-bench 和强化学习推演环境。