GoSuda

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

By Lemon Mint views ...

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 rungv di dalam runsc tidak melewati satu pun pembatasan yang diberlakukan oleh sandbox gVisor luar. Sebaliknya, gvx membuat kernel Sentry gVisor bersarang kedua yang berjalan di ruang pengguna dengan hostlockdown sendiri (PR_SET_NO_NEW_PRIVS + seccomp), netstack dalam memori sendiri, dan volume ext4lite berkas tunggal. dockerd dan kontainer runc internal berada dalam sandbox ganda: panggilan sistem, mount, dan paket mentah mereka tidak pernah keluar dari kernel ruang pengguna gvx internal.

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

Eksekusi gvx Bersarang di Dalam gVisor Luar

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 melalui SECCOMP_RET_TRAP (SIGSYS) dan trampolin jmp rel32 yang telah dipatch secara biner (syshandler), bertukar state register dengan Sentry melalui memori bersama dan SYS_FUTEX.
  • Satu syscallThread stub (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:

  1. Loop User-Notify Seccomp (_RUN_SECCOMP_LOOP = 6, ketika t.seccompNotify != nil): Selama syscallThread.init(), t.installSeccompNotify() memasang listener SECCOMP_RET_USER_NOTIF (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)) sehingga Sentry dapat mengirim permintaan melalui ioctl(SECCOMP_IOCTL_NOTIF_SEND) dan ioctl(SECCOMP_IOCTL_NOTIF_RECV).
  2. Loop Panggilan Sistem Futex (_RUN_SYSCALL_LOOP = 5, ketika t.seccompNotify == nil): syscallThread menjalankan syscall_loop (stub_amd64.s / stub_arm64.s), tertidur pada SYS_FUTEX (FUTEX_WAIT) di atas region bersama syscallSentryMessage berukuran 2 halaman (PROT_READ di stub) dan syscallStubMessage (PROT_READ | PROT_WRITE di 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() mengatur seccompNotifyBusy (atomic.Bool), mencatat satu peringatan untuk proses tersebut, dan membiarkan t.seccompNotify == nil untuk stub tersebut dan semua stub berikutnya.
  • Dalam syscallThread.detach() (syscall_thread_amd64.go dan syscall_thread_arm64.go), t.seccompNotify == nil menetapkan regs.Rbx = _RUN_SYSCALL_LOOP (5) pada amd64 (atau regs.Regs[9] = _RUN_SYSCALL_LOOP pada arm64) alih-alih _RUN_SECCOMP_LOOP (6).
  • Dalam syscallThread.syscall() (syscall_thread.go), Sentry internal mengirim panggilan sistem stub semata-mata melalui memori bersama dan SYS_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:

  • dockerd membuat bridge docker0 dan pasangan veth di dalam netstack ruang pengguna milik gvx sendiri.
  • Semua aturan iptables-legacy (-t nat -N DOCKER, MASQUERADE, conntrack, addrtype) dieksekusi terhadap implementasi netfilter internal gvx.
  • Kontainer runc bersarang me-mount hierarki /proc, /sys, dan /sys/fs/cgroup mereka di dalam tabel mount VFS2 dalam memori milik gvx, tanpa pernah memanggil syscall mount(2) runsc luar.

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

  1. gvx: Menjalankan Kontainer Linux, Docker, dan Kubernetes dalam Satu Proses Tanpa Hak Istimewa: Gambaran umum gvx di Windows (whpx, systrap), macOS (hvf), dan Linux (systrap).
  2. Menjalankan Docker, docker build, dan docker compose Tanpa Root atau User Namespace: Cara gvx menjalankan docker:dind ketika root, sudo, unshare(CLONE_NEWUSER), mount(2), dan /dev/fuse diblokir.
  3. Menjalankan Docker-in-Docker di Dalam gVisor dan Sandbox Bersarang dengan gvx (artikel ini)
  4. Lingkungan Docker Terisolasi yang Cepat untuk Rollout RL Agen AI dengan gvx: Cara menggunakan gvx dan snapshot copy-on-write ext4lite berkas tunggal untuk menjalankan ratusan lingkungan rollout paralel SWE-bench dan RL dengan reset 100 milidetik.