Medii Docker rapide și izolate pentru AI Agent RL Rollouts cu gvx

Blocajul de securitate și infrastructură în rulările RL ale agenților de programare
Antrenarea și evaluarea agenților de inginerie software pe benchmark-uri și framework-uri de reinforcement learning (RL), precum SWE-bench, SWE-ReX, R2E-Gym, SWE-Gym, Verl și OpenHands, necesită executarea a mii de rulări concurente de cod nesigur, generat de LLM-uri. În sarcinile reale de inginerie software, un agent nu se limitează la editarea fișierelor text: el rulează docker build, pornește baze de date și microservicii prin docker compose, execută suite de teste de integrare și inspectează jurnalele containerelor.
La scara rulărilor (128 până la 1.024 de episoade concurente per cluster), infrastructura actuală de containere se confruntă cu patru blocaje sistemice de securitate și performanță:
- Blocarea (lock contention)
dockerdde pe gazdă și scurgerea de stare: Îndreptarea a sute de workeri RL concurenți către un singur/var/run/docker.sockde pe gazdă oferă fiecărui agent acces echivalent cu root la daemonul gazdei, serializează extragerea straturilor de imagini, epuizează alocările de subrețeadocker0, declanșează eroriAnother app is currently holding the xtables lockîniptablesde pe gazdă și lasă containere orfane între episoade. - Riscul de evadare din containerul DinD privilegiat: Rularea containerelor standard
docker:dindnecesită--privileged(CAP_SYS_ADMIN), ceea ce expune dispozitivele bloc brute ale gazdei, agenții de eliberare cgroup și modulele kernel rulărilor RL nesigure. - Constrângeri ale virtualizării hardware (
/dev/kvm): Runtime-urile MicroVM (Firecracker,QEMU,Kata Containers) izolează curat kernelurile interne, dar necesită/dev/kvm. Podurile standard Kubernetes fără virtualizare imbricată, instanțele cloud bare-metal unde KVM este dezactivat și sandbox-urile existentegVisor(runsc) nu pot lansa microVM-uri KVM. - Latență ridicată la resetarea episodului (
env.reset()): Pornirea unui mediudocker:dindproaspăt și descărcarea sau dezarhivarea imaginilor de bază pentru fiecare episod RL durează între 10 și 60 de secunde, lăsând workerii GPU de antrenare inactivi în așteptarea resetării mediilor.
gvx (CLI-ul rungv și SDK-ul Go z.gfire.dev/gvx/pkg/gvx) oferă un runtime de rulare sigur, cu efort redus și eficient din punct de vedere al tokenilor, care rezolvă toate cele patru blocaje. Fiecare mediu de rulare funcționează ca un singur proces unprivileged, auto-blocat în spațiul utilizatorului (izolat de kernelul Sentry gVisor de la Google, de pkg/hostlockdown și de un netstack în memorie), a cărui stare întreagă /var/lib/docker se află în interiorul unui singur fișier .ext4 copy-on-write (ext4lite).
Execuție cu efort redus și eficientă din punct de vedere al tokenilor pentru agenți: Atunci când
docker buildsaudocker composefuncționează din prima în fiecare episod (sau pot fi inițializate de agent printr-un singur tur de 3 comenzi prinrungv), rulările RL nu irosesc tokenii ferestrei de context sau bugetul de pași pentru depanarea eșecurilorservice docker startcauzate deulimit, a eroriloriptables-nftsau a respingerilorpivot_rootalerunc. Fiecare token din traiectorie este dedicat rezolvării sarcinii reale de inginerie software.
Securitate zero-privilegii pentru rulările nesigure ale agenților: Fiecare episod
gvxîși blochează propriul proces al sistemului de operare gazdă înainte de a rula codul din oaspete (PR_SET_NO_NEW_PRIVS+seccompBPF pe Linux,deny defaultSeatbelt pe macOS, Restricted Token + Job Object pe Windows). Chiar dacă un agent RL executădocker run --privilegedsau comenzi distructiverm -rf /în cadrul episodului său, aceste operații rămân strict confinate în kernelulSentrydin spațiul utilizatorului al episodului și în fișierul volum.ext4efemer.
Arhitectura rulării: instanțe paralele unprivileged + snapshot-uri reflink
În gvx, fiecare worker de rulare este un proces OS obișnuit, fără privilegii, cu propriul kernel Sentry gVisor, propriul netstack din spațiul utilizatorului (10.0.2.15/24) și propriul volum de bloc ext4lite (ep-N.ext4), în timp ce partajează, prin cache-ul de pagini al gazdei, o imagine OCI erofs adresabilă prin conținut și doar în citire.

