Kjøring av Docker-in-Docker inne i gVisor og nestede Sandboxes med gvx

Hurtigløsning: Sikker kjøring av docker:dind i en uprivilegert gVisor (runsc)-sandkasse
Skybaserte AI-agentberegningsmiljøer (for eksempel Google Agent Compute Engine-sandkasser), Kubernetes agent-sandbox-distribusjoner (RuntimeClass: gvisor) og evalueringsrammeverk for forsterkningslæring isolerer upålitelige arbeidslaster i gVisor (runsc)-containere for å beskytte vertskjernen. Når en arbeidslast i en standard uprivilegert runsc-container forsøker å installere docker.io eller docker-ce via apt-get, starte service docker start eller dockerd og kjøre docker build, mislykkes den selv om den har root (UID 0) inne i containeren.
Med gvx (rungv) kan du sikkert kjøre docker:dind, docker build og docker compose direkte inne i en uprivilegert gVisor (runsc)-container (eller nøste gvx inne i gvx) uten å svekke den ytre RuntimeClass, eksponere vertssockets eller gi --privileged.
Lavinnsats- og tokeneffektiv løsning for AI-agenter: Inne i et gVisor-basert skymiljø for agenter vil forsøk på å installere
docker.ioog manuelt omgåulimit-feil i/etc/init.d/docker,iptables-nft-feil,iptables-legacy-avvisninger av råsockets,overlay2-monteringsfeil ogrunc- /pivot_root-krasj underdocker buildtypisk bruke over 20 verktøykall-runder og titusenvis av tokens før det til slutt mislykkes helt.gvx(rungv) er en lavinnsats- og tokeneffektiv drop-in-løsning: agenten kjører 3 kommandoer i én runde for å starte en fungerende Docker-daemon i brukerrom og kjører umiddelbartdocker buildogdocker composeuten å endre prosjektfiler.
Bevaring og styrking av isolasjonen i den ytre sandkassen: Å kjøre
rungvinne irunscomgår ikke en eneste begrensning som håndheves av den ytre gVisor-sandkassen. I stedet opprettergvxen andre, nøstet brukerrom-gVisorSentry-kjerne med sin egenhostlockdown(PR_SET_NO_NEW_PRIVS+seccomp), sitt eget minnebasertenetstackog et enkeltfil-ext4lite-volum. Indredockerd- ogrunc-containere er dobbeltsandkassert: systemkallene, monteringene og rå pakkene deres forlater aldri den indregvx-kjernen i brukerrom.
1# Inne i en eksisterende gVisor (runsc)-container eller Kubernetes-pod:
2# Installer rungv fra et SLSA-attestert GitHub-utgivelsesarkiv 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# Kjør standard Docker-bygg og -containere mot den indre gvx-socketen
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# Utdata: overlayfs Alpine Linux ... (containerisert)
17
18docker build -t nested-app:test .
19docker run --rm nested-app:test
Fem grunner til at direkte Docker-installasjon mislykkes inne i gVisor (runsc)
Når en bruker eller en AI-kodeagent kjører apt-get install -y docker.io (eller docker-ce) inne i en standard runsc-container og forsøker å starte dockerd, møter den fem påfølgende tekniske barrierer.
1. Feil i init-skript (systemctl / service docker start ulimit) og kjernemoduler (modprobe 4.4.0)
Etter å ha kjørt apt-get install -y docker.io (eller docker-ce), mislykkes forsøket på å starte daemonen via systemctl start docker eller service docker start umiddelbart, fordi PID 1 ikke er systemd, og /etc/init.d/docker forsøker å heve ulimit -n over sandkassens harde RLIMIT_NOFILE-tak. Når dockerd deretter kalles direkte, kaller initialiseringsskriptene modprobe for å laste overlay, bridge, br_netfilter, ip_tables og nf_nat mot gVisors virtuelle kjerneversjon (4.4.0), som ikke har en katalog /lib/modules/4.4.0:
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), avvisninger av iptables-legacy-råsockets og feil i docker0-broen
På Ubuntu 22.04, Ubuntu 24.04 og Debian 11/12 velger apt-get install docker.io iptables-nft (støttet av nf_tables-netlink-subsystemet) som standardalternativ for /usr/sbin/iptables. Selv om brukeren eller agenten bytter til iptables-legacy (update-alternatives --set iptables /usr/sbin/iptables-legacy), avviser standard uprivilegerte gVisor-sandkasser (--net-raw=false) dessuten AF_INET, SOCK_RAW-sockets med EPERM. Dette gjør at iptables-legacy mislykkes med Permission denied (you must be root), selv når den kjører som UID 0:
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-feil på gofer- / 9p-rootfs og utmattelse av vfs-disk
Med mindre den ytre runsc-administratoren eksplisitt har forhåndskonfigurert en dedikert tmpfs- eller filestore-montering på /var/lib/docker, støttes containerens rootfs av gVisors gofer (9p/lisafs) eller av en nøstet overlay-montering. Montering av overlay2 oppå /var/lib/docker mislykkes:
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 brukere eller agenter forsøker å omgå denne feilen ved å starte dockerd --storage-driver=vfs, deaktiverer Docker deling av Copy-on-Write-lag fullstendig. Hver RUN-instruksjon i en Dockerfile utfører en full rekursiv kopiering (dirCopy) av hele rotfilsystemet. Dette multipliserer et image på 500 MB over 10 byggetrinn til 5 GB med skriving og avslutter sandkassen med no space left on device eller en I/O-tidsavbrudd.
4. docker build (BuildKit) og feil i nøstet runc (pivot_root, /proc, cgroups og DNS)
Selv om dockerd tvinges til å starte med dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs, mislykkes forsøk på å kjøre docker build -t app . eller docker run umiddelbart når BuildKit eller runc starter byggecontaineren:
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. Nøstet runsc mislykkes med SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY)
Hvis en bruker prøver å kjøre en ny runsc-instans inne i runsc ved bruk av systrap-plattformen, avbrytes initialiseringen umiddelbart:
1creating subprocess: sysmsg: failed to initialize thread: failed to install seccomp notify rules: device or resource busy
Både Linux-kjernen og gVisor Sentry tillater høyst én SECCOMP_FILTER_FLAG_NEW_LISTENER-lytter (SECCOMP_RET_USER_NOTIF) per oppgavehierarki. Fordi den ytre sandkassen allerede har den lytterplassen, returnerer det indre seccomp(2)-kallet EBUSY (device or resource busy).
Hvordan gvx løser nøstet kjøring inne i gVisor
gvx adresserer alle fem barrierene inne i sin egen kjerne i brukerrom og i plattformlaget, og gjør det mulig for rungv å kjøre inne i en ytre gVisor (runsc)-container, eller til og med rekursivt inne i en annen gvx-container (gvx i gvx).

