GoSuda

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

By Lemon Mint views ...

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.io a manuálne obchádzanie chýb /etc/init.d/docker ulimit, zlyhaní iptables-nft, odmietnutí surových socketov iptables-legacy, chýb pripojenia overlay2 a pádov runc / pivot_root pri docker build zvyč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 build a docker compose bez úpravy projektových súborov.

Zachovanie a posilnenie izolácie vonkajšieho sandboxu: Spustenie rungv vo vnútri runsc neobíde ani jedno obmedzenie, ktoré presadzuje vonkajší sandbox gVisor. Namiesto toho gvx vytvára druhé, vnorené jadro gVisor Sentry v používateľskom priestore, s vlastným hostlockdown (PR_SET_NO_NEW_PRIVS + seccomp), vlastným pamäťovým netstack a jediným súborovým zväzkom ext4lite. Vnútorné kontajnery dockerd a runc sú dvojito izolované: ich systémové volania, pripojenia a surové pakety nikdy neopustia vnútorné používateľské jadro gvx.

 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).

Vnorené vykonávanie gvx vo vonkajšom gVisor

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 cez SECCOMP_RET_TRAP (SIGSYS) a binárne upravené skoky jmp rel32 (syshandler), pričom si s Sentry vymieňajú stav registrov cez zdieľanú pamäť a SYS_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:

  1. Slučka seccomp user-notify (_RUN_SECCOMP_LOOP = 6, keď t.seccompNotify != nil): Počas syscallThread.init() volanie t.installSeccompNotify() nainštaluje listener SECCOMP_RET_USER_NOTIF (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)), aby Sentry mohol odosielať požiadavky cez ioctl(SECCOMP_IOCTL_NOTIF_SEND) a ioctl(SECCOMP_IOCTL_NOTIF_RECV).
  2. Slučka systémových volaní futex (_RUN_SYSCALL_LOOP = 5, keď t.seccompNotify == nil): syscallThread spúšťa syscall_loop (stub_amd64.s / stub_arm64.s) a čaká na SYS_FUTEX (FUTEX_WAIT) nad zdieľanou oblasťou 2 stránok syscallSentryMessage (PROT_READ v stube) a syscallStubMessage (PROT_READ | PROT_WRITE v 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 == nil pre tento stub aj pre všetky nasledujúce stuby.
  • V syscallThread.detach() (syscall_thread_amd64.go a syscall_thread_arm64.go) podmienka t.seccompNotify == nil nastaví regs.Rbx = _RUN_SYSCALL_LOOP (5) na amd64 (alebo regs.Regs[9] = _RUN_SYSCALL_LOOP na arm64) 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äť a SYS_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:

  • dockerd vytvára svoj most docker0 a páry veth vo vnútri vlastného netstack v používateľskom priestore od gvx.
  • Všetky pravidlá iptables-legacy (-t nat -N DOCKER, MASQUERADE, conntrack, addrtype) sa vykonávajú voči internej implementácii netfilter od gvx.
  • Vnorené kontajnery runc pripájajú svoje hierarchie /proc, /sys a /sys/fs/cgroup vo vnútri pamäťovej tabuľky pripojení VFS2 od gvx a nikdy nevolajú systémové volanie mount(2) vonkajšieho runsc.

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

  1. gvx: Spúšťanie kontajnerov Linux, Docker a Kubernetes v jedinom neprivilegovanom procese: Prehľad gvx naprieč systémami Windows (whpx, systrap), macOS (hvf) a Linux (systrap).
  2. Spúšťanie Dockeru, docker build a docker compose bez roota alebo používateľských menovorov: Ako gvx spúšťa docker:dind, keď sú root, sudo, unshare(CLONE_NEWUSER), mount(2) a /dev/fuse zablokované.
  3. Docker-in-Docker vo vnútri gVisor a vnorených sandboxoch s gvx (tento príspevok)
  4. Rýchle izolované Docker prostredia pre RL rollouty AI agentov s gvx: Ako použiť gvx a jednosúborové snímky ext4lite s copy-on-write na spúšťanie stoviek paralelných prostredí SWE-bench a RL rollout s resetmi trvajúcimi 100 milisekúnd.