Menjalankan Docker-in-Docker di Dalam gVisor dan Nested Sandboxes dengan gvx

Solusi Cepat: Menjalankan docker:dind secara Aman di Dalam Sandbox gVisor (runsc) Tanpa Hak Istimewa
Platform agen AI berbasis cloud, deployment Kubernetes agent-sandbox (RuntimeClass: gvisor), dan harness evaluasi reinforcement learning mengisolasi beban kerja yang tidak tepercaya di dalam kontainer gVisor (runsc) secara khusus untuk melindungi kernel host. Ketika beban kerja di dalam kontainer runsc tanpa hak istimewa standar mencoba memasang docker.io atau docker-ce melalui apt-get dan menjalankan dockerd, proses tersebut gagal meskipun memiliki root (UID 0) di dalam kontainer.
Dengan gvx (rungv), Anda dapat secara aman menjalankan docker:dind, docker build, dan docker compose langsung di dalam kontainer gVisor (runsc) tanpa hak istimewa (atau menyarangkan gvx di dalam gvx) tanpa melemahkan RuntimeClass luar, membuka soket host, atau memberikan --privileged.
Menjaga dan Memperkuat Isolasi Sandbox Luar: Menjalankan
rungvdi dalamrunsctidak melewati satu pun pembatasan yang diberlakukan oleh sandboxgVisorluar. Sebaliknya,gvxmembuat kernelSentrygVisor bersarang kedua yang berjalan di ruang pengguna denganhostlockdownsendiri (PR_SET_NO_NEW_PRIVS+seccomp),netstackdalam memori sendiri, dan volumeext4liteberkas tunggal.dockerddan kontainerruncinternal berada dalam sandbox ganda: panggilan sistem, mount, dan paket mentah mereka tidak pernah keluar dari kernel ruang penggunagvxinternal.
1# Di dalam kontainer gVisor (runsc) yang sudah ada atau Kubernetes Pod:
2# Pasang rungv dari arsip rilis GitHub yang teratestasi SLSA atau melalui 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# Jalankan build dan kontainer Docker standar terhadap soket gvx internal
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# Keluaran: overlayfs Alpine Linux ... (dalam kontainer)
17
18docker build -t nested-app:test .
19docker run --rm nested-app:test
Lima Alasan Mengapa Instalasi Docker Langsung Gagal di Dalam gVisor (runsc)
Ketika pengguna atau agen pengodean AI menjalankan apt-get install -y docker.io (atau docker-ce) di dalam kontainer runsc standar dan mencoba menjalankan dockerd, proses tersebut menghadapi lima hambatan teknis yang berurutan.
1. Pemuatan Modul Kernel (modprobe) Terhadap Kernel Virtual 4.4.0
Selama instalasi paket dan startup dockerd, skrip inisialisasi menjalankan modprobe untuk memuat overlay, br_netfilter, ip_tables, nf_nat, dan xt_conntrack. Sentry gVisor luar memblokir init_module(2) dan finit_module(2) serta melaporkan versi kernel virtual (4.4.0) yang tidak memiliki direktori /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. Kegagalan iptables-nft (Netlink nf_tables) dan Bridge docker0
Pada Ubuntu 22.04, Ubuntu 24.04, dan Debian 11/12, apt-get install docker.io memilih iptables-nft (didukung oleh subsistem netlink nf_tables) sebagai alternatif /usr/sbin/iptables default. Dalam kontainer gVisor standar tanpa hak administrasi jaringan atau dukungan netlink nf_tables, dockerd mengalami crash saat menginisialisasi rantai NAT DOCKER dan bridge docker0:
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. Kegagalan overlay2 pada Rootfs gofer / 9p dan Kehabisan Disk vfs
Kecuali administrator runsc luar telah mengonfigurasi terlebih dahulu mount tmpfs atau filestore khusus di /var/lib/docker, rootfs kontainer didukung oleh gofer (9p/lisafs) gVisor atau mount overlay bersarang. Mount overlay2 di atas /var/lib/docker gagal:
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
Ketika pengguna atau agen mencoba melewati kesalahan ini dengan menjalankan dockerd --storage-driver=vfs, Docker sepenuhnya menonaktifkan berbagi lapisan Copy-on-Write. Setiap instruksi RUN dalam Dockerfile melakukan penyalinan rekursif penuh (dirCopy) dari seluruh root filesystem, sehingga image 500 MB yang tersebar di 10 langkah build berubah menjadi 5 GB penulisan dan menghentikan sandbox dengan no space left on device atau timeout I/O.
4. Penolakan Mount Cgroup dan /proc pada runc Bersarang
Bahkan jika dockerd dipaksa untuk berjalan dengan dockerd --iptables=false --bridge=none --storage-driver=vfs, menjalankan docker run atau docker build memanggil runc untuk membuat kontainer internal. Dalam kontainer gVisor tanpa hak istimewa standar, runc gagal saat melakukan mount cgroup, me-mount ulang /proc, atau memuat filter seccomp internal:
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. runsc Bersarang Gagal dengan SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY)
Jika pengguna mencoba menjalankan instans runsc lain di dalam runsc menggunakan platform systrap, inisialisasi langsung dibatalkan:
1creating subprocess: sysmsg: failed to initialize thread: failed to install seccomp notify rules: device or resource busy
Baik kernel Linux maupun Sentry gVisor hanya mengizinkan paling banyak satu listener SECCOMP_FILTER_FLAG_NEW_LISTENER (SECCOMP_RET_USER_NOTIF) per hierarki task. Karena sandbox luar sudah menempati slot listener tersebut, pemanggilan seccomp(2) internal mengembalikan EBUSY (device or resource busy).
Cara gvx Menyelesaikan Eksekusi Bersarang di Dalam gVisor
gvx mengatasi kelima hambatan tersebut di dalam kernel ruang pengguna dan lapisan platformnya sendiri, sehingga rungv dapat berjalan di dalam kontainer gVisor (runsc) luar, bahkan secara rekursif di dalam kontainer gvx lain (gvx dalam gvx).

