Spouštění Docker-in-Docker uvnitř gVisor a Nested Sandboxes pomocí gvx

Rychlé řešení: Bezpečné spuštění docker:dind uvnitř neprivilegovaného sandboxu gVisor (runsc)
Cloudová výpočetní prostředí AI agentů (například sandboxy Google Agent Compute Engine), nasazení agent-sandbox v Kubernetes (RuntimeClass: gvisor) a evaluační prostředí pro reinforcement learning izolují nedůvěryhodné úlohy uvnitř kontejnerů gVisor (runsc), konkrétně za účelem ochrany jádra hostitele. Když se úloha uvnitř výchozího neprivilegovaného kontejneru runsc pokusí nainstalovat docker.io nebo docker-ce pomocí apt-get, spustit service docker start nebo dockerd a provést docker build, selže, a to i v případě, že má uvnitř kontejneru root (UID 0).
S gvx (rungv) můžete bezpečně spustit docker:dind, docker build a docker compose přímo uvnitř neprivilegovaného kontejneru gVisor (runsc) (nebo vnořit gvx do gvx) bez oslabení vnějšího RuntimeClass, bez zpřístupnění hostitelských socketů a bez udělení --privileged.
Řešení s nízkou náročností a úsporou tokenů pro AI agenty: Uvnitř cloudového agentního prostředí chráněného gVisor se pokus o instalaci
docker.ioa ruční obcházení chybulimitv/etc/init.d/docker, selháníiptables-nft, odmítnutí raw socketů uiptables-legacy, chyby při mountováníoverlay2a pádyrunc/pivot_rootpřidocker buildtypicky spotřebuje 20+ tool-call kroků a desítky tisíc tokenů, než zcela selže.gvx(rungv) je řešení s nízkou náročností a úsporou tokenů, které lze nasadit přímo: agent spustí 3 příkazy v jediném kroku, aby nastartoval funkční uživatelský Docker daemon, a okamžitě provededocker buildadocker composebez úprav projektových souborů.
Zachování a posílení izolace vnějšího sandboxu: Spuštění
rungvuvnitřrunscneobchází jediné omezení vynucované vnějším sandboxemgVisor. Místo tohogvxvytváří druhé, vnořené uživatelské jádroSentrygVisor s vlastnímhostlockdown(PR_SET_NO_NEW_PRIVS+seccomp), vlastním paměťovýmnetstacka svazkemext4litev podobě jediného souboru. Vnitřnídockerda kontejneryruncjsou dvojitě izolovány: jejich systémová volání, připojení souborových systémů a surové pakety nikdy neopustí vnitřní uživatelské jádrogvx.
1# Uvnitř existujícího kontejneru gVisor (runsc) nebo Kubernetes Pod:
2# Nainstalujte rungv z archivu vydání na GitHubu attestovaného SLSA nebo pomocí 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# Spusťte standardní Docker buildy a kontejnery proti vnitřnímu socketu gvx
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# Výstup: overlayfs Alpine Linux ... (containerized)
17
18docker build -t nested-app:test .
19docker run --rm nested-app:test
Pět důvodů, proč přímá instalace Dockeru selhává uvnitř gVisor (runsc)
Když uživatel nebo AI agent pro kódování spustí apt-get install -y docker.io (nebo docker-ce) uvnitř standardního kontejneru runsc a pokusí se spustit dockerd, narazí na pět po sobě jdoucích technických překážek.
1. Selhání init skriptu (systemctl / service docker start ulimit) a kernel modulu (modprobe 4.4.0)
Po spuštění apt-get install -y docker.io (nebo docker-ce) selže pokus o spuštění démona přes systemctl start docker nebo service docker start okamžitě, protože PID 1 není systemd a /etc/init.d/docker se pokouší zvýšit ulimit -n nad tvrdý strop RLIMIT_NOFILE sandboxu. Když je pak dockerd vyvolán přímo, inicializační skripty volají modprobe, aby načetly overlay, bridge, br_netfilter, ip_tables a nf_nat proti virtuální verzi jádra gVisor (4.4.0), která nemá adresář /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. Odmítnutí iptables-nft (netlink nf_tables), odmítnutí raw socketů iptables-legacy a selhání mostu docker0
Na Ubuntu 22.04, Ubuntu 24.04 a Debianu 11/12 vybere apt-get install docker.io jako výchozí alternativu /usr/sbin/iptables nástroj iptables-nft (založený na netlink subsystému nf_tables). Navíc i když uživatel nebo agent přepne na iptables-legacy (update-alternatives --set iptables /usr/sbin/iptables-legacy), výchozí neprivilegované sandboxy gVisor (--net-raw=false) odmítnou sockety AF_INET, SOCK_RAW s EPERM, což způsobí selhání iptables-legacy s Permission denied (you must be root) i při běhu jako 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. Selhání overlay2 na rootfs gofer / 9p a vyčerpání disku v režimu vfs
Pokud vnější správce runsc výslovně předem nenakonfiguroval vyhrazený připojený tmpfs nebo filestore v /var/lib/docker, je rootfs kontejneru podložen gofer gVisoru (9p/lisafs) nebo vnořeným overlay připojením. Připojení overlay2 nad /var/lib/docker selže:
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
Když se uživatelé nebo agenti pokusí tuto chybu obejít spuštěním dockerd --storage-driver=vfs, Docker zcela vypne sdílení vrstev Copy-on-Write. Každá instrukce RUN v Dockerfile provede úplnou rekurzivní kopii (dirCopy) celého kořenového souborového systému, takže obraz o velikosti 500 MB napříč 10 kroky buildu znamená 5 GB zápisů, což ukončí sandbox chybou no space left on device nebo timeoutem I/O.
4. Selhání docker build (BuildKit) a vnořeného runc (pivot_root, /proc, cgroupy a DNS)
I když je dockerd vynuceně spuštěn s dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs, pokus o spuštění docker build -t app . nebo docker run okamžitě selže, když BuildKit nebo runc spouští build kontejner:
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. Vnořený runsc selhává s SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY)
Pokud se uživatel pokusí spustit další instanci runsc uvnitř runsc s platformou systrap, inicializace se okamžitě přeruší:
1creating subprocess: sysmsg: failed to initialize thread: failed to install seccomp notify rules: device or resource busy
Jak jádro Linuxu, tak Sentry gVisoru povolují nejvýše jeden listener SECCOMP_FILTER_FLAG_NEW_LISTENER (SECCOMP_RET_USER_NOTIF) na hierarchii úloh. Protože vnější sandbox již tento slot listeneru drží, vnitřní volání seccomp(2) vrací EBUSY (device or resource busy).
Jak gvx řeší vnořené spouštění uvnitř gVisor
gvx řeší všech pět překážek uvnitř vlastního uživatelského jádra a platformní vrstvy, což umožňuje rungv běžet uvnitř vnějšího kontejneru gVisor (runsc) nebo dokonce rekurzivně uvnitř dalšího kontejneru gvx (gvx v gvx).

