GoSuda

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

By Lemon Mint views ...

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:

  1. Kontensi Lock dockerd Host dan Kebocoran State: Mengarahkan ratusan worker RL konkuren ke satu /var/run/docker.sock host memberikan setiap agent akses setara root ke daemon host, menyerialkan ekstraksi layer image, menghabiskan alokasi subnet docker0, memicu error Another app is currently holding the xtables lock pada iptables host, dan membocorkan container yatim lintas episode.
  2. Risiko Container Escape pada DinD Privileged: Menjalankan container docker:dind standar memerlukan --privileged (CAP_SYS_ADMIN), yang mengekspos block device host mentah, cgroup release agent, dan kernel module kepada rollout RL yang untrusted.
  3. 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 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 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 gvx mengunci proses OS host miliknya sendiri sebelum menjalankan kode guest (PR_SET_NO_NEW_PRIVS + BPF seccomp pada Linux, Seatbelt deny default pada macOS, Restricted Token + Job Object pada Windows). Bahkan jika agent RL menjalankan docker run --privileged atau perintah destruktif rm -rf / di dalam episodenya, operasi tersebut tetap terbatas secara ketat pada kernel user-space Sentry milik episode itu dan file volume .ext4 yang 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.

Arsitektur Rollout RL Agent 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, extended attribute trusted.overlay.*, whiteout device node, dan metadata image) di dalam satu file reguler host, pengelolaan siklus hidup episode menjadi satu operasi file:

  1. env.reset() 100 Milidetik melalui cp --reflink=always:
    • Anda membangun file golden-docker.ext4 sekali 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.
  2. Teardown Tanpa Residu (env.close()):
    • Menghentikan proses rungv (sb.Close() di Go) dan melepas tautan ep-0042.ext4 membebaskan 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.

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 / PersyaratanShared Host dockerdPrivileged docker:dindFirecracker / KVM MicroVMStandard gVisor (runsc)gvx (rungv / pkg/gvx)
Membutuhkan root host atau --privilegedYa (docker.sock = root)Ya (--privileged)Membutuhkan /dev/kvm + setup TAPYa (atau userns + mount)Tidak (satu proses unprivileged)
Berjalan di dalam Pod K8s unprivileged / runsc yang adaTidakTidakTidak (tanpa /dev/kvm)Gagal (EBUSY pada nested seccomp)Ya (fallback loop futex otomatis)
Mendukung docker build & docker compose di dalamBerbagi daemon hostYaYaGagal tanpa tuning DinD hostYa (overlayfs di atas ext4lite)
Isolasi jaringan per episodedocker0 / lock xtables bersamaNamespace terpisahNIC VM terpisahNetns terpisahDiisolasi 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 LinuxMemerlukan 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 Unprivileged: Walkthrough 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 troubleshooting ketika sudo, unshare(CLONE_NEWUSER), mount(2), dan /dev/fuse diblokir.
  3. Menjalankan Docker-in-Docker di Dalam gVisor dan Sandbox Bersarang dengan gvx: Penjelasan mendalam mengapa docker.io dan runsc bersarang gagal di dalam gVisor (runsc) dan bagaimana gvx beralih ke loop futex _RUN_SYSCALL_LOOP saat terjadi EBUSY.
  4. Lingkungan Docker Terisolasi yang Cepat untuk Rollout RL Agent AI dengan gvx (postingan ini)