GoSuda

Lingkungan Docker Terisolasi yang Cepat untuk AI Agent RL Rollouts dengan gvx

By Lemon Mint views ...

Hambatan Keamanan dan Infrastruktur dalam Rollout RL Coding Agent

Pelatihan dan evaluasi agen rekayasa perangkat lunak lintas benchmark dan kerangka kerja reinforcement learning (RL) seperti SWE-bench, SWE-ReX, R2E-Gym, SWE-Gym, Verl, dan OpenHands memerlukan eksekusi ribuan rollout konkuren terhadap kode yang tidak tepercaya dan dihasilkan oleh LLM. Dalam tugas rekayasa perangkat lunak dunia nyata, agen tidak sekadar menyunting berkas teks: agen menjalankan docker build, menjalankan basis data dan microservice melalui docker compose, mengeksekusi rangkaian pengujian integrasi, dan memeriksa log container.

Pada skala rollout (128 hingga 1.024 episode konkuren per klaster), infrastruktur container yang ada menghadapi empat hambatan keamanan dan kinerja sistemik:

  1. Perebutan Lock dockerd Host dan Kebocoran State: Mengarahkan ratusan worker RL konkuren ke satu /var/run/docker.sock host memberikan setiap agen akses setara root ke daemon host, menyerialkan ekstraksi layer image, menghabiskan alokasi subnet docker0, memicu galat Another app is currently holding the xtables lock pada iptables host, dan membocorkan container yatim lintas episode.
  2. Risiko Container Escape pada DinD Istimewa: Menjalankan container docker:dind standar memerlukan --privileged (CAP_SYS_ADMIN), yang mengekspos block device host mentah, release agent cgroup, dan kernel module kepada rollout RL yang tidak tepercaya.
  3. Batasan Virtualisasi Perangkat Keras (/dev/kvm): Runtime MicroVM (Firecracker, QEMU, Kata Containers) mengisolasi kernel internal secara bersih, tetapi memerlukan /dev/kvm. Pod Kubernetes standar tanpa nested virtualization, instans cloud bare-metal tempat KVM dinonaktifkan, dan sandbox gVisor (runsc) yang ada tidak dapat menjalankan microVM KVM.
  4. Latensi Reset Episode (env.reset()) yang Lambat: Memulai lingkungan docker:dind baru serta menarik atau mengekstrak base image untuk setiap episode RL memerlukan 10 hingga 60 detik, sehingga worker pelatihan GPU menganggur selama menunggu reset lingkungan.

gvx (CLI rungv dan Go SDK z.gfire.dev/gvx/pkg/gvx) menyediakan runtime rollout yang aman, berupaya rendah, dan efisien token yang mengatasi keempat hambatan tersebut. Setiap lingkungan rollout dijalankan sebagai satu proses user-space tanpa privilege dan terkunci sendiri (diisolasi oleh kernel Sentry gVisor milik Google, pkg/hostlockdown, dan netstack dalam memori) yang seluruh state /var/lib/docker-nya berada di dalam satu file .ext4 copy-on-write (ext4lite).

Eksekusi Berupaya Rendah dan Efisien Token untuk Agen: Ketika docker build atau docker compose berfungsi langsung di dalam setiap episode (atau dapat di-bootstrap oleh agen dalam satu giliran 3 perintah melalui rungv), rollout RL tidak membuang token context window maupun anggaran langkah untuk memecahkan masalah kegagalan service docker start ulimit, galat iptables-nft, atau penolakan pivot_root oleh runc. Setiap token dalam trajektori diarahkan untuk menyelesaikan tugas rekayasa perangkat lunak yang sebenarnya.

Keamanan Tanpa Privilege untuk Rollout Agen yang Tidak Tepercaya: Setiap episode gvx mengunci proses OS host miliknya sendiri sebelum menjalankan kode guest (PR_SET_NO_NEW_PRIVS + seccomp BPF pada Linux, deny default Seatbelt pada macOS, Restricted Token + Job Object pada Windows). Bahkan jika agen RL menjalankan docker run --privileged atau perintah destruktif rm -rf / di dalam episodenya, operasi tersebut tetap terbatas secara ketat pada kernel Sentry user-space episode tersebut dan file volume .ext4 yang bersifat sementara.


Arsitektur Rollout: Instans Paralel Tanpa Privilege + Snapshot Reflink

Dalam gvx, setiap worker rollout adalah proses OS tanpa privilege biasa dengan kernel gVisor Sentry sendiri, netstack user-space sendiri (10.0.2.15/24), dan volume blok ext4lite sendiri (ep-N.ext4), sementara itu berbagi cache image OCI erofs yang bersifat read-only dan content-addressable melalui page cache host.

Arsitektur Rollout RL Agen Paralel dengan gvx

Mengapa Volume ext4lite Berkas Tunggal Mengubah Ekonomi Reset RL

