GoSuda

Docker-in-Docker Uitvoeren Binnen gVisor en Geneste Sandboxes met gvx

By Lemon Mint views ...

Snelle oplossing: docker:dind veilig uitvoeren binnen een niet-geprivilegieerde gVisor (runsc)-sandbox

Compute-omgevingen voor cloud-AI-agents (zoals Google Agent Compute Engine-sandboxes), Kubernetes-agent-sandbox-deployments (RuntimeClass: gvisor) en evaluatieharnassen voor reinforcement learning isoleren onbetrouwbare workloads in gVisor (runsc)-containers, specifiek om de host-kernel te beschermen. Wanneer een workload in een standaard niet-geprivilegieerde runsc-container probeert docker.io of docker-ce te installeren via apt-get, service docker start of dockerd te starten en docker build uit te voeren, faalt deze, zelfs wanneer deze root (UID 0) binnen de container heeft.

Met gvx (rungv) kunt u docker:dind, docker build en docker compose veilig rechtstreeks uitvoeren binnen een niet-geprivilegieerde gVisor (runsc)-container (of gvx binnen gvx nesten), zonder de buitenste RuntimeClass te verzwakken, hostsockets bloot te stellen of --privileged toe te kennen.

Oplossing met weinig inspanning en efficiënt qua tokens voor AI-agents: Binnen een door gVisor ondersteunde cloud-agentomgeving leidt een poging om docker.io te installeren en handmatig de workarounds voor /etc/init.d/docker ulimit-fouten, iptables-nft-fouten, iptables-legacy-weigeringen van raw sockets, overlay2-mountfouten en docker build runc-/pivot_root-crashes op te lossen, doorgaans tot meer dan 20 tool-call-beurten en tienduizenden tokens voordat het volledig mislukt. gvx (rungv) is een oplossing met weinig inspanning en efficiënt qua tokens die direct inzetbaar is: de agent voert 3 commando's in één beurt uit om een werkende user-space Docker-daemon te starten en voert vervolgens onmiddellijk docker build en docker compose uit zonder projectbestanden te wijzigen.

Behoud en versterking van de isolatie van de buitenste sandbox: Het uitvoeren van rungv binnen runsc omzeilt geen enkele beperking die door de buitenste gVisor-sandbox wordt afgedwongen. In plaats daarvan maakt gvx een tweede, geneste user-space gVisor Sentry-kernel met zijn eigen hostlockdown (PR_SET_NO_NEW_PRIVS + seccomp), zijn eigen in-memory netstack en een enkelvoudig ext4lite-volume in één bestand. Interne dockerd- en runc-containers zijn dubbel gesandboxed: hun systeemaanroepen, mounts en ruwe pakketten verlaten de interne user-space kernel van gvx nooit.

 1# Binnen een bestaande gVisor (runsc)-container of Kubernetes Pod:
 2# Installeer rungv vanuit een SLSA-geattesteerd GitHub-releasearchief of via go install:
 3CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest
 4
 5mkdir -p ~/.gvx
 6rungv run \
 7  -p unix:~/.gvx/docker.sock:2375 \
 8  -v gvx-docker:/var/lib/docker \
 9  -e DOCKER_TLS_CERTDIR= \
10  docker:dind --tls=false &
11
12# Voer standaard Docker-builds en -containers uit tegen de interne gvx-socket
13export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
14until docker info >/dev/null 2>&1; do sleep 0.5; done
15docker info --format '{{.Driver}} {{.OperatingSystem}}'
16# Uitvoer: overlayfs Alpine Linux ... (containerized)
17
18docker build -t nested-app:test .
19docker run --rm nested-app:test

Vijf redenen waarom directe Docker-installatie faalt binnen gVisor (runsc)

Wanneer een gebruiker of AI-codeeragent apt-get install -y docker.io (of docker-ce) uitvoert binnen een standaard runsc-container en probeert dockerd te starten, stuit hij op vijf opeenvolgende technische barrières.

