GoSuda

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

By Lemon Mint views ...

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ță:

  1. Blocarea (lock contention) dockerd de pe gazdă și scurgerea de stare: Îndreptarea a sute de workeri RL concurenți către un singur /var/run/docker.sock de pe gazdă oferă fiecărui agent acces echivalent cu root la daemonul gazdei, serializează extragerea straturilor de imagini, epuizează alocările de subrețea docker0, declanșează erori Another app is currently holding the xtables lock în iptables de pe gazdă și lasă containere orfane între episoade.
  2. Riscul de evadare din containerul DinD privilegiat: Rularea containerelor standard docker:dind necesită --privileged (CAP_SYS_ADMIN), ceea ce expune dispozitivele bloc brute ale gazdei, agenții de eliberare cgroup și modulele kernel rulărilor RL nesigure.
  3. 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 existente gVisor (runsc) nu pot lansa microVM-uri KVM.
  4. Latență ridicată la resetarea episodului (env.reset()): Pornirea unui mediu docker:dind proaspă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 build sau docker compose funcționează din prima în fiecare episod (sau pot fi inițializate de agent printr-un singur tur de 3 comenzi prin rungv), rulările RL nu irosesc tokenii ferestrei de context sau bugetul de pași pentru depanarea eșecurilor service docker start cauzate de ulimit, a erorilor iptables-nft sau a respingerilor pivot_root ale runc. 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 + seccomp BPF pe Linux, deny default Seatbelt pe macOS, Restricted Token + Job Object pe Windows). Chiar dacă un agent RL execută docker run --privileged sau comenzi distructive rm -rf / în cadrul episodului său, aceste operații rămân strict confinate în kernelul Sentry din spațiul utilizatorului al episodului și în fișierul volum .ext4 efemer.


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.

Arhitectura paralelă a rulărilor RL ale agenților cu gvx

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:

  1. env.reset() de 100 de milisecunde prin cp --reflink=always:
    • Construiți o singură dată un fișier golden-docker.ext4 care conține imaginile de bază preextrase (python:3.11, postgres:16, redis:7 sau imagini de bază SWE-bench specifice 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.
  2. Dezafectare fără reziduuri (env.close()):
    • Terminarea procesului rungv (sb.Close() în Go) și ștergerea ep-0042.ext4 recuperează 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.

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 privilegiatFirecracker / KVM MicroVMgVisor standard (runsc)gvx (rungv / pkg/gvx)
root pe gazdă sau --privileged necesarDa (docker.sock = root)Da (--privileged)Necesită /dev/kvm + configurare TAPDa (sau userns + mount)Nu (un singur proces unprivileged)
Funcționează în Pod-uri K8s unprivileged / runsc existentNuNuNu (fără /dev/kvm)Eșuează (EBUSY la seccomp imbricat)Da (buclă futex de rezervă automată)
Suportă docker build și docker compose în interiorPartajează daemonul gazdeiDaDaEșuează fără reglaj DinD pe gazdăDa (overlayfs pe ext4lite)
Izolarea rețelei per episoddocker0 / lock xtables partajatNamespace separatNIC VM separatNetns separatnetstack 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 LinuxNecesită VM LinuxDoar Linux KVMDoar LinuxNativ pe Windows, macOS și Linux

Lecturi suplimentare în această serie

  1. gvx: Rularea containerelor Linux, Docker și Kubernetes într-un singur proces unprivileged: Prezentare completă a arhitecturii gvx pe Windows (whpx, systrap), macOS (hvf) și Linux (systrap).
  2. 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/fuse sunt blocate.
  3. Rularea Docker-in-Docker în gVisor și în sandbox-uri imbricate cu gvx: Analiză detaliată a motivelor pentru care docker.io și runsc imbricat eșuează în gVisor (runsc) și a modului în care gvx trece la bucla futex _RUN_SYSCALL_LOOP la EBUSY.
  4. Medii Docker izolate rapide pentru rulările RL ale agenților AI cu gvx (această postare)