1. Automatické přepnutí systrap z EBUSY na futex smyčku _RUN_SYSCALL_LOOP
V systrap na Linuxu běží každý podprocess stubu se dvěma typy vláken:
- Vlákna provádění hosta (
sysmsg): zachytávají systémová volání hosta přesSECCOMP_RET_TRAP(SIGSYS) a binárně upravené trampolínyjmp rel32(syshandler), přičemž si s Sentry předávají stav registrů přes sdílenou paměť aSYS_FUTEX. - Vlákno stubu
syscallThread(syscall_thread.go): Provádí systémová volání správy adresního prostoru (mmap,munmap,clone) uvnitř podprocesu stubu jménem Sentry.
V upstreamovém gVisoru podporuje syscallThread dva režimy probuzení v stub_amd64.s a stub_arm64.s:
- Smyčka seccomp User-Notify (
_RUN_SECCOMP_LOOP = 6, kdyžt.seccompNotify != nil): BěhemsyscallThread.init()instalujet.installSeccompNotify()listenerSECCOMP_RET_USER_NOTIF(seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)), aby mohl Sentry odesílat požadavky přesioctl(SECCOMP_IOCTL_NOTIF_SEND)aioctl(SECCOMP_IOCTL_NOTIF_RECV). - Smyčka systémových volání futex (
_RUN_SYSCALL_LOOP = 5, kdyžt.seccompNotify == nil):syscallThreadspouštísyscall_loop(stub_amd64.s/stub_arm64.s) a uspává se naSYS_FUTEX(FUTEX_WAIT) nad sdílenou oblastí 2 stráneksyscallSentryMessage(PROT_READve stubu) asyscallStubMessage(PROT_READ | PROT_WRITEve stubu).
Upstreamový gVisor bezpodmínečně volá t.installSeccompNotify() vždy, když je seccompNotifyIsSupported pravda, a přerušuje vytvoření stubu, pokud seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, ...) vrátí EBUSY.
V gvx (pkg/sentry/platform/systrap/seccomp_notify_xplat.go a syscall_thread.go) volá 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}
Když installSeccompNotify() vrátí unix.EBUSY, protože vnější gVisor (runsc) nebo vnější sandbox gvx již drží jediný slot listeneru v řetězu filtrů procesu:
installSeccompNotifyIfFree()nastavíseccompNotifyBusy(atomic.Bool), zaloguje jedno varování pro proces a ponechát.seccompNotify == nilpro tento stub i všechny následující stuby.- V
syscallThread.detach()(syscall_thread_amd64.goasyscall_thread_arm64.go) nastavít.seccompNotify == nilhodnoturegs.Rbx = _RUN_SYSCALL_LOOP(5) naamd64(neboregs.Regs[9] = _RUN_SYSCALL_LOOPnaarm64) namísto_RUN_SECCOMP_LOOP(6). - V
syscallThread.syscall()(syscall_thread.go) vnitřní Sentry rozesílá systémová volání stubu výhradně přes sdílenou paměť aSYS_FUTEX:
1// pkg/sentry/platform/systrap/syscall_thread.go
2// Upozorněte vlákno systémových volání na nový požadavek na systémové volání.
3atomic.AddUint32(&sentryMsg.state, 1)
4futexWakeUint32(&sentryMsg.state)
5
6// Počkejte na odpověď.
7futexWaitWake(&sentryMsg.state, atomic.LoadUint32(&sentryMsg.state))
Protože filtry SECCOMP_RET_TRAP procházejí zásobník vláken hosta libovolně hluboko a syscallThread se vrací k _RUN_SYSCALL_LOOP, aniž by požádal o druhý listener uživatelských notifikací, gvx běží čistě uvnitř runsc a podporuje rekurzivní vnoření (gvx uvnitř gvx).
2. Nativní overlayfs na svazcích ext4lite v podobě jediného souboru
Aby se předešlo připojení overlayfs nad kořenový souborový systém gofer (9p/lisafs) vnějšího kontejneru gVisor, -v gvx-docker:/var/lib/docker podkládá /var/lib/docker interním souborovým systémem ext4lite v gvx, uloženým v jediném běžném souboru (gvx-docker.ext4).
Pro vnější runsc je gvx-docker.ext4 pouze jeden běžný soubor přistupovaný přes pread64(2) a pwrite64(2). Uvnitř gvx poskytuje ext4lite souborový systém ext4 s žurnálem JBD2, rozšířené atributy trusted.overlay.* a security.capability, whiteout uzly znakových zařízení (0:0), položky adresáře d_type a RENAME_EXCHANGE, což umožňuje vnitřnímu dockerd použít jeho nativní ovladač úložiště overlayfs.
3. Samostatný netstack s docker0 a iptables-legacy
Uvnitř gvx neinteraguje dockerd s síťovými rozhraními ani netfilter tabulkami vnějšího kontejneru runsc. Místo toho:
dockerdvytváří svůj mostdocker0a páryvethuvnitř vlastního uživatelskéhonetstackvgvx.- Všechna pravidla
iptables-legacy(-t nat -N DOCKER,MASQUERADE,conntrack,addrtype) se provádějí proti interní implementaci netfilteru vgvx. - Vnořené kontejnery
runcpřipojují své hierarchie/proc,/sysa/sys/fs/cgroupuvnitř tabulky připojeníVFS2v pamětigvxa nikdy nevolají systémové volánímount(2)vnějšíhorunsc.
Ověření vnořeného docker:dind uvnitř gVisor (nebo gvx v gvx)
Oba scénáře (rungv uvnitř runsc nebo rungv uvnitř rungv) můžete ověřit následujícími příkazy:
1# Uvnitř vnějšího kontejneru gVisor (runsc) nebo vnějšího kontejneru rungv:
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# Ověřte, že vnitřní Docker daemon používá nativní overlayfs (nikoli vfs)
14docker info --format 'Storage Driver: {{.Driver}}'
15# Výstup: Storage Driver: overlayfs
16
17# Spusťte vícevrstvý build a proveďte vnořený kontejner
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# Výstup: nested-build-ok
31
32kill -INT $DIND_PID
Další čtení v této sérii
- gvx: Spouštění linuxových kontejnerů, Dockeru a Kubernetes v jednom neprivilegovaném procesu: Přehled
gvxnapříč Windows (whpx,systrap), macOS (hvf) a Linuxem (systrap). - Spouštění Dockeru, docker build a docker compose bez rootu nebo uživatelských jmenných prostorů: Jak
gvxspouštídocker:dind, když jsou blokoványroot,sudo,unshare(CLONE_NEWUSER),mount(2)a/dev/fuse. - Docker-in-Docker uvnitř gVisoru a vnořených sandboxů s gvx (tento příspěvek)
- Rychlá izolovaná Docker prostředí pro RL rollouty AI agentů s gvx: Jak používat
gvxa jednosouborové snímkyext4lites copy-on-write k provozování stovek paralelních prostředí SWE-bench a RL rollout s resetem za 100 milisekund.