1. Init-script (systemctl / service docker start ulimit) en kernelmodule (modprobe 4.4.0)-fouten

Na het uitvoeren van apt-get install -y docker.io (of docker-ce) mislukt het starten van de daemon via systemctl start docker of service docker start onmiddellijk, omdat PID 1 geen systemd is en /etc/init.d/docker probeert ulimit -n te verhogen boven het harde RLIMIT_NOFILE-plafond van de sandbox. Wanneer dockerd vervolgens rechtstreeks wordt aangeroepen, roepen initialisatiescripts modprobe aan om overlay, bridge, br_netfilter, ip_tables en nf_nat te laden tegen de virtuele kernelversie van gVisor (4.4.0), die geen map /lib/modules/4.4.0 heeft:

1System has not been booted with systemd as init system (PID 1). Can't operate.
2Failed to connect to bus: Host is down
3/etc/init.d/docker: 62: ulimit: error setting limit (Operation not permitted)
4level=warning msg="Running modprobe bridge br_netfilter failed with message: modprobe: WARNING: Module bridge not found in directory /lib/modules/4.4.0\nmodprobe: WARNING: Module br_netfilter not found in directory /lib/modules/4.4.0\n, error: exit status 1"
5modprobe: FATAL: Module overlay not found in directory /lib/modules/4.4.0
6modprobe: FATAL: Module br_netfilter not found in directory /lib/modules/4.4.0
7modprobe: FATAL: Module ip_tables not found in directory /lib/modules/4.4.0
8modprobe: FATAL: Module nf_nat not found in directory /lib/modules/4.4.0

2. iptables-nft (nf_tables Netlink), weigeringen van raw sockets door iptables-legacy en storingen van de docker0-bridge

Op Ubuntu 22.04, Ubuntu 24.04 en Debian 11/12 selecteert apt-get install docker.io iptables-nft (ondersteund door het nf_tables netlink-subsysteem) als standaard /usr/sbin/iptables-alternatief. Bovendien, zelfs wanneer de gebruiker of agent overschakelt naar iptables-legacy (update-alternatives --set iptables /usr/sbin/iptables-legacy), wijzen standaard niet-geprivilegieerde gVisor-sandboxes (--net-raw=false) AF_INET, SOCK_RAW-sockets af met EPERM, waardoor iptables-legacy faalt met Permission denied (you must be root), zelfs wanneer als UID 0 wordt gedraaid:

1failed to start daemon: Error initializing network controller: error obtaining controller instance: failed to register "bridge" driver: failed to create NAT chain DOCKER: iptables failed: iptables --wait -t nat -N DOCKER: iptables v1.8.10 (nf_tables): Could not fetch rule set generation id: Invalid argument
2iptables v1.8.10 (legacy): can't initialize iptables table `nat': Permission denied (you must be root)
3Perhaps iptables or your kernel needs to be upgraded.
4iptables v1.8.10 (legacy): can't initialize iptables table 'nat': Table does not exist (do you need to insmod?)
5iptables: No chain/target/match by that name.
6Unable to enable SKIP DNAT rule: (iptables failed: iptables --wait -t nat -I DOCKER -i docker0 -j RETURN: iptables: No chain/target/match by that name.)
7Error initializing network controller: error obtaining controller instance: failed to create bridge "docker0": operation not permitted

3. overlay2-fout op gofer/9p-rootfs en uitputting van de schijf door vfs

Tenzij de beheerder van de buitenste runsc expliciet een speciale tmpfs- of filestore-mount op /var/lib/docker heeft geconfigureerd, wordt de containerrootfs ondersteund door de gofer (9p/lisafs) van gVisor of door een geneste overlay-mount. Het mounten van overlay2 bovenop /var/lib/docker mislukt:

