Kørsel af Docker-in-Docker inde i gVisor og indlejrede Sandboxes med gvx

Hurtig løsning: Sikker kørsel af docker:dind i en uprivilegeret gVisor (runsc) sandbox
Cloud AI-agentplatforme, Kubernetes agent-sandbox-deployments (RuntimeClass: gvisor) og evalueringsharnesser til forstærkningslæring isolerer utroværdige workloads inde i gVisor (runsc)-containere specifikt for at beskytte værtens kerne. Når en workload inde i en standard uprivilegeret runsc-container forsøger at installere docker.io eller docker-ce via apt-get og starte dockerd, fejler den, selv hvis den har root (UID 0) inde i containeren.
Med gvx (rungv) kan du sikkert køre docker:dind, docker build og docker compose direkte inde i en uprivilegeret gVisor (runsc)-container (eller indlejre gvx i gvx) uden at svække den ydre RuntimeClass, eksponere værtssockets eller give --privileged.
Bevarelse og styrkelse af den ydre sandbox-isolation: At køre
rungvinde irunscomgår ikke en eneste begrænsning håndhævet af den ydregVisor-sandbox. I stedet oprettergvxen anden, indlejret brugerrumsbaseret gVisorSentry-kerne med sin egenhostlockdown(PR_SET_NO_NEW_PRIVS+seccomp), sin egen in-memorynetstackog en enkelt-filext4lite-volume. Indredockerd- ogrunc-containere er dobbelt sandboxede: deres systemkald, monteringer og rå pakker forlader aldrig den indregvx-brugerrumskerne.
1# Inde i en eksisterende gVisor (runsc)-container eller Kubernetes Pod:
2# Installer rungv fra et SLSA-attesteret GitHub release-arkiv eller 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# Kør standard Docker-builds og containere mod den indre 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# Output: overlayfs Alpine Linux ... (containerized)
17
18docker build -t nested-app:test .
19docker run --rm nested-app:test
Fem grunde til, hvorfor direkte Docker-installation fejler inde i gVisor (runsc)
Når en bruger eller en AI-kodningsagent kører apt-get install -y docker.io (eller docker-ce) inde i en standard runsc-container og forsøger at starte dockerd, rammer den fem sekventielle tekniske barrierer.
1. Kernemodul-indlæsning (modprobe) mod virtuel kerne 4.4.0
Under pakkeinstallation og opstart af dockerd kører initialiseringsscripts modprobe for at indlæse overlay, br_netfilter, ip_tables, nf_nat og xt_conntrack. Den ydre gVisor Sentry blokerer init_module(2) og finit_module(2) og rapporterer en virtuel kerneversion (4.4.0), som ikke har en /lib/modules/4.4.0-mappe:
1modprobe: FATAL: Module overlay not found in directory /lib/modules/4.4.0
2modprobe: FATAL: Module br_netfilter not found in directory /lib/modules/4.4.0
3modprobe: FATAL: Module ip_tables not found in directory /lib/modules/4.4.0
4modprobe: FATAL: Module nf_nat not found in directory /lib/modules/4.4.0
2. iptables-nft (nf_tables Netlink) og docker0-bridgefejl
På Ubuntu 22.04, Ubuntu 24.04 og Debian 11/12 vælger apt-get install docker.io iptables-nft (understøttet af nf_tables netlink-undersystemet) som standardalternativet /usr/sbin/iptables. I en standard gVisor-container uden netværksadministratorrettigheder eller nf_tables netlink-understøttelse crasher dockerd, mens den initialiserer DOCKER NAT-kæden og docker0-bridgen:
1failed to start daemon: Error initializing network controller: error obtaining controller instance: failed to create NAT chain DOCKER: iptables failed: iptables -t nat -N DOCKER
2iptables v1.8.10 (nf_tables): Could not fetch rule set generation id: Invalid argument
3iptables v1.8.10 (legacy): can't initialize iptables table 'nat': Table does not exist (do you need to insmod?)
4iptables: No chain/target/match by that name.
5Unable 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.)
6Error initializing network controller: error obtaining controller instance: failed to create bridge "docker0": operation not permitted
3. overlay2-fejl på gofer / 9p-rootfs og vfs-diskudmattelse
Medmindre den ydre runsc-administrator udtrykkeligt har forkonfigureret en dedikeret tmpfs- eller filestore-montering på /var/lib/docker, er containerens rootfs understøttet af gVisors gofer (9p/lisafs) eller en indlejret overlay-montering. Montering af overlay2 oven på /var/lib/docker fejler:
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
Når brugere eller agenter forsøger at omgå denne fejl ved at starte dockerd --storage-driver=vfs, deaktiverer Docker fuldstændigt deling af Copy-on-Write-lag. Hver RUN-instruktion i en Dockerfile udfører en fuld rekursiv kopiering (dirCopy) af hele root-filsystemet, hvilket multiplicerer et 500 MB image over 10 build-trin til 5 GB skrivninger og får sandboxen til at afslutte med no space left on device eller en I/O-timeout.
4. Indlejrede runc cgroup- og /proc-monteringsafvisninger
Selv hvis dockerd tvinges til at starte med dockerd --iptables=false --bridge=none --storage-driver=vfs, kalder docker run eller docker build runc for at oprette den indre container. I en standard uprivilegeret gVisor-container fejler runc, når den monterer cgroups, gen-monterer /proc eller indlæser indre seccomp-filtre:
1docker: 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:
2error 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
3cgroups: cgroup mountpoint does not exist: unknown
4runc create failed: systemd not running on this host: cannot use systemd cgroup manager
5error during container init: error mounting "proc" to rootfs at "/proc": mount proc:/proc (via /proc/self/fd/6), flags: 0xe: operation not permitted
6error during container init: error loading seccomp filter into kernel: operation not permitted
5. Indlejret runsc fejler med SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY)
Hvis en bruger forsøger at køre en anden runsc-instans inde i runsc ved hjælp af systrap-platformen, afbrydes initialiseringen straks:
1creating subprocess: sysmsg: failed to initialize thread: failed to install seccomp notify rules: device or resource busy
Både Linux-kernen og gVisor Sentry tillader højst én SECCOMP_FILTER_FLAG_NEW_LISTENER (SECCOMP_RET_USER_NOTIF)-lytter pr. task-hierarki. Fordi den ydre sandbox allerede har den lytterplads, returnerer det indre seccomp(2)-kald EBUSY (device or resource busy).
Hvordan gvx løser indlejret eksekvering inde i gVisor
gvx adresserer alle fem barrierer inde i sin egen brugerrumskerne og platformslag, hvilket gør det muligt for rungv at køre inde i en ydre gVisor (runsc)-container eller endda rekursivt inde i en anden gvx-container (gvx i gvx).