1. Fallback systrap Otomatis dari EBUSY ke Loop Futex _RUN_SYSCALL_LOOP
Dalam systrap Linux, setiap subproses stub menjalankan dua jenis thread:
- Thread eksekusi tamu (
sysmsg): Menjebak panggilan sistem tamu melaluiSECCOMP_RET_TRAP(SIGSYS) dan trampolinjmp rel32yang telah dipatch secara biner (syshandler), bertukar state register dengan Sentry melalui memori bersama danSYS_FUTEX. - Satu
syscallThreadstub (syscall_thread.go): Menjalankan panggilan sistem ruang alamat (mmap,munmap,clone) di dalam subproses stub atas nama Sentry.
Dalam gVisor hulu, syscallThread mendukung dua mode wake-up pada stub_amd64.s dan stub_arm64.s:
- Loop User-Notify Seccomp (
_RUN_SECCOMP_LOOP = 6, ketikat.seccompNotify != nil): SelamasyscallThread.init(),t.installSeccompNotify()memasang listenerSECCOMP_RET_USER_NOTIF(seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)) sehingga Sentry dapat mengirim permintaan melaluiioctl(SECCOMP_IOCTL_NOTIF_SEND)danioctl(SECCOMP_IOCTL_NOTIF_RECV). - Loop Panggilan Sistem Futex (
_RUN_SYSCALL_LOOP = 5, ketikat.seccompNotify == nil):syscallThreadmenjalankansyscall_loop(stub_amd64.s/stub_arm64.s), tertidur padaSYS_FUTEX(FUTEX_WAIT) di atas region bersamasyscallSentryMessageberukuran 2 halaman (PROT_READdi stub) dansyscallStubMessage(PROT_READ | PROT_WRITEdi stub).
gVisor hulu secara tanpa syarat memanggil t.installSeccompNotify() setiap kali seccompNotifyIsSupported bernilai true, dan membatalkan pembuatan stub jika seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, ...) mengembalikan EBUSY.
Di gvx (pkg/sentry/platform/systrap/seccomp_notify_xplat.go dan syscall_thread.go), syscallThread.init() memanggil 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}
Ketika installSeccompNotify() mengembalikan unix.EBUSY karena sandbox gVisor (runsc) luar atau sandbox gvx luar sudah menempati slot listener tunggal pada rantai filter proses:
installSeccompNotifyIfFree()mengaturseccompNotifyBusy(atomic.Bool), mencatat satu peringatan untuk proses tersebut, dan membiarkant.seccompNotify == niluntuk stub tersebut dan semua stub berikutnya.- Dalam
syscallThread.detach()(syscall_thread_amd64.godansyscall_thread_arm64.go),t.seccompNotify == nilmenetapkanregs.Rbx = _RUN_SYSCALL_LOOP(5) padaamd64(atauregs.Regs[9] = _RUN_SYSCALL_LOOPpadaarm64) alih-alih_RUN_SECCOMP_LOOP(6). - Dalam
syscallThread.syscall()(syscall_thread.go), Sentry internal mengirim panggilan sistem stub semata-mata melalui memori bersama danSYS_FUTEX:
1// pkg/sentry/platform/systrap/syscall_thread.go
2// Beri tahu thread syscall tentang permintaan panggilan sistem baru.
3atomic.AddUint32(&sentryMsg.state, 1)
4futexWakeUint32(&sentryMsg.state)
5
6// Tunggu balasan.
7futexWaitWake(&sentryMsg.state, atomic.LoadUint32(&sentryMsg.state))
Karena filter SECCOMP_RET_TRAP pada thread tamu ditumpuk secara sembarang dalam kedalaman, dan syscallThread kembali ke _RUN_SYSCALL_LOOP tanpa meminta listener notifikasi pengguna kedua, gvx berjalan dengan bersih di dalam runsc dan mendukung penyarangan rekursif (gvx di dalam gvx).
2. overlayfs Native pada Volume ext4lite Berkas Tunggal
Untuk menghindari mount overlayfs di atas root filesystem gofer (9p/lisafs) kontainer gVisor luar, -v gvx-docker:/var/lib/docker mendukung /var/lib/docker dengan filesystem internal ext4lite milik gvx yang disimpan dalam satu berkas reguler (gvx-docker.ext4).
Bagi runsc luar, gvx-docker.ext4 hanyalah satu berkas reguler yang diakses melalui pread64(2) dan pwrite64(2). Di dalam gvx, ext4lite menyediakan filesystem ext4 dengan jurnal JBD2, atribut terperluas trusted.overlay.* dan security.capability, node whiteout perangkat karakter (0:0), entri direktori d_type, dan RENAME_EXCHANGE, sehingga dockerd internal dapat menggunakan driver penyimpanan overlayfs aslinya.
3. netstack Mandiri dengan docker0 dan iptables-legacy
Di dalam gvx, dockerd tidak berinteraksi dengan antarmuka jaringan atau tabel netfilter kontainer runsc luar. Sebaliknya:
dockerdmembuat bridgedocker0dan pasanganvethdi dalamnetstackruang pengguna milikgvxsendiri.- Semua aturan
iptables-legacy(-t nat -N DOCKER,MASQUERADE,conntrack,addrtype) dieksekusi terhadap implementasi netfilter internalgvx. - Kontainer
runcbersarang me-mount hierarki/proc,/sys, dan/sys/fs/cgroupmereka di dalam tabel mountVFS2dalam memori milikgvx, tanpa pernah memanggil syscallmount(2)runscluar.
Memverifikasi docker:dind Bersarang di Dalam gVisor (atau gvx dalam gvx)
Anda dapat memverifikasi kedua skenario (rungv di dalam runsc, atau rungv di dalam rungv) dengan perintah berikut:
1# Di dalam kontainer gVisor (runsc) luar atau kontainer rungv luar:
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# Verifikasi bahwa daemon Docker internal menggunakan overlayfs asli (bukan vfs)
14docker info --format 'Storage Driver: {{.Driver}}'
15# Keluaran: Storage Driver: overlayfs
16
17# Jalankan build multi-lapisan dan eksekusi kontainer bersarang
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# Keluaran: nested-build-ok
31
32kill -INT $DIND_PID
Bacaan Lanjutan dalam Seri Ini
- gvx: Menjalankan Kontainer Linux, Docker, dan Kubernetes dalam Satu Proses Tanpa Hak Istimewa: Gambaran umum
gvxdi Windows (whpx,systrap), macOS (hvf), dan Linux (systrap). - Menjalankan Docker, docker build, dan docker compose Tanpa Root atau User Namespace: Cara
gvxmenjalankandocker:dindketikaroot,sudo,unshare(CLONE_NEWUSER),mount(2), dan/dev/fusediblokir. - Menjalankan Docker-in-Docker di Dalam gVisor dan Sandbox Bersarang dengan gvx (artikel ini)
- Lingkungan Docker Terisolasi yang Cepat untuk Rollout RL Agen AI dengan gvx: Cara menggunakan
gvxdan snapshot copy-on-writeext4liteberkas tunggal untuk menjalankan ratusan lingkungan rollout paralelSWE-benchdan RL dengan reset 100 milidetik.