Docker-in-Docker futtatása gVisor és Nested Sandboxes környezetben a gvx segítségével

Gyors megoldás: docker:dind biztonságos futtatása nem privilegizált gVisor (runsc) sandboxban
Cloud AI agent platformok, Kubernetes agent-sandbox telepítések (RuntimeClass: gvisor) és megerősítéses tanulási (reinforcement learning) kiértékelő keretrendszerek a megbízhatatlan munkaterheléseket gVisor (runsc) konténerekbe izolálják, kifejezetten a gazdagép kernelének védelme érdekében. Amikor egy munkaterhelés egy alapértelmezett, nem privilegizált runsc konténerben megpróbálja telepíteni a docker.io vagy docker-ce csomagot apt-get segítségével, és elindítani a dockerd folyamatot, ez meghiúsul, még akkor is, ha a konténeren belül root (UID 0) jogosultsággal rendelkezik.
Az gvx (rungv) segítségével biztonságosan futtathatja a docker:dind, a docker build és a docker compose parancsokat közvetlenül egy nem privilegizált gVisor (runsc) konténerben (vagy egymásba ágyazhatja a gvx-et a gvx-en belül), anélkül, hogy gyengítené a külső RuntimeClass beállítást, host socketeket tenne elérhetővé vagy --privileged jogosultságot adna.
A külső sandbox izolációjának megőrzése és megerősítése: A
rungvrunsc-en belüli futtatása nem kerül meg egyetlen korlátozást sem, amelyet a külsőgVisorsandbox érvényesít. Ehelyett agvxegy második, beágyazott, felhasználói térben futó gVisorSentrykernelt hoz létre, sajáthostlockdown(PR_SET_NO_NEW_PRIVS+seccomp) védelemmel, saját memórián belülinetstackhálózattal és egyfájlosext4litekötettel. A belsődockerdésrunckonténerek kétszeresen sandboxoltak: rendszerhívásaik, csatolásaik és nyers csomagjaik soha nem hagyják el a belsőgvxfelhasználói térbeli kernelt.
1# Meglévő gVisor (runsc) konténerben vagy Kubernetes Podban:
2# A rungv telepítése SLSA-attesztált GitHub kiadási archívumból vagy go install paranccsal:
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# Szokásos Docker buildek és konténerek futtatása a belső gvx socketen keresztül
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# Kimenet: overlayfs Alpine Linux ... (konténerizált)
17
18docker build -t nested-app:test .
19docker run --rm nested-app:test
Öt ok, amiért a közvetlen Docker telepítés meghiúsul a gVisor (runsc) környezetben
Amikor egy felhasználó vagy egy AI kódoló agent egy szabványos runsc konténerben futtatja az apt-get install -y docker.io (vagy docker-ce) parancsot, és megpróbálja elindítani a dockerd folyamatot, öt egymást követő technikai akadályba ütközik.
1. Kernelmodul-betöltés (modprobe) a virtuális 4.4.0 kernel ellen
A csomagtelepítés és a dockerd indítása során az inicializáló szkriptek modprobe parancsot futtatnak az overlay, br_netfilter, ip_tables, nf_nat és xt_conntrack modulok betöltéséhez. A külső gVisor Sentry letiltja az init_module(2) és finit_module(2) rendszerhívásokat, és olyan virtuális kernelverziót (4.4.0) jelent, amelyhez nem tartozik /lib/modules/4.4.0 könyvtár:
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. iptables-nft (nf_tables Netlink) és a docker0 híd hibái
Az Ubuntu 22.04, Ubuntu 24.04 és Debian 11/12 rendszereken az apt-get install docker.io az iptables-nft csomagot (amelyet az nf_tables netlink alrendszer támogat) választja alapértelmezett /usr/sbin/iptables alternatívaként. Egy szabványos gVisor konténerben, hálózati adminisztrátori jogosultságok vagy nf_tables netlink támogatás nélkül, a dockerd összeomlik a DOCKER NAT lánc és a docker0 híd inicializálása közben:
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. overlay2 hiba gofer / 9p rootfs és vfs lemezkimerülés esetén
Hacsak a külső runsc rendszergazda kifejezetten nem konfigurált dedikált tmpfs vagy filestore csatolást a /var/lib/docker útvonalon, a konténer rootfs a gVisor gofer (9p/lisafs) rendszerére vagy egy beágyazott overlay csatolásra épül. Az overlay2 csatolása a /var/lib/docker fölé meghiúsul:
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
Amikor a felhasználók vagy az agentek a dockerd --storage-driver=vfs indításával próbálják megkerülni ezt a hibát, a Docker teljesen letiltja a Copy-on-Write rétegmegosztást. A Dockerfile minden RUN utasítása a teljes gyökérfájlrendszer rekurzív másolását (dirCopy) hajtja végre, így egy 500 MB-os image 10 build lépésben 5 GB írássá sokszorozódik, és a sandbox no space left on device hibával vagy I/O időtúllépéssel leáll.
4. Beágyazott runc cgroup- és /proc csatolási elutasítások
Még akkor is, ha a dockerd kényszerítve indul a dockerd --iptables=false --bridge=none --storage-driver=vfs paranccsal, a docker run vagy docker build futtatása meghívja a runc programot a belső konténer létrehozásához. Egy alapértelmezett, nem privilegizált gVisor konténerben a runc meghiúsul a cgroupok csatolásakor, a /proc újracsatolásakor vagy a belső seccomp szűrők betöltésekor:
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. A beágyazott runsc meghiúsul a SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY) miatt
Ha a felhasználó egy másik runsc példányt próbál futtatni runsc-en belül a systrap platform használatával, az inicializálás azonnal megszakad:
1creating subprocess: sysmsg: failed to initialize thread: failed to install seccomp notify rules: device or resource busy
Mind a Linux kernel, mind a gVisor Sentry legfeljebb egy SECCOMP_FILTER_FLAG_NEW_LISTENER (SECCOMP_RET_USER_NOTIF) listenert enged task-hierarchiánként. Mivel a külső sandbox már birtokolja ezt a listener helyet, a belső seccomp(2) hívás EBUSY (device or resource busy) hibával tér vissza.
Hogyan oldja meg a gvx a beágyazott végrehajtást a gVisor-on belül
A gvx mind az öt akadályt a saját felhasználói térbeli kernelében és platformrétegében kezeli, így a rungv képes fut egy külső gVisor (runsc) konténerben, vagy akár rekurzívan egy másik gvx konténerben belül (gvx a gvx-ben).

