Docker-in-Docker futtatása gVisor-on belül és Nested Sandboxes-ban a gvx segítségével

Gyors megoldás: docker:dind biztonságos futtatása nem privilegizált gVisor (runsc) sandboxban
A felhőalapú MI-ügynök számítási környezetek (például a Google Agent Compute Engine sandboxai), a Kubernetes agent-sandbox telepítések (RuntimeClass: gvisor) és a megerősítéses tanulási kiértékelő keretrendszerek gVisor (runsc) konténerekbe zárják a megbízhatatlan munkaterheléseket, kifejezetten a gazdagép kernelének védelme érdekében. Amikor egy alapértelmezett, nem privilegizált runsc konténerben futó munkaterhelés az apt-get paranccsal a docker.io vagy docker-ce csomagot próbálja telepíteni, a service docker start vagy dockerd parancsot elindítani, és docker build parancsot futtatni, akkor hibával jár, még akkor is, ha a konténeren belül root (UID 0) jogosultsággal rendelkezik.
A 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 a gvx-et egy másik gvx belsejében is elindíthatja), anélkül, hogy gyengítené a külső RuntimeClass beállításait, host socketeket tenne elérhetővé, vagy --privileged jogosultságot adna.
Kis erőfeszítésű, tokenhatékony megoldás MI-ügynökök számára: Egy gVisor-alapú felhőbeli ügynöki környezetben a
docker.iotelepítésének és a/etc/init.d/dockerulimithibáinak, aziptables-nfthibáknak, aziptables-legacynyers socket tiltásainak, azoverlay2csatolási hibáknak, valamint adocker buildrunc/pivot_rootösszeomlásainak manuális kikerülése jellemzően 20+ eszközhívási körön és tízezernyi tokenen emészti fel a munkamenetet, mielőtt teljesen kudarcot vallana. Agvx(rungv) egy kis erőfeszítésű, tokenhatékony, közvetlenül behelyettesíthető megoldás: az ügynök egyetlen körben 3 paranccsal elindít egy működő, felhasználói térben futó Docker démont, majd projektfájlok módosítása nélkül azonnal végrehajtja adocker buildésdocker composeparancsokat.
A külső sandbox izoláció megőrzése és megerősítése: A
rungvrunsc-en belüli futtatása nem kerüli meg a külsőgVisorsandbox egyetlen korlátozását sem. Ehelyett agvxegy második, beágyazott, felhasználói térben működő gVisorSentrykernelt hoz létre sajáthostlockdown(PR_SET_NO_NEW_PRIVS+seccomp) védelemmel, saját memóriabelinetstackhálózattal és egyetlen fájlból állóext4litekötettel. A belsődockerdésrunckonténerek kettős sandboxban futnak: 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-val igazolt 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 build és konténerfuttatás 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 MI-kódoló ügynök 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 démont, öt egymást követő műszaki akadályba ütközik.
1. Init-szkript (systemctl / service docker start ulimit) és kernelmodul (modprobe 4.4.0) hibák
A apt-get install -y docker.io (vagy docker-ce) futtatása után a démon indítása systemctl start docker vagy service docker start paranccsal azonnal meghiúsul, mert a PID 1 nem systemd, a /etc/init.d/docker pedig megpróbálja a ulimit -n értékét a sandbox kemény RLIMIT_NOFILE határa fölé emelni. Amikor a dockerd ezután közvetlenül indul, az inicializáló szkriptek a modprobe paranccsal próbálják betölteni az overlay, bridge, br_netfilter, ip_tables és nf_nat modulokat a gVisor virtuális kernelverziójához (4.4.0) képest, amelyhez nincs /lib/modules/4.4.0 könyvtár:
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 (nf_tables Netlink), iptables-legacy nyers socket tiltások és docker0 híd hibák
Az Ubuntu 22.04, Ubuntu 24.04 és Debian 11/12 rendszereken az apt-get install docker.io az iptables-nft eszközt (amely az nf_tables netlink alrendszeren alapul) választja alapértelmezett /usr/sbin/iptables alternatívaként. Ráadásul, még ha a felhasználó vagy az ügynök át is vált az iptables-legacy eszközre (update-alternatives --set iptables /usr/sbin/iptables-legacy), az alapértelmezett nem privilegizált gVisor sandboxok (--net-raw=false) EPERM hibával elutasítják az AF_INET, SOCK_RAW socketeket, aminek következtében az iptables-legacy akkor is Permission denied (you must be root) hibával leáll, ha UID 0 felhasználóként fut:
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. overlay2 hiba gofer / 9p rootfs esetén és vfs lemezkimerülés
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-e a gVisor gofer (9p/lisafs) vagy egy beágyazott overlay csatolás mögött található. 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 ügynökök a dockerd --storage-driver=vfs indításával próbálják megkerülni ezt a hibát, a Docker teljesen kikapcsolja 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) végzi el, így egy 500 MB-os image 10 build lépés alatt 5 GB írássá szorzódik, és a sandbox no space left on device hibával vagy I/O időtúllépéssel leáll.
4. docker build (BuildKit) és beágyazott runc hibák (pivot_root, /proc, cgroupok és DNS)
Még ha a dockerd a dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs paranccsal kényszerítetten is elindul, a docker build -t app . vagy docker run parancs azonnal meghiúsul, amikor a BuildKit vagy a runc elindítja a build konténert:
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. Beágyazott runsc hiba SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY) miatt
Ha a felhasználó a systrap platformot használva egy másik runsc példányt próbál futtatni egy runsc belsejében, 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 feladatlánconké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 futtatást a gVisor környezetben
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 futni 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áról a _RUN_SYSCALL_LOOP futex ciklusra
A Linux systrap rendszerben minden stub alfolyamat kétfajta szálat futtat:
- Vendég végrehajtási szálak (
sysmsg): A vendég rendszerhívásokatSECCOMP_RET_TRAP(SIGSYS) és binárisan patcheltjmp rel32trampolinok (syshandler) fogják el, a regiszterállapotot pedig megosztott memórián ésSYS_FUTEXhívásokon keresztül cserélik ki a Sentry-vel. - Egy stub
syscallThread(syscall_thread.go): A Sentry megbízásából a stub alfolyamaton belül végrehajtja a címtér-kezelő rendszerhívásokat (mmap,munmap,clone).
Az 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 küldhessen kéréseket. - Futex rendszerhívási ciklus (
_RUN_SYSCALL_LOOP = 5, hat.seccompNotify == nil): AsyscallThreadasyscall_loop(stub_amd64.s/stub_arm64.s) függvényt futtatja, ésSYS_FUTEX(FUTEX_WAIT) hívásra 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, amikor a seccompNotifyIsSupported igaz, és megszakítja a stub létrehozását, ha a seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, ...) EBUSY értékkel tér vissza.
A gvx (pkg/sentry/platform/systrap/seccomp_notify_xplat.go és syscall_thread.go) esetén a syscallThread.init() meghívja a t.installSeccompNotifyIfFree() függvényt:
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 az installSeccompNotify() a unix.EBUSY értékkel tér 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) változót, a folyamatra nézve egyszer naplóbejegyzést ír, és az adott stubnál, valamint az összes későbbi stubnál at.seccompNotify == nilállapotot hagyja érvényben. - A
syscallThread.detach()függvényben (syscall_thread_amd64.goéssyscall_thread_arm64.go) at.seccompNotify == nilfeltétel eseténamd64architektúrán aregs.Rbx = _RUN_SYSCALL_LOOP(5),arm64architektúrán pedig aregs.Regs[9] = _RUN_SYSCALL_LOOPértéket állítja be 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_FUTEXhívásokon keresztül 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 tetszőlegesen mélyen egymásba ágyazódhatnak a vendég szálakon, a syscallThread pedig a _RUN_SYSCALL_LOOP módra vált vissza anélkül, hogy második felhasználói értesítési listenert kérne, a gvx tisztán fut az runsc belsejében, és támogatja a rekurzív beá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ájlrendszere alá helyezi, 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) hívásokkal érnek el. A gvx belsejében az ext4lite JBD2 naplózással ellátott ext4 fájlrendszert biztosít, trusted.overlay.* és security.capability bővített 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 eszközökkel
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érbelinetstackrétegében hozza létre. - Az összes
iptables-legacyszabály (-t nat -N DOCKER,MASQUERADE,conntrack,addrtype) agvxbelső netfilter implementációjában fut le. - A beágyazott
runckonténerek a/proc,/sysés/sys/fs/cgrouphierarchiáikat agvxmemóriabeliVFS2csatolási táblájá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 gVisor környezetben (vagy gvx a gvx-ben)
Mindkét forgatókönyvet (rungv runsc-en belül, vagy rungv rungv-n belül) az alábbi parancsokkal ellenőrizheti:
1# Külső gVisor (runsc) konténerben vagy 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, hogy a belső Docker démon 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 beágyazott konténer indítá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 olvasnivaló 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 jogosultság vagy felhasználói névterek nélkül: Hogyan futtatja a
gvxadocker:dindparancsot akkor, ha aroot, asudo, azunshare(CLONE_NEWUSER), amount(2)és a/dev/fusele van tiltva. - Docker-in-Docker futtatása gVisorban és beágyazott sandboxokban a gvx segítségével (ez a bejegyzés)
- Gyors, izolált Docker környezetek MI-ügynökök RL rollout-jaihoz a gvx segítségével: Hogyan használható a
gvxés az egyfájlosext4litecopy-on-write pillanatkép több száz párhuzamosSWE-benchés RL rollout környezet futtatására, 100 milliszekundumos visszaállítással.