Spúšťanie Docker-in-Docker vnútri gVisor a Nested Sandboxes s gvx

Rýchle riešenie: Bezpečné spúšťanie docker:dind vnútri neprivilegovaného sandboxu gVisor (runsc)
Cloudové platformy pre AI agentov, nasadenia agent-sandbox v Kubernetes (RuntimeClass: gvisor) a evaluačné rámce pre reinforcement learning izolujú nedôveryhodné úlohy v kontajneroch gVisor (runsc), konkrétne na ochranu jadra hostiteľa. Keď sa úloha v predvolenom neprivilegovanom kontajneri runsc pokúsi nainštalovať docker.io alebo docker-ce pomocou apt-get a spustiť dockerd, zlyhá aj vtedy, ak má v kontajneri oprávnenia root (UID 0).
Pomocou gvx (rungv) môžete bezpečne spúšťať docker:dind, docker build a docker compose priamo vnútri neprivilegovaného kontajnera gVisor (runsc) (alebo vnoriť gvx do gvx) bez oslabenia vonkajšej RuntimeClass, bez sprístupnenia hostiteľských socketov a bez udelenia --privileged.
Zachovanie a posilnenie izolácie vonkajšieho sandboxu: Spustenie
rungvvnútrirunscneobchádza ani jedno obmedzenie vynucované vonkajším sandboxomgVisor. Namiesto tohogvxvytvorí druhé, vnorené jadrogVisor Sentryv používateľskom priestore s vlastnýmhostlockdown(PR_SET_NO_NEW_PRIVS+seccomp), vlastnýmnetstackv pamäti a jedným súborovým zväzkomext4lite. Vnútorné kontajnerydockerdaruncsú dvojito sandboxované: ich systémové volania, pripojenia a surové pakety nikdy neopustia vnútorné jadrogvxv používateľskom priestore.
1# Vnútri existujúceho kontajnera gVisor (runsc) alebo Kubernetes Pod:
2# Nainštalujte rungv z archívu vydania na GitHube s SLSA atestáciou alebo pomocou 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# Spúšťajte štandardné Docker buildy a kontajnery voči vnútorné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 ... (v kontajneri)
17
18docker build -t nested-app:test .
19docker run --rm nested-app:test
Päť dôvodov, prečo priama inštalácia Dockeru zlyhá vnútri gVisor (runsc)
Keď používateľ alebo AI agent pre programovanie spustí apt-get install -y docker.io (alebo docker-ce) vnútri štandardného kontajnera runsc a pokúsi sa spustiť dockerd, narazí na päť za sebou nasledujúcich technických prekážok.
1. Načítanie kernel modulov (modprobe) voči virtuálnemu jadru 4.4.0
Počas inštalácie balíka a spúšťania dockerd inicializačné skripty spúšťajú modprobe na načítanie modulov overlay, br_netfilter, ip_tables, nf_nat a xt_conntrack. Vonkajší Sentry gVisor blokuje init_module(2) a finit_module(2) a hlási virtuálnu verziu jadra (4.4.0), pre ktorú neexistuje adresár /lib/modules/4.4.0:
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. Zlyhania iptables-nft (netlink nf_tables) a mostu docker0
Na Ubuntu 22.04, Ubuntu 24.04 a Debian 11/12 príkaz apt-get install docker.io vyberie iptables-nft (založený na netlink podsystéme nf_tables) ako predvolenú alternatívu /usr/sbin/iptables. V štandardnom kontajneri gVisor bez sieťových administrátorských oprávnení alebo podpory netlink nf_tables dockerd zlyhá pri inicializácii reťazca NAT DOCKER a mostu docker0:
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. Zlyhanie overlay2 na rootfs gofer / 9p a vyčerpanie disku pri vfs
Pokiaľ vonkajší administrátor runsc výslovne nepredkonfiguroval vyhradené pripojenie tmpfs alebo filestore na /var/lib/docker, koreňový súborový systém kontajnera je podložený gofer (9p/lisafs) od gVisor alebo vnoreným overlay pripojením. Pripojenie overlay2 na /var/lib/docker zlyhá:
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
Keď sa používatelia alebo agenti pokúsia túto chybu obísť spustením dockerd --storage-driver=vfs, Docker úplne vypne zdieľanie vrstiev Copy-on-Write. Každá inštrukcia RUN v Dockerfile vykoná úplnú rekurzívnu kópiu (dirCopy) celého koreňového súborového systému, takže obraz s veľkosťou 500 MB v 10 krokoch zostavenia vytvorí 5 GB zápisov a ukončí sandbox chybou no space left on device alebo časovým limitom I/O.
4. Odmietnutie vnorených cgroup a pripojení /proc v runc
Aj keď je dockerd vynútene spustený s dockerd --iptables=false --bridge=none --storage-driver=vfs, spustenie docker run alebo docker build vyvolá runc, ktorý vytvorí vnútorný kontajner. V predvolenom neprivilegovanom kontajneri gVisor runc zlyhá pri pripájaní cgroup, opätovnom pripájaní /proc alebo načítaní vnútorných filtrov seccomp:
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. Vnorený runsc zlyhá s SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY)
Ak sa používateľ pokúsi spustiť ďalšiu inštanciu runsc vnútri runsc s platformou systrap, inicializácia sa okamžite preruší:
1creating subprocess: sysmsg: failed to initialize thread: failed to install seccomp notify rules: device or resource busy
Linuxové jadro aj Sentry gVisor povoľujú najviac jeden listener SECCOMP_FILTER_FLAG_NEW_LISTENER (SECCOMP_RET_USER_NOTIF) na hierarchiu úloh. Keďže vonkajší sandbox už drží tento slot listenera, vnútorné volanie seccomp(2) vráti EBUSY (device or resource busy).
Ako gvx rieši vnorené spúšťanie vnútri gVisor
gvx rieši všetkých päť prekážok vo vlastnom jadre v používateľskom priestore a vrstve platformy, čo umožňuje rungv bežať vnútri vonkajšieho kontajnera gVisor (runsc) alebo dokonca rekurzívne vnútri ďalšieho kontajnera gvx (gvx v gvx).

