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

Rýchle riešenie: Bezpečné spustenie docker:dind vo vnútri neprivilegovaného sandboxu gVisor (runsc)
Výpočtové prostredia cloudových AI agentov (napríklad sandboxy Google Agent Compute Engine), nasadenia Kubernetes agent-sandbox (RuntimeClass: gvisor) a hodnotiace prostredia pre posilňovacie učenie izolujú nedôveryhodné úlohy vo vnútri kontajnerov gVisor (runsc), konkrétne s cieľom chrániť jadro hostiteľa. Keď sa úloha vo vnútri predvoleného neprivilegovaného kontajnera runsc pokúsi nainštalovať docker.io alebo docker-ce pomocou apt-get, spustiť service docker start alebo dockerd a vykonať docker build, zlyhá, a to aj vtedy, ak má v kontajneri oprávnenia root (UID 0).
Pomocou gvx (rungv) môžete bezpečne spustiť docker:dind, docker build a docker compose priamo vo vnútri neprivilegovaného kontajnera gVisor (runsc) (alebo vnoriť gvx do gvx) bez oslabenia vonkajšieho RuntimeClass, bez sprístupnenia hostiteľských socketov a bez udelenia --privileged.
Riešenie s minimálnym úsilím a efektívnym využitím tokenov pre AI agentov: Vo vnútri prostredia cloudového agenta chráneného gVisorom sa pokus o inštaláciu
docker.ioa manuálne obchádzanie chýb/etc/init.d/dockerulimit, zlyhaníiptables-nft, odmietnutí surových socketoviptables-legacy, chýb pripojeniaoverlay2a pádovrunc/pivot_rootpridocker buildzvyčajne minie viac ako 20 volaní nástrojov a desiatky tisíc tokenov skôr, než úplne zlyhá.gvx(rungv) je riešenie s minimálnym úsilím a efektívnym využitím tokenov, ktoré možno nasadiť bez úprav: agent spustí 3 príkazy v jedinom kroku, čím naštartuje funkčný Docker daemon v používateľskom priestore, a okamžite vykonádocker buildadocker composebez úpravy projektových súborov.
Zachovanie a posilnenie izolácie vonkajšieho sandboxu: Spustenie
rungvvo vnútrirunscneobíde ani jedno obmedzenie, ktoré presadzuje vonkajší sandboxgVisor. Namiesto tohogvxvytvára druhé, vnorené jadrogVisor Sentryv používateľskom priestore, s vlastnýmhostlockdown(PR_SET_NO_NEW_PRIVS+seccomp), vlastným pamäťovýmnetstacka jediným súborovým zväzkomext4lite. Vnútorné kontajnerydockerdaruncsú dvojito izolované: ich systémové volania, pripojenia a surové pakety nikdy neopustia vnútorné používateľské jadrogvx.
1# Vo vnútri existujúceho kontajnera gVisor (runsc) alebo Kubernetes Pod:
2# Nainštalujte rungv z archívu vydania na GitHube s atestáciou SLSA alebo cez 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# Spustite štandardné Docker zostavenia a kontajnery cez vnútorný socket 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äť dôvodov, prečo priama inštalácia Dockeru zlyháva vo vnútri gVisor (runsc)
Keď používateľ alebo AI agent ppre programovanie spustí apt-get install -y docker.io (alebo docker-ce) vo vnútri štandardného kontajnera runsc a pokúsi sa spustiť dockerd, narazí na päť po sebe nasledujúcich technických prekážok.
1. Zlyhania inicializačného skriptu (systemctl / service docker start ulimit) a kernelového modulu (modprobe 4.4.0)
Po spustení apt-get install -y docker.io (alebo docker-ce) pokus o spustenie démona cez systemctl start docker alebo service docker start okamžite zlyhá, pretože PID 1 nie je systemd a /etc/init.d/docker sa pokúša zvýšiť ulimit -n nad tvrdý strop RLIMIT_NOFILE sandboxu. Keď sa potom dockerd spustí priamo, inicializačné skripty volajú modprobe, aby načítali overlay, bridge, br_netfilter, ip_tables a nf_nat oproti virtuálnej verzii jadra gVisor (4.4.0), ktorá nemá adresár /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 (netlink nf_tables), odmietnutia surových socketov iptables-legacy a zlyhania mosta 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 netlinkovom subsystéme nf_tables) ako predvolenú alternatívu /usr/sbin/iptables. Navyše, aj keď používateľ alebo agent prepne na iptables-legacy (update-alternatives --set iptables /usr/sbin/iptables-legacy), predvolené neprivilegované sandboxy gVisor (--net-raw=false) odmietnu sockety AF_INET, SOCK_RAW s EPERM, čo spôsobí zlyhanie iptables-legacy s hlásením Permission denied (you must be root) aj vtedy, keď beží ako 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. Zlyhanie overlay2 na koreňovom súborovom systéme gofer / 9p a vyčerpanie disku pri vfs
Pokiaľ vonkajší správca runsc výslovne nenakonfiguroval vyhradený pripojený bod 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 pripojením overlay. 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ívne kopírovanie (dirCopy``) celého koreňového súborového systému, takže obraz s veľkosťou 500 MB sa v 10 krokoch zostavenia zmení na 5 GB zápisov, čo ukončí sandbox chybou no space left on device` alebo časovým limitom I/O.
4. Zlyhania docker build (BuildKit) a vnorených runc (pivot_root, /proc, cgroups a DNS)
Aj keď sa dockerd vynúti spustiť s dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs, pokus o spustenie docker build -t app . alebo docker run zlyhá okamžite, keď BuildKit alebo runc spúšťa kontajner zostavenia:
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. Vnorený runsc zlyhá s SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY)
Ak sa používateľ pokúsi spustiť ďalšiu inštanciu runsc vo vnútri runsc s použitím platformy 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 gVisor Sentry povoľujú najviac jeden listener SECCOMP_FILTER_FLAG_NEW_LISTENER (SECCOMP_RET_USER_NOTIF) na hierarchiu úloh. Keďže vonkajší sandbox už drží túto pozíciu listenera, vnútorné volanie seccomp(2) vráti EBUSY (device or resource busy).
Ako gvx rieši vnorené vykonávanie vo vnútri gVisor
gvx rieši všetkých päť prekážok vo vnútri svojho vlastného používateľského jadra a vrstvy platformy, čo umožňuje spustiť rungv vo vnútri vonkajšieho kontajnera gVisor (runsc) alebo dokonca rekurzívne vo vnútri ďalšieho kontajnera gvx (gvx v gvx).

1. Automatický prechod z systrap z EBUSY na futex slučku _RUN_SYSCALL_LOOP
V Linuxovom systrap každý podproces stubu 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 s 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) vo vnútri podprocesu stubu v mene Sentry.
V upstreamovom gVisor syscallThread podporuje dva režimy prebudenia v súboroch 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 mohol 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 čaká naSYS_FUTEX(FUTEX_WAIT) nad zdieľanou oblasťou 2 stránoksyscallSentryMessage(PROT_READv stube) asyscallStubMessage(PROT_READ | PROT_WRITEv stube).
Upstreamový gVisor bezpodmienečne volá t.installSeccompNotify() vždy, keď je seccompNotifyIsSupported pravda, a preruší vytváranie 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ú pozíciu listenera v reťazci filtrov procesu:
installSeccompNotifyIfFree()nastavíseccompNotifyBusy(atomic.Bool), zaznamená jedno varovanie pre proces a ponechát.seccompNotify == nilpre tento stub aj pre 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ýlučne cez zdieľanú pamäť aSYS_FUTEX:
1// pkg/sentry/platform/systrap/syscall_thread.go
2// Upozorni systémové vlákno na novú požiadavku na systémové volanie.
3atomic.AddUint32(&sentryMsg.state, 1)
4futexWakeUint32(&sentryMsg.state)
5
6// Čakaj na odpoveď.
7futexWaitWake(&sentryMsg.state, atomic.LoadUint32(&sentryMsg.state))
Pretože filtre SECCOMP_RET_TRAP na vláknach hosťa môžu byť zásobníkovo ľubovoľne hlboké a syscallThread sa vracia k _RUN_SYSCALL_LOOP bez požiadania o druhý listener používateľských notifikácií, gvx beží bez problémov vo vnútri runsc a podporuje rekurzívne vnorenie (gvx vo vnútri gvx).
2. Natívny overlayfs na jednosúborových zväzkoch ext4lite
Aby sa predišlo 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 regulárnom súbore (gvx-docker.ext4).
Pre vonkajší runsc je gvx-docker.ext4 iba jeden regulárny súbor, ku ktorému sa pristupuje cez pread64(2) a pwrite64(2). Vo vnútri gvx poskytuje ext4lite súborový systém ext4 s žurnálom JBD2, rozšírené atribúty trusted.overlay.* a security.capability, uzly zariadení typu znak na označovanie odstránení (0:0), záznamy adresárov d_type a RENAME_EXCHANGE, čo umožňuje vnútornému dockerd použiť jeho natívny ovládač úložiska overlayfs.
3. Samostatný netstack s docker0 a iptables-legacy
Vo vnútri gvx dockerd neinteraguje so sieťovými rozhraniami ani tabuľkami netfilter vonkajšieho kontajnera runsc. Namiesto toho:
dockerdvytvára svoj mostdocker0a páryvethvo vnútri vlastnéhonetstackv používateľskom priestore odgvx.- 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/cgroupvo vnútri pamäťovej tabuľky pripojeníVFS2odgvxa nikdy nevolajú systémové volaniemount(2)vonkajšiehorunsc.
Overenie vnoreného docker:dind vo vnútri gVisor (alebo gvx v gvx)
Oba scenáre (rungv vo vnútri runsc alebo rungv vo vnútri rungv) môžete overiť pomocou nasledujúcich príkazov:
1# Vo 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é zostavenie 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 kontajnerov Linux, Docker a Kubernetes v jedinom neprivilegovanom procese: Prehľad
gvxnaprieč systémami Windows (whpx,systrap), macOS (hvf) a Linux (systrap). - Spúšťanie Dockeru, docker build a docker compose bez roota alebo používateľských menovorov: Ako
gvxspúšťadocker:dind, keď súroot,sudo,unshare(CLONE_NEWUSER),mount(2)a/dev/fusezablokované. - Docker-in-Docker vo vnútri gVisor a vnorených sandboxoch s gvx (tento príspevok)
- Rýchle izolované Docker prostredia pre RL rollouty AI agentov s gvx: Ako použiť
gvxa jednosúborové snímkyext4lites copy-on-write na spúšťanie stoviek paralelných prostredíSWE-bencha RL rollout s resetmi trvajúcimi 100 milisekúnd.