Karena filesystem ext4lite milik gvx menyimpan seluruh volume ext4 ber-journal JBD2 (termasuk direktori layer overlayfs Docker, atribut extended trusted.overlay.*, node perangkat whiteout, dan metadata image) di dalam satu file reguler host, manajemen siklus hidup episode menjadi satu operasi file:

  1. env.reset() 100 Milidetik melalui cp --reflink=always:
    • Anda membangun file golden-docker.ext4 sekali yang berisi base image yang sudah ditarik sebelumnya (python:3.11, postgres:16, redis:7, atau base image SWE-bench spesifik tugas).
    • Pada awal setiap episode RL, cp --reflink=always golden-docker.ext4 ep-0042.ext4 (pada XFS, Btrfs, ZFS, atau APFS macOS) membuat klon Copy-on-Write yang independen dalam waktu kurang dari 100 milidetik tanpa menyalin blok disk yang mendasarinya.
    • Hanya blok 4 KiB yang diubah selama episode tertentu yang memakan ruang disk tambahan.
  2. Teardown Tanpa Sisa (env.close()):
    • Menghentikan proses rungv (sb.Close() pada Go) dan menghapus tautan ep-0042.ext4 membebaskan 100% proses, soket jaringan, mount, dan penyimpanan episode tersebut dalam ~10 milidetik. Tidak ada yang pernah di-mount dalam tabel mount kernel host atau terdaftar di cgroup host.

Alur Kerja Langkah demi Langkah: Snapshot .ext4 Emas dan Reset Episode Cepat

Anda dapat memasang rungv pada setiap node worker rollout baik dengan mengunduh binary prebuilt yang diatestasi SLSA dari GitHub Releases (gh attestation verify rungv-v0.2.0-linux-amd64.tar.gz --repo gfire-sigs/gvx --signer-workflow gfire-sigs/gvx/.github/workflows/release.yml --deny-self-hosted-runners) atau dengan menjalankan CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest.

Langkah 1: Buat Volume docker:dind Emas Sekali Saja

 1mkdir -p /srv/rl-snapshots /srv/rl-episodes
 2
 3# 1. Jalankan instans seed docker:dind yang didukung oleh golden-docker.ext4
 4rungv run \
 5  -p unix:/srv/rl-snapshots/seed.sock:2375 \
 6  --mount type=volume,src=/srv/rl-snapshots/golden-docker.ext4,dst=/var/lib/docker \
 7  -e DOCKER_TLS_CERTDIR= \
 8  docker:dind --tls=false &
 9SEED_PID=$!
10
11# 2. Isi awal base image dan build cache di dalam golden-docker.ext4
12export DOCKER_HOST="unix:///srv/rl-snapshots/seed.sock"
13until docker info >/dev/null 2>&1; do sleep 0.5; done
14
15docker pull python:3.11-slim
16docker pull redis:7-alpine
17docker pull postgres:16-alpine
18
19# 3. Hentikan instans seed secara bertahap agar ext4lite melakukan flush dan kompaksi image
20kill -INT $SEED_PID
21wait $SEED_PID

Langkah 2: Jalankan Episode RL Paralel dalam 100 Milidetik

Setiap worker rollout paralel mengklon golden-docker.ext4 melalui reflink dan menjalankan instans docker:dind terisolasi dengan semua image sudah tersedia di /var/lib/docker:

 1EPISODE_ID="ep-0042"
 2SOCK="/srv/rl-episodes/${EPISODE_ID}.sock"
 3
 4# Klon Copy-on-Write instan (~100 ms pada XFS/Btrfs/APFS)
 5cp --reflink=always \
 6  /srv/rl-snapshots/golden-docker.ext4 \
 7  "/srv/rl-episodes/${EPISODE_ID}.ext4"
 8
 9# Jalankan episode tanpa privilege yang terisolasi dengan RAM 4 GiB dan 2 CPU
10rungv run \
11  -m 4g --cpus 2 \
12  -p "unix:${SOCK}:2375" \
13  --mount "type=volume,src=/srv/rl-episodes/${EPISODE_ID}.ext4,dst=/var/lib/docker" \
14  -e DOCKER_TLS_CERTDIR= \
15  docker:dind --tls=false &
16EP_PID=$!
17
18export DOCKER_HOST="unix://${SOCK}"
19until docker info >/dev/null 2>&1; do sleep 0.2; done
20
21# Di dalam worker rollout, image langsung tersedia tanpa pull dari jaringan
22docker images
23
24# Teardown episode setelah perhitungan reward (~10 ms)
25kill -KILL $EP_PID
26rm -f "/srv/rl-episodes/${EPISODE_ID}.ext4" "${SOCK}"

Pool Rollout Terprogram Menggunakan Go SDK z.gfire.dev/gvx/pkg/gvx