De ce volumele ext4lite dintr-un singur fișier schimbă economia resetării RL
Deoarece filesystem-ul ext4lite stochează un întreg volum ext4 cu jurnal JBD2 (inclusiv directoarele straturilor overlayfs ale Docker, atributele extinse trusted.overlay.*, nodurile de dispozitiv whiteout și metadatele imaginilor) într-un singur fișier obișnuit al gazdei, gestionarea ciclului de viață al episodului devine o singură operație asupra fișierului:
env.reset()de 100 de milisecunde princp --reflink=always:- Construiți o singură dată un fișier
golden-docker.ext4care conține imaginile de bază preextrase (python:3.11,postgres:16,redis:7sau imagini de bazăSWE-benchspecifice sarcinii). - La începutul fiecărui episod RL,
cp --reflink=always golden-docker.ext4 ep-0042.ext4(pe XFS, Btrfs, ZFS sau macOS APFS) creează o clonă Copy-on-Write independentă în mai puțin de 100 de milisecunde, fără a copia blocurile de disc subiacente. - Doar blocurile de 4 KiB modificate în timpul acelui episod consumă spațiu suplimentar pe disc.
- Construiți o singură dată un fișier
- Dezafectare fără reziduuri (
env.close()):- Terminarea procesului
rungv(sb.Close()în Go) și ștergereaep-0042.ext4recuperează 100% din procesele, socket-urile de rețea, montările și stocarea episodului în aproximativ 10 milisecunde. Nimic nu este montat vreodată în tabela de montări a kernelului gazdă și nimic nu este înregistrat în cgroup-urile gazdei.
- Terminarea procesului
Flux de lucru pas cu pas: snapshot .ext4 de referință și resetare rapidă a episodului
Puteți instala rungv pe fiecare nod worker de rulare fie descărcând binarul precompilat atestat SLSA de pe 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), fie rulând CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest.
Pasul 1: Creați volumul de referință docker:dind o singură dată
1mkdir -p /srv/rl-snapshots /srv/rl-episodes
2
3# 1. Pornește o instanță seed docker:dind susținută de 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. Preîncarcă imaginile de bază și cache-ul de build în 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. Oprește elegant instanța seed, astfel încât ext4lite să golească și să compacteze imaginea
20kill -INT $SEED_PID
21wait $SEED_PID
Pasul 2: Lansați episoade RL paralele în 100 de milisecunde
Fiecare worker de rulare paralel clonează golden-docker.ext4 prin reflink și pornește o instanță docker:dind izolată, cu toate imaginile deja prezente în /var/lib/docker:
1EPISODE_ID="ep-0042"
2SOCK="/srv/rl-episodes/${EPISODE_ID}.sock"
3
4# Clonă Copy-on-Write instantanee (~100 ms pe XFS/Btrfs/APFS)
5cp --reflink=always \
6 /srv/rl-snapshots/golden-docker.ext4 \
7 "/srv/rl-episodes/${EPISODE_ID}.ext4"
8
9# Lansează episodul izolat, fără privilegii, cu 4 GiB RAM și 2 CPU-uri
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# În interiorul workerului de rulare, imaginile sunt disponibile imediat, fără descărcare din rețea
22docker images
23
24# Dezafectarea episodului după calcularea recompensei (~10 ms)
25kill -KILL $EP_PID
26rm -f "/srv/rl-episodes/${EPISODE_ID}.ext4" "${SOCK}"
Pool de rulare programatic folosind SDK-ul Go z.gfire.dev/gvx/pkg/gvx
Dacă serverul de mediu RL sau harness-ul de evaluare este scris în Go (sau apelează un daemon de rulare Go prin gRPC), puteți gestiona episoadele de containere paralele direct prin 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. Clonează prin reflink volumul ext4lite de referință
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. Aplică timeout-ul de ceas per episod
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, // limită de 4 GiB
55 CPUMillis: 2000, // 2,0 CPU-uri
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}
Comparație a runtime-urilor de sandbox pentru agenți RL
| Metrică / Cerință | dockerd partajat pe gazdă | docker:dind privilegiat | Firecracker / KVM MicroVM | gVisor standard (runsc) | gvx (rungv / pkg/gvx) |
|---|---|---|---|---|---|
root pe gazdă sau --privileged necesar | Da (docker.sock = root) | Da (--privileged) | Necesită /dev/kvm + configurare TAP | Da (sau userns + mount) | Nu (un singur proces unprivileged) |
Funcționează în Pod-uri K8s unprivileged / runsc existent | Nu | Nu | Nu (fără /dev/kvm) | Eșuează (EBUSY la seccomp imbricat) | Da (buclă futex de rezervă automată) |
Suportă docker build și docker compose în interior | Partajează daemonul gazdei | Da | Da | Eșuează fără reglaj DinD pe gazdă | Da (overlayfs pe ext4lite) |
| Izolarea rețelei per episod | docker0 / lock xtables partajat | Namespace separat | NIC VM separat | Netns separat | netstack izolat în proces |
Latența resetării episodului la cald (env.reset()) | 5 s până la 30 s (prune container/volum) | 10 s până la 60 s (daemon rece) | ~150 ms (snapshot VM) | 5 s până la 20 s | ~100 ms (cp --reflink=always .ext4) |
| Dezvoltare locală multiplatformă (Windows / macOS / Linux) | Necesită VM Linux | Necesită VM Linux | Doar Linux KVM | Doar Linux | Nativ pe Windows, macOS și Linux |
Lecturi suplimentare în această serie
- gvx: Rularea containerelor Linux, Docker și Kubernetes într-un singur proces unprivileged: Prezentare completă a arhitecturii
gvxpe Windows (whpx,systrap), macOS (hvf) și Linux (systrap). - Rularea Docker, docker build și docker compose fără root sau user namespaces: Ghid rapid de inițializare în 3 pași și de depanare, atunci când
sudo,unshare(CLONE_NEWUSER),mount(2)și/dev/fusesunt blocate. - Rularea Docker-in-Docker în gVisor și în sandbox-uri imbricate cu gvx: Analiză detaliată a motivelor pentru care
docker.ioșirunscimbricat eșuează îngVisor(runsc) și a modului în caregvxtrece la bucla futex_RUN_SYSCALL_LOOPlaEBUSY. - Medii Docker izolate rapide pentru rulările RL ale agenților AI cu gvx (această postare)