1. Automatisk systrap-fallback fra EBUSY til _RUN_SYSCALL_LOOP futex-løkken
I Linux systrap kører hver stub-underproces to slags tråde:
- Gæsteeksekveringstråde (
sysmsg): Fanger gæstesystemkald viaSECCOMP_RET_TRAP(SIGSYS) og binært patchedejmp rel32-trampolines (syshandler), og udveksler registertilstand med Sentry over delt hukommelse ogSYS_FUTEX. - En stub-
syscallThread(syscall_thread.go): Udfører adresserumssystemkald (mmap,munmap,clone) inde i stub-underprocessen på vegne af Sentry.
I upstream gVisor understøtter syscallThread to vækningstilstande i stub_amd64.s og stub_arm64.s:
- Seccomp User-Notify-løkke (
_RUN_SECCOMP_LOOP = 6, nårt.seccompNotify != nil): UndersyscallThread.init()installerert.installSeccompNotify()enSECCOMP_RET_USER_NOTIF-lytter (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)), så Sentry kan sende forespørgsler viaioctl(SECCOMP_IOCTL_NOTIF_SEND)ogioctl(SECCOMP_IOCTL_NOTIF_RECV). - Futex-systemkaldsløkke (
_RUN_SYSCALL_LOOP = 5, nårt.seccompNotify == nil):syscallThreadkørersyscall_loop(stub_amd64.s/stub_arm64.s) og sover påSYS_FUTEX(FUTEX_WAIT) over den delte 2-siderssyscallSentryMessage(PROT_READi stubben) ogsyscallStubMessage(PROT_READ | PROT_WRITEi stubben) region.
Upstream gVisor kalder ubetinget t.installSeccompNotify(), når seccompNotifyIsSupported er sand, og afbryder stub-oprettelsen, hvis seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, ...) returnerer EBUSY.
I gvx (pkg/sentry/platform/systrap/seccomp_notify_xplat.go og syscall_thread.go) kalder syscallThread.init() t.installSeccompNotifyIfFree():
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}
Når installSeccompNotify() returnerer unix.EBUSY, fordi en ydre gVisor (runsc) eller ydre gvx-sandbox allerede holder procesfilterkædens enkelte lytterplads:
installSeccompNotifyIfFree()sætterseccompNotifyBusy(atomic.Bool), logger én advarsel for processen og ladert.seccompNotify == nilfor denne stub og alle efterfølgende stubs.- I
syscallThread.detach()(syscall_thread_amd64.googsyscall_thread_arm64.go) sættert.seccompNotify == nilregs.Rbx = _RUN_SYSCALL_LOOP(5) påamd64(ellerregs.Regs[9] = _RUN_SYSCALL_LOOPpåarm64) i stedet for_RUN_SECCOMP_LOOP(6). - I
syscallThread.syscall()(syscall_thread.go) sender den indre Sentry stub-systemkald udelukkende via delt hukommelse ogSYS_FUTEX:
1// pkg/sentry/platform/systrap/syscall_thread.go
2// Underret systemkaldstråden om en ny systemkaldsforespørgsel.
3atomic.AddUint32(&sentryMsg.state, 1)
4futexWakeUint32(&sentryMsg.state)
5
6// Vent på svar.
7futexWaitWake(&sentryMsg.state, atomic.LoadUint32(&sentryMsg.state))
Fordi SECCOMP_RET_TRAP-filtre på gæstetråde stables vilkårligt dybt, og syscallThread falder tilbage til _RUN_SYSCALL_LOOP uden at anmode om en anden brugerunderretningslytter, kører gvx rent inde i runsc og understøtter rekursiv indlejring (gvx i gvx).
2. Native overlayfs på enkelt-fil ext4lite-volumener
For at undgå at montere overlayfs oven på den ydre gVisor-containers gofer (9p/lisafs) root-filsystem, forankrer -v gvx-docker:/var/lib/docker /var/lib/docker i gvxs interne ext4lite-filsystem, lagret i en enkelt almindelig fil (gvx-docker.ext4).
For den ydre runsc er gvx-docker.ext4 blot én almindelig fil, tilgået via pread64(2) og pwrite64(2). Inde i gvx leverer ext4lite et JBD2-journaliseret ext4-filsystem med trusted.overlay.*- og security.capability-udvidede attributter, character device-whiteout-noder (0:0), d_type-mappeposter og RENAME_EXCHANGE, hvilket gør det muligt for den indre dockerd at bruge sin native overlayfs-storage driver.
3. Selvstændig netstack med docker0 og iptables-legacy
Inde i gvx interagerer dockerd ikke med den ydre runsc-containers netværksgrænseflader eller netfilter-tabeller. I stedet:
dockerdopretter sindocker0-bridge ogveth-par inde igvxs egen brugerrums-netstack.- Alle
iptables-legacy-regler (-t nat -N DOCKER,MASQUERADE,conntrack,addrtype) udføres modgvxs interne netfilter-implementering. - Indlejrede
runc-containere monterer deres/proc-,/sys- og/sys/fs/cgroup-hierarkier inde igvxs in-memoryVFS2-monteringstabel og kalder aldrig det ydrerunscmount(2)-systemkald.
Verificering af indlejret docker:dind inde i gVisor (eller gvx i gvx)
Du kan verificere begge scenarier (rungv inde i runsc, eller rungv inde i rungv) med følgende kommandoer:
1# Inde i en ydre gVisor (runsc)-container eller en ydre 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# Verificer at den indre Docker-daemon bruger native overlayfs (ikke vfs)
14docker info --format 'Storage Driver: {{.Driver}}'
15# Output: Storage Driver: overlayfs
16
17# Kør et multi-lags build og eksekver en indlejret 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# Output: nested-build-ok
31
32kill -INT $DIND_PID
Yderligere læsning i denne serie
- gvx: Kørsel af Linux-containere, Docker og Kubernetes i én uprivilegeret proces: Oversigt over
gvxpå tværs af Windows (whpx,systrap), macOS (hvf) og Linux (systrap). - Kørsel af Docker, docker build og docker compose uden root eller brugernavnerum: Hvordan
gvxkørerdocker:dind, nårroot,sudo,unshare(CLONE_NEWUSER),mount(2)og/dev/fuseer blokeret. - Kørsel af Docker-in-Docker inde i gVisor og indlejrede sandboxes med gvx (dette indlæg)
- Hurtige isolerede Docker-miljøer til AI-agent RL-rollouts med gvx: Hvordan man bruger
gvxog enkelt-filext4litecopy-on-write snapshots til at køre hundredvis af parallelleSWE-bench- og RL-rollout-miljøer med 100-millisekunders nulstillinger.