1. Automatikus systrap visszaesés az EBUSY hibától a _RUN_SYSCALL_LOOP futex ciklusra
A Linux systrap rendszerben minden stub alfolyamat kétféle szálat futtat:
- Vendég végrehajtási szálak (
sysmsg): A vendég rendszerhívásokatSECCOMP_RET_TRAP(SIGSYS) és bináris módosítottjmp rel32trambulinok (syshandler) fogják el, a regiszterállapotot pedig megosztott memórián ésSYS_FUTEXsegítségével cserélik ki a Sentry-vel. - Egy stub
syscallThread(syscall_thread.go): A stub alfolyamaton belül a Sentry nevében végrehajtja a címtér-rendszerhívásokat (mmap,munmap,clone).
A upstream gVisorban a syscallThread két ébresztési módot támogat a stub_amd64.s és stub_arm64.s fájlokban:
- Seccomp User-Notify ciklus (
_RUN_SECCOMP_LOOP = 6, hat.seccompNotify != nil): AsyscallThread.init()során at.installSeccompNotify()egySECCOMP_RET_USER_NOTIFlistenert telepít (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)), hogy a Sentry azioctl(SECCOMP_IOCTL_NOTIF_SEND)ésioctl(SECCOMP_IOCTL_NOTIF_RECV)hívásokon keresztül továbbíthasson kéréseket. - Futex rendszerhívás ciklus (
_RUN_SYSCALL_LOOP = 5, hat.seccompNotify == nil): AsyscallThreadasyscall_loop(stub_amd64.s/stub_arm64.s) függvényt futtatja, és aSYS_FUTEX(FUTEX_WAIT) hívásnál alszik a megosztott, 2 lapossyscallSentryMessage(a stubbanPROT_READ) éssyscallStubMessage(a stubbanPROT_READ | PROT_WRITE) régión.
Az upstream gVisor feltétel nélkül meghívja a t.installSeccompNotify() függvényt, amint a seccompNotifyIsSupported értéke igaz, és megszakítja a stub létrehozását, ha a seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, ...) EBUSY hibát ad vissza.
A gvx rendszerben (pkg/sentry/platform/systrap/seccomp_notify_xplat.go és syscall_thread.go) a syscallThread.init() a t.installSeccompNotifyIfFree() függvényt hívja:
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}
Amikor a installSeccompNotify() unix.EBUSY értéket ad vissza, mert egy külső gVisor (runsc) vagy külső gvx sandbox már birtokolja a folyamat szűrőláncának egyetlen listener helyét:
- Az
installSeccompNotifyIfFree()beállítja aseccompNotifyBusy(atomic.Bool) értéket, a folyamatra nézve egyetlen figyelmeztetést naplóz, és at.seccompNotify == nilértéket hagyja az adott stubnál és az összes későbbi stubnál. - A
syscallThread.detach()függvényben (syscall_thread_amd64.goéssyscall_thread_arm64.go) at.seccompNotify == nileseténregs.Rbx = _RUN_SYSCALL_LOOP(5) értéket állít beamd64architektúrán (vagyregs.Regs[9] = _RUN_SYSCALL_LOOPértéketarm64architektúrán) a_RUN_SECCOMP_LOOP(6) helyett. - A
syscallThread.syscall()függvényben (syscall_thread.go) a belső Sentry a stub rendszerhívásokat kizárólag megosztott memórián ésSYS_FUTEXsegítségével továbbítja:
1// pkg/sentry/platform/systrap/syscall_thread.go
2// Notify the syscall thread about a new syscall request.
3atomic.AddUint32(&sentryMsg.state, 1)
4futexWakeUint32(&sentryMsg.state)
5
6// Wait for reply.
7futexWaitWake(&sentryMsg.state, atomic.LoadUint32(&sentryMsg.state))
Mivel a SECCOMP_RET_TRAP szűrők a vendég szálakon tetszőlegesen mélyen egymásba ágyazódhatnak, a syscallThread pedig további felhasználói értesítési listener kérése nélkül visszaesik a _RUN_SYSCALL_LOOP módra, a gvx tisztán fut a runsc-en belül, és támogatja a rekurzív egymásba ágyazást (gvx a gvx-ben).
2. Natív overlayfs egyfájlos ext4lite köteteken
Annak elkerülésére, hogy az overlayfs a külső gVisor konténer gofer (9p/lisafs) gyökérfájlrendszerére kerüljön, a -v gvx-docker:/var/lib/docker paraméter a /var/lib/docker könyvtárat a gvx belső ext4lite fájlrendszerével támasztja alá, amely egyetlen szabályos fájlban (gvx-docker.ext4) tárolódik.
A külső runsc számára a gvx-docker.ext4 csupán egy szabályos fájl, amelyet pread64(2) és pwrite64(2) rendszerhívásokkal érnek el. A gvx belsejében az ext4lite JBD2-naplózott ext4 fájlrendszert biztosít, trusted.overlay.* és security.capability kiterjesztett attribútumokkal, karakteres eszköz whiteout csomópontokkal (0:0), d_type könyvtárbejegyzésekkel és RENAME_EXCHANGE támogatással. Ez lehetővé teszi, hogy a belső dockerd a saját natív overlayfs tárolómeghajtóját használja.
3. Önálló netstack a docker0 és iptables-legacy támogatással
A gvx belsejében a dockerd nem lép kapcsolatba a külső runsc konténer hálózati interfészeivel vagy netfilter táblázataival. Ehelyett:
- A
dockerdadocker0hidat és avethpárokat agvxsaját felhasználói térbelinetstackrendszerében hozza létre. - Minden
iptables-legacyszabály (-t nat -N DOCKER,MASQUERADE,conntrack,addrtype) agvxbelső netfilter implementációján hajtódik végre. - A beágyazott
runckonténerek a/proc,/sysés/sys/fs/cgrouphierarchiáikat agvxmemórián belüliVFS2csatolási táblázatában csatolják, és soha nem hívják meg a külsőrunscmount(2)rendszerhívását.
Beágyazott docker:dind ellenőrzése a gVisor-on belül (vagy gvx a gvx-ben)
Mindkét forgatókönyv (rungv a runsc-en belül, vagy rungv a rungv-en belül) a következő parancsokkal ellenőrizhető:
1# Egy külső gVisor (runsc) konténerben vagy egy külső rungv konténerben:
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# Ellenőrzés: a belső Docker daemon natív overlayfs-t használ-e (nem vfs-t)
14docker info --format 'Storage Driver: {{.Driver}}'
15# Kimenet: Storage Driver: overlayfs
16
17# Többrétegű build futtatása és egy beágyazott konténer végrehajtása
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# Kimenet: nested-build-ok
31
32kill -INT $DIND_PID
További olvasmányok ebben a sorozatban
- gvx: Linux konténerek, Docker és Kubernetes futtatása egyetlen nem privilegizált folyamatban: Áttekintés a
gvxműködéséről Windows (whpx,systrap), macOS (hvf) és Linux (systrap) rendszereken. - Docker, docker build és docker compose futtatása root vagy felhasználói névterek nélkül: Hogyan futtatja a
gvxadocker:dindparancsot, ha aroot,sudo,unshare(CLONE_NEWUSER),mount(2)és/dev/fusele van tiltva. - Docker-in-Docker futtatása gVisor-on belül és beágyazott sandboxokban a gvx segítségével (ez a bejegyzés)
- Gyors, izolált Docker környezetek AI agentek RL rollout feladataihoz a gvx segítségével: Hogyan használható a
gvxés az egyfájlosext4litecopy-on-write pillanatképek több száz párhuzamosSWE-benchés RL rollout környezet futtatásához, 100 milliszekundumos visszaállítással.