1level=error msg="[graphdriver] prior storage driver overlay2 failed: driver not supported"
2failed to mount overlay: invalid argument
3Error starting daemon: error initializing graphdriver: driver not supported: overlay2

Wanneer gebruikers of agents proberen deze fout te omzeilen door dockerd --storage-driver=vfs te starten, schakelt Docker het delen van Copy-on-Write-lagen volledig uit. Elke RUN-instructie in een Dockerfile voert een volledige recursieve kopie (dirCopy) uit van het gehele root-bestandssysteem, waardoor een image van 500 MB over 10 buildstappen wordt vermenigvuldigd tot 5 GB aan schrijfacties, en de sandbox eindigt met no space left on device of een I/O-timeout.

4. docker build (BuildKit) en fouten in geneste runc (pivot_root, /proc, cgroups en DNS)

Zelfs wanneer dockerd wordt gedwongen te starten met dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs, mislukt het uitvoeren van docker build -t app . of docker run onmiddellijk wanneer BuildKit of runc de buildcontainer start:

 1ERROR: failed to solve: process "/bin/sh -c ..." did not complete successfully:
 2docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process:
 3error during container init: error jailing process inside rootfs: pivot_root .: invalid argument
 4error during container init: error mounting "cgroup" to rootfs at "/sys/fs/cgroup": mount cgroup:/sys/fs/cgroup/cpuset (via /proc/self/fd/6), flags: 0xf, data: cpuset: operation not permitted
 5cgroups: cgroup mountpoint does not exist: unknown
 6runc create failed: systemd not running on this host: cannot use systemd cgroup manager
 7error during container init: error mounting "proc" to rootfs at "/proc": mount proc:/proc (via /proc/self/fd/6), flags: 0xe: operation not permitted
 8error during container init: error loading seccomp filter into kernel: operation not permitted
 9Temporary failure resolving 'deb.debian.org'
10Temporary failure resolving 'archive.ubuntu.com'

5. Geneste runsc faalt met SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY)

Als een gebruiker probeert een andere runsc-instantie binnen runsc uit te voeren met het systrap-platform, wordt de initialisatie onmiddellijk afgebroken:

1creating subprocess: sysmsg: failed to initialize thread: failed to install seccomp notify rules: device or resource busy

Zowel de Linux-kernel als de gVisor Sentry staan hooguit één SECCOMP_FILTER_FLAG_NEW_LISTENER (SECCOMP_RET_USER_NOTIF)-listener per taakhiërarchie toe. Omdat de buitenste sandbox die listener-slot al bezet houdt, geeft de interne seccomp(2)-aanroep EBUSY terug (device or resource busy).


Hoe gvx geneste uitvoering binnen gVisor oplost

gvx pakt alle vijf barrières aan binnen zijn eigen user-space kernel en platformlaag, waardoor rungv kan draaien binnen een buitenste gVisor (runsc)-container of zelfs recursief binnen een andere gvx-container (gvx in gvx).

Geneste gvx-uitvoering binnen buitenste gVisor

1. Automatische systrap-terugval van EBUSY naar de _RUN_SYSCALL_LOOP futex-lus

In Linux systrap voert elk stub-subproces twee soorten threads uit:

  • Gastuitvoeringsthreads (sysmsg): Onderscheppen gast-systeemaanroepen via SECCOMP_RET_TRAP (SIGSYS) en binair gepatchte jmp rel32-trampolines (syshandler), en wisselen registerstatus uit met de Sentry via gedeeld geheugen en SYS_FUTEX.
  • Een stub-syscallThread (syscall_thread.go): Voert adresruimte-systeemaanroepen (mmap, munmap, clone) uit binnen het stub-subproces namens de Sentry.

