Lingkungan Docker Terisolasi yang Cepat untuk Rollout RL Agen AI dengan gvx

Hambatan Keamanan dan Infrastruktur dalam Rollout RL Coding Agent
Melatih dan mengevaluasi software engineering agent di berbagai benchmark dan framework reinforcement learning (RL) seperti SWE-bench, SWE-ReX, R2E-Gym, SWE-Gym, Verl, dan OpenHands membutuhkan eksekusi ribuan rollout konkuren terhadap kode untrusted yang dihasilkan LLM. Dalam tugas software engineering dunia nyata, agent tidak hanya menyunting file teks: agent menjalankan docker build, menyalakan database dan microservice melalui docker compose, mengeksekusi suite integration test, dan memeriksa log container.
Pada skala rollout (128 hingga 1.024 episode konkuren per cluster), infrastruktur container yang ada menghadapi empat hambatan keamanan dan performa yang bersifat sistemik:
- Kontensi Lock
dockerdHost dan Kebocoran State: Mengarahkan ratusan worker RL konkuren ke satu/var/run/docker.sockhost memberikan setiap agent akses setara root ke daemon host, menyerialkan ekstraksi layer image, menghabiskan alokasi subnetdocker0, memicu errorAnother app is currently holding the xtables lockpadaiptableshost, dan membocorkan container yatim lintas episode. - Risiko Container Escape pada DinD Privileged: Menjalankan container
docker:dindstandar memerlukan--privileged(CAP_SYS_ADMIN), yang mengekspos block device host mentah, cgroup release agent, dan kernel module kepada rollout RL yang untrusted. - Batasan Virtualisasi Perangkat Keras (
/dev/kvm): Runtime MicroVM (Firecracker,QEMU,Kata Containers) mengisolasi kernel internal dengan bersih, tetapi memerlukan/dev/kvm. Pod Kubernetes standar tanpa nested virtualization, instance cloud bare-metal tempat KVM dinonaktifkan, dan sandboxgVisor(runsc) yang ada tidak dapat menjalankan microVM KVM. - Latensi Reset Episode (
env.reset()) yang Lambat: Memulai lingkungandocker:dindbaru serta menarik atau membongkar base image untuk setiap episode RL memakan waktu 10 hingga 60 detik, sehingga worker training GPU menganggur selama menunggu reset lingkungan.
gvx (CLI rungv dan Go SDK z.gfire.dev/gvx/pkg/gvx) menyediakan runtime rollout yang aman dengan pertahanan berlapis (defense-in-depth), yang mengatasi keempat hambatan tersebut. Setiap lingkungan rollout berjalan sebagai satu proses user-space tanpa privilese yang mengunci dirinya sendiri (diisolasi oleh kernel Sentry gVisor milik Google, pkg/hostlockdown, dan netstack dalam memori), dengan seluruh state /var/lib/docker-nya berada di dalam satu file .ext4 copy-on-write tunggal (ext4lite).
Keamanan Zero-Privilege untuk Rollout Agent yang Untrusted: Setiap episode
gvxmengunci proses OS host miliknya sendiri sebelum menjalankan kode guest (PR_SET_NO_NEW_PRIVS+ BPFseccomppada Linux, Seatbeltdeny defaultpada macOS, Restricted Token + Job Object pada Windows). Bahkan jika agent RL menjalankandocker run --privilegedatau perintah destruktifrm -rf /di dalam episodenya, operasi tersebut tetap terbatas secara ketat pada kernel user-spaceSentrymilik episode itu dan file volume.ext4yang bersifat sementara.
Arsitektur Rollout: Instance Unprivileged Paralel + Snapshot Reflink
Dalam gvx, setiap worker rollout adalah proses OS unprivileged biasa dengan kernel gVisor Sentry-nya sendiri, netstack user-space-nya sendiri (10.0.2.15/24), dan volume blok ext4lite-nya sendiri (ep-N.ext4), sementara berbagi cache image OCI erofs yang bersifat read-only dan content-addressable melalui page cache host.

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, extended attribute trusted.overlay.*, whiteout device node, dan metadata image) di dalam satu file reguler host, pengelolaan siklus hidup episode menjadi satu operasi file:
env.reset()100 Milidetik melaluicp --reflink=always:- Anda membangun file
golden-docker.ext4sekali saja yang berisi base image yang sudah di-pull 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 clone Copy-on-Write yang independen dalam waktu kurang dari 100 milidetik tanpa menyalin blok disk yang mendasarinya. - Hanya blok 4 KiB yang di-dirty selama episode spesifik tersebut yang menggunakan ruang disk tambahan.
- Anda membangun file
- Teardown Tanpa Residu (
env.close()):- Menghentikan proses
rungv(sb.Close()di Go) dan melepas tautanep-0042.ext4membebaskan 100% proses, socket jaringan, mount, dan penyimpanan episode dalam sekitar 10 milidetik. Tidak ada yang pernah di-mount pada tabel mount kernel host atau didaftarkan di cgroup host.
- Menghentikan proses
Alur Kerja Langkah demi Langkah: Snapshot .ext4 Golden dan Reset Episode Cepat
Anda dapat memasang rungv pada setiap node worker rollout, baik dengan mengunduh binary prebuilt yang diattest 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 Golden Sekali Saja
1mkdir -p /srv/rl-snapshots /srv/rl-episodes
2
3# 1. Mulai instance seed docker:dind yang didukung 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 instance seed dengan baik-baik 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 meng-clone golden-docker.ext4 melalui reflink dan mem-boot instance docker:dind yang terisolasi dengan semua image sudah tersedia di /var/lib/docker:
1EPISODE_ID="ep-0042"
2SOCK="/srv/rl-episodes/${EPISODE_ID}.sock"
3
4# Clone CoW 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 unprivileged 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 kalkulasi 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 environment 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. Clone reflink volume golden ext4lite
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 timeout 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 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 Agent
| Metrik / Persyaratan | Shared Host dockerd | Privileged docker:dind | Firecracker / KVM MicroVM | Standard gVisor (runsc) | gvx (rungv / pkg/gvx) |
|---|---|---|---|---|---|
Membutuhkan root host atau --privileged | Ya (docker.sock = root) | Ya (--privileged) | Membutuhkan /dev/kvm + setup TAP | Ya (atau userns + mount) | Tidak (satu proses unprivileged) |
Berjalan di dalam Pod K8s unprivileged / runsc yang ada | Tidak | Tidak | Tidak (tanpa /dev/kvm) | Gagal (EBUSY pada nested seccomp) | Ya (fallback loop futex otomatis) |
Mendukung docker build & docker compose di dalam | Berbagi daemon host | Ya | Ya | Gagal tanpa tuning DinD host | Ya (overlayfs di atas ext4lite) |
| Isolasi jaringan per episode | docker0 / lock xtables bersama | Namespace terpisah | NIC VM terpisah | Netns terpisah | Diisolasi dalam netstack di dalam proses |
Latensi reset episode hangat (env.reset()) | 5 detik hingga 30 detik (prune container/volume) | 10 detik hingga 60 detik (daemon dingin) | ~150 ms (snapshot VM) | 5 detik hingga 20 detik | ~100 ms (cp --reflink=always .ext4) |
| Pengembangan lokal lintas platform (Windows / macOS / Linux) | Memerlukan VM Linux | Memerlukan VM Linux | Hanya Linux KVM | Hanya Linux | Native di Windows, macOS, dan Linux |
Bacaan Lanjutan dalam Seri Ini
- gvx: Menjalankan Container Linux, Docker, dan Kubernetes dalam Satu Proses Unprivileged: Walkthrough arsitektur lengkap
gvxdi Windows (whpx,systrap), macOS (hvf), dan Linux (systrap). - Menjalankan Docker, docker build, dan docker compose Tanpa Root atau User Namespace: Panduan bootstrap cepat 3 langkah dan troubleshooting ketika
sudo,unshare(CLONE_NEWUSER),mount(2), dan/dev/fusediblokir. - Menjalankan Docker-in-Docker di Dalam gVisor dan Sandbox Bersarang dengan gvx: Penjelasan mendalam mengapa
docker.iodanrunscbersarang gagal di dalamgVisor(runsc) dan bagaimanagvxberalih ke loop futex_RUN_SYSCALL_LOOPsaat terjadiEBUSY. - Lingkungan Docker Terisolasi yang Cepat untuk Rollout RL Agent AI dengan gvx (postingan ini)