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)是在用户空间中运行完整 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. 在 gvx 中启动 docker:dind,并使用单文件 ext4lite 卷
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 守护进程套接字

当主机的 /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. 若无主机 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 内核创建命名空间、挂载文件系统或配置网络接口。相反,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 和嵌套沙箱中使用 gvx 运行 Docker-in-Docker:gvx 如何解决 gVisor(runsc)内的 modprobe、iptables-nft、gofer 上的 overlay2,以及嵌套的 SECCOMP_FILTER_FLAG_NEW_LISTENER(EBUSY)问题。
  4. 使用 gvx 为 AI 智能体 RL 推演提供快速隔离的 Docker 环境:借助 100 毫秒写时复制 .ext4 卷重置,扩展并行的 SWE-bench 和 RL 推演环境。