Jika server lingkungan RL atau harness evaluasi Anda ditulis dalam Go (atau memanggil daemon rollout Go melalui gRPC), Anda dapat mengelola episode container paralel secara langsung melalui z.gfire.dev/gvx/pkg/gvx:

 1package main
 2
 3import (
 4	"bytes"
 5	"context"
 6	"fmt"
 7	"log"
 8	"os"
 9	"os/exec"
10	"path/filepath"
11	"time"
12
13	"z.gfire.dev/gvx/pkg/gvx"
14)
15
16func runEpisode(ctx context.Context, goldenExt4, scratchDir string, epID int) error {
17	epExt4 := filepath.Join(scratchDir, fmt.Sprintf("ep-%04d.ext4", epID))
18	defer os.Remove(epExt4)
19
20	// 1. Klon reflink volume ext4lite emas
21	cpCmd := exec.CommandContext(ctx, "cp", "--reflink=always", goldenExt4, epExt4)
22	if err := cpCmd.Run(); err != nil {
23		return fmt.Errorf("clone golden volume: %w", err)
24	}
25
26	// 2. Terapkan batas waktu wall-clock per episode
27	epCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
28	defer cancel()
29
30	var stdout, stderr bytes.Buffer
31	sb, err := gvx.New(epCtx, gvx.Config{
32		Platform:               gvx.PlatformAuto,
33		NetworkMode:            gvx.NetModeNAT,
34		NetRaw:                 true,
35		AllowPacketSocketWrite: true,
36		AllowSUID:              true,
37		OCIImage:               "docker:dind",
38		Entrypoint:             []string{"sh", "-c"},
39		EntrypointSet:          true,
40		Cmd: []string{
41			"dockerd-entrypoint.sh --tls=false >/var/log/dockerd.log 2>&1 & " +
42				"until docker info >/dev/null 2>&1; do sleep 0.2; done && " +
43				"docker run --rm python:3.11-slim python -c 'print(6 * 7)'",
44		},
45		Envv: []string{"DOCKER_TLS_CERTDIR="},
46		Mounts: []gvx.Mount{
47			{
48				Kind:      gvx.MountVolume,
49				ImagePath: epExt4,
50				GuestPath: "/var/lib/docker",
51			},
52		},
53		Resources: gvx.Resources{
54			MemoryBytes: 4 << 30, // Batas maksimum 4 GiB
55			CPUMillis:   2000,    // 2.0 CPU
56		},
57		Stdout: &stdout,
58		Stderr: &stderr,
59	})
60	if err != nil {
61		return fmt.Errorf("init episode %d: %w", epID, err)
62	}
63	defer sb.Close()
64
65	exitCode, err := sb.Run(epCtx)
66	if err != nil {
67		return fmt.Errorf("run episode %d: %w", epID, err)
68	}
69
70	log.Printf("episode %d finished: exit=%d stdout=%q", epID, exitCode, stdout.String())
71	return nil
72}
73
74func main() {
75	ctx := context.Background()
76	if err := runEpisode(ctx, "/srv/rl-snapshots/golden-docker.ext4", "/srv/rl-episodes", 1); err != nil {
77		log.Fatal(err)
78	}
79}

Perbandingan Runtime Sandbox RL Agen

Metrik / PersyaratanShared Host dockerdPrivileged docker:dindFirecracker / KVM MicroVMgVisor Standar (runsc)gvx (rungv / pkg/gvx)
Memerlukan root host atau --privilegedYa (docker.sock = root)Ya (--privileged)Membutuhkan /dev/kvm + pengaturan TAPYa (atau userns + mount)Tidak (satu proses tanpa privilege)
Berfungsi di dalam Pod K8s tanpa privilege / runsc yang adaTidakTidakTidak (tanpa /dev/kvm)Gagal (EBUSY pada seccomp bersarang)Ya (fallback futex loop otomatis)
Mendukung docker build & docker compose di dalamBerbagi daemon hostYaYaGagal tanpa penyetelan DinD hostYa (overlayfs pada ext4lite)
Isolasi jaringan per episodedocker0 / kunci xtables bersamaNamespace terpisahNIC VM terpisahNetns terpisahTerisolasi dalam netstack di dalam proses
Latensi reset episode hangat (env.reset())5 dtk hingga 30 dtk (prune container/volume)10 dtk hingga 60 dtk (daemon dingin)~150 ms (snapshot VM)5 dtk hingga 20 dtk~100 ms (cp --reflink=always .ext4)
Pengembangan lokal lintas platform (Windows / macOS / Linux)Membutuhkan VM LinuxMembutuhkan VM LinuxHanya Linux KVMHanya LinuxNative di Windows, macOS, dan Linux

Bacaan Lanjutan dalam Seri Ini

  1. gvx: Menjalankan Container Linux, Docker, dan Kubernetes dalam Satu Proses Tanpa Privilege: Penjelasan arsitektur lengkap gvx di Windows (whpx, systrap), macOS (hvf), dan Linux (systrap).
  2. Menjalankan Docker, docker build, dan docker compose Tanpa Root atau User Namespace: Panduan bootstrap cepat 3 langkah dan pemecahan masalah ketika sudo, unshare(CLONE_NEWUSER), mount(2), dan /dev/fuse diblokir.
  3. Menjalankan Docker-in-Docker di dalam gVisor dan Sandbox Bersarang dengan gvx: Pembahasan mendalam mengapa docker.io dan runsc bersarang gagal di dalam gVisor (runsc) dan bagaimana gvx beralih ke loop futex _RUN_SYSCALL_LOOP saat EBUSY.
  4. Lingkungan Docker Terisolasi Cepat untuk Rollout RL Agen AI dengan gvx (artikel ini)