In upstream gVisor ondersteunt syscallThread twee wekmodi in stub_amd64.s en stub_arm64.s:

  1. Seccomp User-Notify-lus (_RUN_SECCOMP_LOOP = 6, wanneer t.seccompNotify != nil): Tijdens syscallThread.init() installeert t.installSeccompNotify() een SECCOMP_RET_USER_NOTIF-listener (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)), zodat de Sentry verzoeken kan verzenden via ioctl(SECCOMP_IOCTL_NOTIF_SEND) en ioctl(SECCOMP_IOCTL_NOTIF_RECV).
  2. Futex-systeemaanroeplus (_RUN_SYSCALL_LOOP = 5, wanneer t.seccompNotify == nil): De syscallThread voert syscall_loop uit (stub_amd64.s / stub_arm64.s), en slaapt op SYS_FUTEX (FUTEX_WAIT) over het gedeelde 2-pagina's syscallSentryMessage-gebied (PROT_READ in de stub) en syscallStubMessage-gebied (PROT_READ | PROT_WRITE in de stub).

Upstream gVisor roept t.installSeccompNotify() onvoorwaardelijk aan wanneer seccompNotifyIsSupported waar is, en breekt het aanmaken van de stub af als seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, ...) EBUSY retourneert.

In gvx (pkg/sentry/platform/systrap/seccomp_notify_xplat.go en syscall_thread.go) roept syscallThread.init() t.installSeccompNotifyIfFree() aan:

 1// pkg/sentry/platform/systrap/seccomp_notify_xplat.go
 2var seccompNotifyBusy atomic.Bool
 3
 4func (t *syscallThread) installSeccompNotifyIfFree() (*os.File, error) {
 5	if seccompNotifyBusy.Load() {
 6		return nil, nil
 7	}
 8	f, err := t.installSeccompNotify()
 9	if errors.Is(err, unix.EBUSY) {
10		if seccompNotifyBusy.CompareAndSwap(false, true) {
11			log.Warningf("Another seccomp listener exists in the filter chain of the host process. " +
12				"The stub threads use the slower futex loop instead of seccomp-unotify.")
13		}
14		return nil, nil
15	}
16	return f, err
17}

Wanneer installSeccompNotify() unix.EBUSY retourneert omdat een buitenste gVisor (runsc) of buitenste gvx-sandbox de enkele listener-slot van de filterketen van het proces al bezet houdt:

  • installSeccompNotifyIfFree() zet seccompNotifyBusy (atomic.Bool), logt één waarschuwing voor het proces, en laat t.seccompNotify == nil voor die stub en alle volgende stubs.
  • In syscallThread.detach() (syscall_thread_amd64.go en syscall_thread_arm64.go) zet t.seccompNotify == nil regs.Rbx = _RUN_SYSCALL_LOOP (5) op amd64 (of regs.Regs[9] = _RUN_SYSCALL_LOOP op arm64) in plaats van _RUN_SECCOMP_LOOP (6).
  • In syscallThread.syscall() (syscall_thread.go) verstuurt de interne Sentry stub-systeemaanroepen uitsluitend via gedeeld geheugen en SYS_FUTEX:
1// pkg/sentry/platform/systrap/syscall_thread.go
2// Breng de syscall-thread op de hoogte van een nieuw syscall-verzoek.
3atomic.AddUint32(&sentryMsg.state, 1)
4futexWakeUint32(&sentryMsg.state)
5
6// Wacht op antwoord.
7futexWaitWake(&sentryMsg.state, atomic.LoadUint32(&sentryMsg.state))

Omdat SECCOMP_RET_TRAP-filters op gastthreads willekeurig diep in de stack werken en syscallThread terugvalt op _RUN_SYSCALL_LOOP zonder een tweede user-notification-listener aan te vragen, draait gvx probleemloos binnen runsc en ondersteunt het recursieve nesting (gvx in gvx).

2. Native overlayfs op ext4lite-volumes met één bestand

Om te voorkomen dat overlayfs wordt gemount bovenop het gofer (9p/lisafs) root-bestandssysteem van de buitenste gVisor-container, ondersteunt -v gvx-docker:/var/lib/docker /var/lib/docker met het interne ext4lite-bestandssysteem van gvx, opgeslagen in één regulier bestand (gvx-docker.ext4).

