Docker-in-Docker-ympäristön ajaminen gVisorin ja sisäkkäisten Sandboxien sisällä gvx:n avulla

Pikaratkaisu: docker:dind-kontin turvallinen suoritus etuoikeudettomassa gVisor (runsc) -Sandboxissa
Pilvipohjaiset tekoälyagenttialustat, Kubernetes-agent-sandbox-käyttöönotot (RuntimeClass: gvisor) ja vahvistusoppimisen arviointiympäristöt eristävät luottamattomat työkuormat gVisor (runsc) -kontteihin nimenomaan isäntäytimen suojaamiseksi. Kun oletusarvoisesti etuoikeudettomassa runsc-kontissa toimiva työkuorma yrittää asentaa paketin docker.io tai docker-ce komennolla apt-get ja käynnistää dockerd:n, se epäonnistuu, vaikka sillä olisi kontin sisällä root (UID 0).
gvx (rungv):n avulla voit turvallisesti suorittaa docker:dind-, docker build- ja docker compose -komennot suoraan etuoikeudettomassa gVisor (runsc) -kontissa (tai sijoittaa gvx:n gvx:n sisälle) heikentämättä ulomman RuntimeClass:n turvallisuutta, paljastamatta isännän socketeja tai myöntämättä --privileged-oikeutta.
Ulomman Sandbox-eristyksen säilyttäminen ja vahvistaminen:
rungv:n suorittaminenrunsc:n sisällä ei ohita yhtäkään ulommangVisor-sandboxin asettamaa rajoitusta. Sen sijaangvxluo toisen, sisäkkäisen käyttäjätilan gVisorSentry-ytimen, jolla on omahostlockdown(PR_SET_NO_NEW_PRIVS+seccomp), oma muistinvarainennetstackja yksitiedostoinenext4lite-volyymi. Sisäisetdockerd- jarunc-kontit ovat kaksinkertaisesti sandboxattuja: niiden järjestelmäkutsut, liitokset ja raakapaketit eivät koskaan poistu sisäisengvx:n käyttäjätilaytimen sisältä.
1# Olemassa olevan gVisor (runsc) -kontin tai Kubernetes Podin sisällä:
2# Asenna rungv SLSA-todennetusta GitHub-julkaisuarkistosta tai go install -komennolla:
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# Suorita tavalliset Docker-buildit ja -kontit sisäistä gvx-socketia vasten
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# Tulos: overlayfs Alpine Linux ... (containerized)
17
18docker build -t nested-app:test .
19docker run --rm nested-app:test
Viisi syytä, miksi suora Docker-asennus epäonnistuu gVisor:n (runsc) sisällä
Kun käyttäjä tai tekoälyn koodausagentti suorittaa komennon apt-get install -y docker.io (tai docker-ce) tavallisen runsc-kontin sisällä ja yrittää käynnistää dockerd:n, se kohtaa viisi peräkkäistä teknistä estettä.
1. Ytimen moduulien lataaminen (modprobe) virtuaalista ydintä 4.4.0 vastaan
Paketin asennuksen ja dockerd:n käynnistyksen aikana alustuskomentosarjat suorittavat modprobe-komennon ladatakseen moduulit overlay, br_netfilter, ip_tables, nf_nat ja xt_conntrack. Ulompi gVisor Sentry estää kutsut init_module(2) ja finit_module(2) ja raportoi virtuaalisen ytimen version (4.4.0), jolle ei ole hakemistoa /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. iptables-nft:n (nf_tables Netlink) ja docker0-sillan virheet
Ubuntu 22.04:ssä, Ubuntu 24.04:ssä ja Debian 11/12:ssa apt-get install docker.io valitsee oletusvaihtoehdoksi polulle /usr/sbin/iptables vaihtoehdon iptables-nft (jota tukee nf_tables-netlink-alijärjestelmä). Tavallisessa gVisor-kontissa ilman verkon ylläpitäjän oikeuksia tai nf_tables-netlink-tukea dockerd kaatuu alustaessaan DOCKER NAT -ketjua ja docker0-siltaa:
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-virhe gofer- / 9p-juurijärjestelmässä ja vfs-levytilan loppuminen
Ellei ulomman runsc:n ylläpitäjä ole nimenomaisesti esikonfiguroinut omistettua tmpfs- tai filestore-liitosta kohteeseen /var/lib/docker, kontin juurijärjestelmä perustuu gVisorin gofer- (9p/lisafs) tai sisäkkäiseen overlay-liitokseen. overlay2:n liittäminen kohteen /var/lib/docker päälle epäonnistuu:
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
Kun käyttäjät tai agentit yrittävät ohittaa tämän virheen käynnistämällä dockerd --storage-driver=vfs:n, Docker poistaa kokonaan Copy-on-Write-kerrosten jakamisen käytöstä. Jokainen Dockerfile:n RUN-ohje suorittaa koko juurijärjestelmän täyden rekursiivisen kopioinnin (dirCopy), mikä kertoo 500 MB:n kuvan 10 buildivaiheessa 5 GB:n kirjoitukseksi ja päättää sandboxin virheeseen no space left on device tai I/O-aikakatkaisuun.
4. Sisäkkäisten runc-cgroup- ja /proc-liitosten hylkäämiset
Vaikka dockerd pakotettaisiin käynnistymään komennolla dockerd --iptables=false --bridge=none --storage-driver=vfs, komennot docker run tai docker build kutsuvat runc:n luomaan sisäisen kontin. Oletusarvoisessa etuoikeudettomassa gVisor-kontissa runc epäonnistuu liittäessään cgroupeja, liittäessään /proc:n uudelleen tai ladatessaan sisäisiä seccomp-suodattimia:
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. Sisäkkäinen runsc epäonnistuu virheeseen SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY)
Jos käyttäjä yrittää suorittaa toisen runsc-instanssin runsc:n sisällä systrap-alustaa käyttäen, alustus keskeytyy välittömästi:
1creating subprocess: sysmsg: failed to initialize thread: failed to install seccomp notify rules: device or resource busy
Sekä Linux-ydin että gVisor Sentry sallivat tehtävähierarkiaa kohden enintään yhden SECCOMP_FILTER_FLAG_NEW_LISTENER- (SECCOMP_RET_USER_NOTIF) -kuuntelijan. Koska ulompi sandbox on jo varannut kuuntelijapaikan, sisäinen seccomp(2)-kutsu palauttaa arvon EBUSY (device or resource busy).
Miten gvx ratkaisee sisäkkäisen suorituksen gVisor:n sisällä
gvx ratkaisee kaikki viisi estettä omassa käyttäjätilaytimessään ja alustakerroksessaan, mikä mahdollistaa rungv:n suorittamisen ulomman gVisor (runsc) -kontin sisällä tai jopa rekursiivisesti toisen gvx-kontin sisällä (gvx gvx:ssä).