1. Automatický záložný režim systrap z EBUSY na futex slučku _RUN_SYSCALL_LOOP
V Linux systrap každý podproces stub spúšťa dva druhy vlákien:
- Vlákna vykonávania hosťa (
sysmsg): zachytávajú systémové volania hosťa cezSECCOMP_RET_TRAP(SIGSYS) a binárne upravené skokyjmp rel32(syshandler), pričom si so Sentry vymieňajú stav registrov cez zdieľanú pamäť aSYS_FUTEX. - Stub
syscallThread(syscall_thread.go): vykonáva systémové volania adresného priestoru (mmap,munmap,clone) vnútri podprocesu stub v mene Sentry.
V upstream gVisor syscallThread podporuje dva režimy prebudenia v stub_amd64.s a stub_arm64.s:
- Slučka Seccomp User-Notify (
_RUN_SECCOMP_LOOP = 6, keďt.seccompNotify != nil): PočassyscallThread.init()volaniet.installSeccompNotify()nainštaluje listenerSECCOMP_RET_USER_NOTIF(seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)), aby Sentry mohla odosielať požiadavky cezioctl(SECCOMP_IOCTL_NOTIF_SEND)aioctl(SECCOMP_IOCTL_NOTIF_RECV). - Slučka systémových volaní Futex (
_RUN_SYSCALL_LOOP = 5, keďt.seccompNotify == nil):syscallThreadspúšťasyscall_loop(stub_amd64.s/stub_arm64.s) a spí naSYS_FUTEX(FUTEX_WAIT) nad zdieľanou oblasťou dvoch stránoksyscallSentryMessage(PROT_READv stub) asyscallStubMessage(PROT_READ | PROT_WRITEv stub).
Upstream gVisor bezpodmienečne volá t.installSeccompNotify() vždy, keď je seccompNotifyIsSupported pravdivé, a zruší vytvorenie stubu, ak seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, ...) vráti EBUSY.
V gvx (pkg/sentry/platform/systrap/seccomp_notify_xplat.go a syscall_thread.go) volá syscallThread.init() funkciu 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}
Keď installSeccompNotify() vráti unix.EBUSY, pretože vonkajší gVisor (runsc) alebo vonkajší sandbox gvx už drží jediný slot listenera v reťazci filtrov procesu:
installSeccompNotifyIfFree()nastavíseccompNotifyBusy(atomic.Bool), zaznamená jedno varovanie pre proces a ponechát.seccompNotify == nilpre tento stub a všetky nasledujúce stuby.- V
syscallThread.detach()(syscall_thread_amd64.goasyscall_thread_arm64.go) podmienkat.seccompNotify == nilnastavíregs.Rbx = _RUN_SYSCALL_LOOP(5) naamd64(aleboregs.Regs[9] = _RUN_SYSCALL_LOOPnaarm64) namiesto_RUN_SECCOMP_LOOP(6). - V
syscallThread.syscall()(syscall_thread.go) vnútorný Sentry odosiela systémové volania stubu výhradne cez zdieľanú pamäť aSYS_FUTEX:
1// pkg/sentry/platform/systrap/syscall_thread.go
2// Oznámte vláknu syscall stubu novú požiadavku na systémové volanie.
3atomic.AddUint32(&sentryMsg.state, 1)
4futexWakeUint32(&sentryMsg.state)
5
6// Počkajte na odpoveď.
7futexWaitWake(&sentryMsg.state, atomic.LoadUint32(&sentryMsg.state))
Keďže filtre SECCOMP_RET_TRAP na vláknach hosťa sa vrstvia ľubovoľne hlboko a syscallThread sa vracia k _RUN_SYSCALL_LOOP bez žiadania druhého používateľského listenera notifikácií, gvx beží čisto vnútri runsc a podporuje rekurzívne vnorenie (gvx vnútri gvx).
2. Natívny overlayfs na jednosúborových zväzkoch ext4lite
Aby sa zabránilo pripojeniu overlayfs na koreňový súborový systém gofer (9p/lisafs) vonkajšieho kontajnera gVisor, -v gvx-docker:/var/lib/docker podkladá /var/lib/docker interným súborovým systémom ext4lite od gvx, uloženým v jedinom bežnom súbore (gvx-docker.ext4).
Pre vonkajší runsc je gvx-docker.ext4 len jeden bežný súbor, ku ktorému sa pristupuje cez pread64(2) a pwrite64(2). Vnútri gvx poskytuje ext4lite súborový systém ext4 s žurnálom JBD2, s rozšírenými atribútmi trusted.overlay.* a security.capability, uzlami whiteout znakových zariadení (0:0), položkami adresárov d_type a RENAME_EXCHANGE, čo umožňuje vnútornému dockerd použiť natívny ovládač úložiska overlayfs.
3. Samostatný netstack s docker0 a iptables-legacy
Vnútri gvx dockerd neinteraguje so sieťovými rozhraniami vonkajšieho kontajnera runsc ani s tabuľkami netfilter. Namiesto toho:
dockerdvytvára svoj mostdocker0a páryvethvnútri vlastnéhonetstackodgvxv používateľskom priestore.- Všetky pravidlá
iptables-legacy(-t nat -N DOCKER,MASQUERADE,conntrack,addrtype) sa vykonávajú voči internej implementácii netfilter odgvx. - Vnorené kontajnery
runcpripájajú svoje hierarchie/proc,/sysa/sys/fs/cgroupvnútri pamäťovej tabuľky pripojeníVFS2odgvxa nikdy nevolajú systémové volaniemount(2)vonkajšiehorunsc.
Overenie vnoreného docker:dind vnútri gVisor (alebo gvx v gvx)
Obidva scenáre (rungv vnútri runsc alebo rungv vnútri rungv) môžete overiť pomocou nasledujúcich príkazov:
1# Vnútri vonkajšieho kontajnera gVisor (runsc) alebo vonkajšieho kontajnera 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# Overte, že vnútorný Docker daemon používa natívny overlayfs (nie vfs)
14docker info --format 'Storage Driver: {{.Driver}}'
15# Výstup: Storage Driver: overlayfs
16
17# Spustite viacvrstvový build a vykonajte vnorený kontajner
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
Ďalšie čítanie v tejto sérii
- gvx: Spúšťanie Linux kontajnerov, Dockeru a Kubernetes v jednom neprivilegovanom procese: Prehľad
gvxnaprieč systémami Windows (whpx,systrap), macOS (hvf) a Linux (systrap). - Spúšťanie Dockeru, docker build a docker compose bez root oprávnení alebo používateľských namespace: Ako
gvxspúšťadocker:dind, keď sú zablokovanéroot,sudo,unshare(CLONE_NEWUSER),mount(2)a/dev/fuse. - Docker-in-Docker vnútri gVisor a vnorených sandboxov s gvx (tento príspevok)
- Rýchle izolované Docker prostredia pre RL rollouty AI agentov s gvx: Ako použiť
gvxa jednosúborové copy-on-write snapshotyext4litena spustenie stoviek paralelných prostredí SWE-bench a RL rolloutov s resetmi za 100 milisekúnd.