1. Automatisk systrap-tilbakefall fra EBUSY til futex-løkken _RUN_SYSCALL_LOOP
I Linux systrap kjører hver stub-underprosess to typer tråder:
- Gjesteutførelsestråder (
sysmsg): Fanger gjestens systemkall viaSECCOMP_RET_TRAP(SIGSYS) og binærpatchedejmp rel32-trampoliner (syshandler), og utveksler registertilstand med Sentry over delt minne ogSYS_FUTEX. - En stub-
syscallThread(syscall_thread.go): Utfører systemkall for adresserommet (mmap,munmap,clone) inne i stub-underprosessen på vegne av Sentry.
I upstream gVisor støtter syscallThread to vekkingsmodi 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)), slik at Sentry kan sende forespørsler viaioctl(SECCOMP_IOCTL_NOTIF_SEND)ogioctl(SECCOMP_IOCTL_NOTIF_RECV). - Futex-løkke for systemkall (
_RUN_SYSCALL_LOOP = 5, nårt.seccompNotify == nil):syscallThreadkjørersyscall_loop(stub_amd64.s/stub_arm64.s) og sover påSYS_FUTEX(FUTEX_WAIT) over den delte 2-sidige regionensyscallSentryMessage(PROT_READi stubben) ogsyscallStubMessage(PROT_READ | PROT_WRITEi stubben).
Upstream gVisor kaller ubetinget t.installSeccompNotify() når som helst seccompNotifyIsSupported er sann, og avbryter opprettelsen av stubben dersom 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) kaller 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 ytre gVisor (runsc)- eller ytre gvx-sandkasse allerede har prosessens filterkjedes eneste lytterplass:
installSeccompNotifyIfFree()setterseccompNotifyBusy(atomic.Bool), logger én advarsel for prosessen og lart.seccompNotify == nilfor den stubben og alle påfølgende stubber.- I
syscallThread.detach()(syscall_thread_amd64.googsyscall_thread_arm64.go) settert.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-systemkall utelukkende gjennom delt minne ogSYS_FUTEX:
1// pkg/sentry/platform/systrap/syscall_thread.go
2// Varsle systemkalltråden om en ny systemkallforespørsel.
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å gjestetråder kan stables vilkårlig dypt, og syscallThread faller tilbake til _RUN_SYSCALL_LOOP uten å be om en andre brukervarsellytter, kjører gvx rent inne i runsc og støtter rekursiv nøsting (gvx i gvx).
2. Native overlayfs på enkeltfil-ext4lite-volumer
For å unngå å montere overlayfs oppå rotfilsystemet gofer (9p/lisafs) til den ytre gVisor-containeren, støtter -v gvx-docker:/var/lib/docker /var/lib/docker med gvxs interne ext4lite-filsystem, lagret i en enkelt vanlig fil (gvx-docker.ext4).
For ytre runsc er gvx-docker.ext4 bare én vanlig fil, som aksesseres via pread64(2) og pwrite64(2). Inne i gvx gir ext4lite et JBD2-journalført ext4-filsystem med utvidede attributter trusted.overlay.* og security.capability, whiteout-noder for tegnenheter (0:0), d_type-katalogoppføringer og RENAME_EXCHANGE, slik at den indre dockerd kan bruke sin opprinnelige overlayfs-lagringsdriver.
3. Selvstendig netstack med docker0 og iptables-legacy
Inne i gvx samhandler ikke dockerd med nettverksgrensesnittene eller netfilter-tabellene til den ytre runsc-containeren. I stedet:
- Oppretter
dockerdsindocker0-bro ogveth-par inne igvxs egennetstacki brukerrom. - Kjøres alle
iptables-legacy-regler (-t nat -N DOCKER,MASQUERADE,conntrack,addrtype) motgvxs interne netfilter-implementasjon. - Monterer nøstede
runc-containere sine/proc-,/sys- og/sys/fs/cgroup-hierarkier igvxs minnebaserteVFS2-monteringstabell, og kaller aldri systemkalletmount(2)i den ytrerunsc.
Verifisering av nøstet docker:dind inne i gVisor (eller gvx i gvx)
Du kan verifisere begge scenarioene (rungv inne i runsc, eller rungv inne i rungv) med følgende kommandoer:
1# Inne i en ytre gVisor (runsc)-container eller en ytre 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# Verifiser at den indre Docker-daemonen bruker native overlayfs (ikke vfs)
14docker info --format 'Storage Driver: {{.Driver}}'
15# Utdata: Storage Driver: overlayfs
16
17# Kjør et bygg med flere lag og utfør en nøstet 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# Utdata: nested-build-ok
31
32kill -INT $DIND_PID
Videre lesning i denne serien
- gvx: Kjøring av Linux-containere, Docker og Kubernetes i én uprivilegert prosess: Oversikt over
gvxpå tvers av Windows (whpx,systrap), macOS (hvf) og Linux (systrap). - Kjøring av Docker, docker build og docker compose uten root eller brukerrom: Hvordan
gvxkjørerdocker:dindnårroot,sudo,unshare(CLONE_NEWUSER),mount(2)og/dev/fuseer blokkert. - Kjøring av Docker-in-Docker inne i gVisor og nøstede sandkasser med gvx (dette innlegget)
- Raske isolerte Docker-miljøer for AI-agenters RL-utrullinger med gvx: Hvordan bruke
gvxog enkeltfil-ext4lite-snapshots med copy-on-write for å kjøre hundrevis av parallelleSWE-bench- og RL-utrullingsmiljøer med tilbakestillinger på 100 millisekunder.