Voor de buitenste runsc is gvx-docker.ext4 slechts één regulier bestand dat wordt benaderd via pread64(2) en pwrite64(2). Binnen gvx biedt ext4lite een door JBD2 gejournaled ext4-bestandssysteem met trusted.overlay.*- en security.capability-extended attributes, character device whiteout-nodes (0:0), d_type-directory-entries en RENAME_EXCHANGE, waardoor de interne dockerd zijn native overlayfs-opslagdriver kan gebruiken.

3. Zelfstandige netstack met docker0 en iptables-legacy

Binnen gvx communiceert dockerd niet met de netwerkinterfaces of netfilter-tabellen van de buitenste runsc-container. In plaats daarvan:

  • dockerd maakt zijn docker0-bridge en veth-paren aan binnen de eigen user-space netstack van gvx.
  • Alle iptables-legacy-regels (-t nat -N DOCKER, MASQUERADE, conntrack, addrtype) worden uitgevoerd tegen de interne netfilter-implementatie van gvx.
  • Geneste runc-containers mounten hun /proc-, /sys- en /sys/fs/cgroup-hiërarchieën binnen de in-memory VFS2-mounttabel van gvx, zonder ooit de mount(2)-systeemaanroep van de buitenste runsc aan te roepen.

Geneste docker:dind binnen gVisor (of gvx in gvx) verifiëren

U kunt beide scenario's (rungv binnen runsc of rungv binnen rungv) verifiëren met de volgende commando's:

 1# Binnen een buitenste gVisor (runsc)-container of een buitenste rungv-container:
 2mkdir -p ~/.gvx
 3rungv run \
 4  -p unix:~/.gvx/docker.sock:2375 \
 5  -v gvx-docker:/var/lib/docker \
 6  -e DOCKER_TLS_CERTDIR= \
 7  docker:dind --tls=false &
 8DIND_PID=$!
 9
10export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
11until docker info >/dev/null 2>&1; do sleep 0.5; done
12
13# Controleer dat de interne Docker-daemon native overlayfs gebruikt (niet vfs)
14docker info --format 'Storage Driver: {{.Driver}}'
15# Uitvoer: Storage Driver: overlayfs
16
17# Voer een build met meerdere lagen uit en start een geneste container
18mkdir -p /tmp/nested-test && cd /tmp/nested-test
19cat <<'EOF' > Dockerfile
20FROM alpine:3.20
21RUN adduser -D -u 1001 appuser && mkdir -p /app && chown appuser:appuser /app
22USER appuser
23WORKDIR /app
24RUN echo "nested-build-ok" > status.txt
25CMD ["cat", "/app/status.txt"]
26EOF
27
28docker build -t nested-test:latest .
29docker run --rm nested-test:latest
30# Uitvoer: nested-build-ok
31
32kill -INT $DIND_PID

Verder lezen in deze reeks

  1. gvx: Linux-containers, Docker en Kubernetes uitvoeren in één niet-geprivilegieerd proces: Overzicht van gvx op Windows (whpx, systrap), macOS (hvf) en Linux (systrap).
  2. Docker, docker build en docker compose uitvoeren zonder root of user namespaces: Hoe gvx docker:dind uitvoert wanneer root, sudo, unshare(CLONE_NEWUSER), mount(2) en /dev/fuse zijn geblokkeerd.
  3. Docker-in-Docker binnen gVisor en geneste sandboxes uitvoeren met gvx (dit bericht)
  4. Snelle geïsoleerde Docker-omgevingen voor RL-rollouts van AI-agents met gvx: Hoe u gvx en enkelvoudige ext4lite-copy-on-write-snapshots gebruikt om honderden parallelle SWE-bench- en RL-rolloutomgevingen uit te voeren met resets van 100 milliseconden.