1. Automaattinen systrap-varajärjestelmä EBUSY-tilanteesta _RUN_SYSCALL_LOOP-futex-silmukkaan
Linuxin systrap-alustassa jokainen stub-aliprosessi suorittaa kahdentyyppisiä säikeitä:
- Vierasjärjestelmän suoritussäikeet (
sysmsg): Kaappaavat vierasjärjestelmäkutsutSECCOMP_RET_TRAP-mekanismilla (SIGSYS) ja binääripatchatuillajmp rel32-trampoliineilla (syshandler), vaihtaen rekisteritilan Sentryn kanssa jaetun muistin jaSYS_FUTEX:n kautta. - Stub-
syscallThread(syscall_thread.go): Suorittaa osoiteavaruuden järjestelmäkutsut (mmap,munmap,clone) stub-aliprosessin sisällä Sentryn puolesta.
Upstream-gVisorissa syscallThread tukee kahta herätystilaa tiedostoissa stub_amd64.s ja stub_arm64.s:
- Seccomp User-Notify -silmukka (
_RUN_SECCOMP_LOOP = 6, kunt.seccompNotify != nil):syscallThread.init()-vaiheen aikanat.installSeccompNotify()asentaaSECCOMP_RET_USER_NOTIF-kuuntelijan (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)), jotta Sentry voi lähettää pyynnötioctl(SECCOMP_IOCTL_NOTIF_SEND)- jaioctl(SECCOMP_IOCTL_NOTIF_RECV)-kutsujen kautta. - Futex-järjestelmäkutsusilmukka (
_RUN_SYSCALL_LOOP = 5, kunt.seccompNotify == nil):syscallThreadsuorittaasyscall_loop-silmukan (stub_amd64.s/stub_arm64.s) ja nukkuuSYS_FUTEX:n (FUTEX_WAIT) varassa jaetulla 2-sivun alueellasyscallSentryMessage(stubissaPROT_READ) jasyscallStubMessage(stubissaPROT_READ | PROT_WRITE).
Upstream gVisor kutsuu ehdoitta t.installSeccompNotify()-funktiota aina, kun seccompNotifyIsSupported on tosi, ja keskeyttää stubin luonnin, jos seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, ...) palauttaa arvon EBUSY.
gvx:ssä (pkg/sentry/platform/systrap/seccomp_notify_xplat.go ja syscall_thread.go) syscallThread.init() kutsuu funktiota 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}
Kun installSeccompNotify() palauttaa arvon unix.EBUSY, koska ulompi gVisor (runsc) tai ulompi gvx-sandbox on jo varannut prosessin suodatinketjun ainoan kuuntelijapaikan:
installSeccompNotifyIfFree()asettaa muuttujanseccompNotifyBusy(atomic.Bool), kirjaa prosessille yhden varoituksen ja jättäät.seccompNotify == nil-arvoksi kyseiselle stubille ja kaikille sitä seuraaville stubeille.syscallThread.detach()-funktiossa (syscall_thread_amd64.gojasyscall_thread_arm64.go)t.seccompNotify == nilasettaaamd64-arkkitehtuurilla arvonregs.Rbx = _RUN_SYSCALL_LOOP(5) (taiarm64-arkkitehtuurillaregs.Regs[9] = _RUN_SYSCALL_LOOP) arvon_RUN_SECCOMP_LOOP(6) sijaan.syscallThread.syscall()-funktiossa (syscall_thread.go) sisäinen Sentry välittää stub-järjestelmäkutsut yksinomaan jaetun muistin jaSYS_FUTEX:n kautta:
1// pkg/sentry/platform/systrap/syscall_thread.go
2// Ilmoita syscall-säikeelle uudesta järjestelmäkutsupyynnöstä.
3atomic.AddUint32(&sentryMsg.state, 1)
4futexWakeUint32(&sentryMsg.state)
5
6// Odota vastausta.
7futexWaitWake(&sentryMsg.state, atomic.LoadUint32(&sentryMsg.state))
Koska SECCOMP_RET_TRAP-suodattimet vierasäikeillä voivat pinota mielivaltaisen syviksi ja syscallThread palaa _RUN_SYSCALL_LOOP-silmukkaan pyytämättä toista käyttäjäilmoituskuuntelijaa, gvx toimii moitteettomasti runsc:n sisällä ja tukee rekursiivista sisäkkäisyyttä (gvx gvx:n sisällä).
2. Natiivi overlayfs yksitiedostoisilla ext4lite-volyymeilla
Jotta overlayfs:ää ei liitettäisi ulomman gVisor-kontin gofer- (9p/lisafs) juurijärjestelmän päälle, -v gvx-docker:/var/lib/docker tukee kohdetta /var/lib/docker gvx:n sisäisellä ext4lite-tiedostojärjestelmällä, joka on tallennettu yhteen tavalliseen tiedostoon (gvx-docker.ext4).
Ulomman runsc:n näkökulmasta gvx-docker.ext4 on vain yksi tavallinen tiedosto, johon päästään käsiksi pread64(2)- ja pwrite64(2)-kutsuilla. gvx:n sisällä ext4lite tarjoaa JBD2-lokitetun ext4-tiedostojärjestelmän, jossa on trusted.overlay.*- ja security.capability-laajennetut attribuutit, merkkilaitteen whiteout-solmut (0:0), d_type-hakemistomerkinnät ja RENAME_EXCHANGE, mikä mahdollistaa sisäisen dockerd:n käyttää natiivia overlayfs-tallennusajuriaan.
3. Itsenäinen netstack, jossa docker0 ja iptables-legacy
gvx:n sisällä dockerd ei ole vuorovaikutuksessa ulomman runsc-kontin verkkoliittymien tai netfilter-taulukoiden kanssa. Sen sijaan:
dockerdluodocker0-siltansa javeth-parinsagvx:n oman käyttäjätilannetstack-pinon sisällä.- Kaikki
iptables-legacy-säännöt (-t nat -N DOCKER,MASQUERADE,conntrack,addrtype) suoritetaangvx:n sisäistä netfilter-toteutusta vastaan. - Sisäkkäiset
runc-kontit liittävät/proc-,/sys- ja/sys/fs/cgroup-hierarkiansagvx:n muistissa olevaanVFS2-liitostauluun, eivätkä koskaan kutsu ulommanrunsc:nmount(2)-järjestelmäkutsua.
Sisäkkäisen docker:dind-kontin todentaminen gVisor:n sisällä (tai gvx gvx:ssä)
Voit todentaa molemmat skenaariot (rungv runsc:n sisällä tai rungv rungv:n sisällä) seuraavilla komennoilla:
1# Ulomman gVisor (runsc) -kontin tai ulomman rungv-kontin sisällä:
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# Varmista, että sisäinen Docker-daemon käyttää natiivia overlayfs-tiedostojärjestelmää (ei vfs:ää)
14docker info --format 'Storage Driver: {{.Driver}}'
15# Tulos: Storage Driver: overlayfs
16
17# Suorita monikerroksinen build ja suorita sisäkkäinen kontti
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# Tulos: nested-build-ok
31
32kill -INT $DIND_PID
Lisälukemista tästä sarjasta
- gvx: Linux-konttien, Dockerin ja Kubernetesin ajaminen yhdessä etuoikeudettomassa prosessissa: Yleiskatsaus
gvx:stä Windowsissa (whpx,systrap), macOS:ssä (hvf) ja Linuxissa (systrap). - Docker-, docker build- ja docker compose -komentojen suorittaminen ilman juurioikeuksia tai käyttäjänimiavaruuksia: Miten
gvxsuorittaadocker:dind-kontin, kunroot,sudo,unshare(CLONE_NEWUSER),mount(2)ja/dev/fuseon estetty. - Docker-in-Docker-kontin suorittaminen gVisorin ja sisäkkäisten sandboxien sisällä gvx:llä (tämä kirjoitus)
- Nopeat eristetyt Docker-ympäristöt tekoälyagenttien RL-rollouteihin gvx:llä: Miten käyttää
gvx:ää ja yksitiedostoisiaext4litecopy-on-write -tilannekuvia satojen rinnakkaistenSWE-bench- ja RL-rollout-ympäristöjen ajamiseen 100 millisekunnin nollauksilla.