---
id: 517bf76b20da9b1bac643f4bac1bfd6b
author: Lemon Mint
title: Medii Docker izolate și rapide pentru Rollouts RL ale agenților AI cu gvx
description: Cum să rulezi în mod sigur sute de medii Docker și Kubernetes paralele, puternic izolate și fără privilegii pentru SWE-bench și pentru rollout-urile de reinforcement learning ale agenților de programare, cu resetări de volume ext4lite de tip copy-on-write în 100 de milisecunde.
language: ro
date: 2026-11-06T12:02:55.95126412Z
path: /blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04
go_package: z.gfire.dev/gvx
go_repourl: https://github.com/gfire-sigs/gvx.git
category: ai
---

## Blocajul de securitate și infrastructură în rollout-urile 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 rollout-uri concurente de **cod nesigur, generat de LLM-uri**. În sarcinile reale de inginerie software, un agent nu se mulțumește să editeze fișiere 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 scară de rollout (128 până la 1.024 de episoade concurente per cluster), infrastructura de containere existentă se lovește de patru blocaje sistemice de securitate și performanță:

1. **Contencia de blocare a `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 cel de root la daemonul gazdei, serializează extragerea straturilor de imagini, epuizează alocările de subrețea ale `docker0`, declanșează erori `Another app is currently holding the xtables lock` în `iptables`-ul gazdei și lasă containere orfane între episoade.
2. **Riscul de evadare din containerele DinD privilegiate**: Rularea containerelor standard `docker:dind` necesită `--privileged` (`CAP_SYS_ADMIN`), ceea ce expune dispozitivele de bloc brute ale gazdei, agenții de eliberare cgroup și modulele kernel în rollout-urile RL nesigure.
3. **Constrângerile virtualizării hardware (`/dev/kvm`)**: Runtime-urile MicroVM (`Firecracker`, `QEMU`, `Kata Containers`) izolează curat kernelurile interne, dar necesită `/dev/kvm`. Pod-urile Kubernetes standard fără virtualizare imbricată, instanțele cloud bare-metal unde KVM este dezactivat și sandbox-urile `gVisor` (`runsc`) existente nu pot lansa microVM-uri KVM.
4. **Latența lentă a resetării episodului (`env.reset()`)**: Pornirea unui mediu `docker:dind` nou și extragerea sau dezarhivarea imaginilor de bază pentru fiecare episod RL durează între 10 și 60 de secunde, lăsând workerii de antrenare GPU inactivi în timp ce așteaptă resetarea mediilor.

**[gvx](https://github.com/gfire-sigs/gvx)** (CLI-ul `rungv` și SDK-ul Go `z.gfire.dev/gvx/pkg/gvx`) oferă un **runtime de rollout sigur, cu apărare în profunzime**, care rezolvă toate cele patru blocaje. Fiecare mediu de rollout rulează ca **un singur proces în spațiul utilizatorului, fără privilegii și auto-blocat** (izolat de kernelul Sentry al gVisor de la Google, de `pkg/hostlockdown` și de un `netstack` în memorie), a cărui stare întreagă `/var/lib/docker` se află într-un **singur fișier `.ext4` copy-on-write (`ext4lite`)**.

> **Securitate fără privilegii pentru rollout-urile nesigure ale agenților**: Fiecare episod `gvx` își blochează propriul proces al sistemului de operare gazdă înainte de a rula codul 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 interiorul episodului său, aceste operațiuni rămân strict confinate la kernelul Sentry în spațiul utilizatorului al episodului respectiv și la fișierul de volum `.ext4` efemer.

---

## Arhitectura rollout-ului: instanțe paralele fără privilegii + snapshot-uri reflink

În `gvx`, fiecare worker de rollout este un proces obișnuit de sistem de operare, fără privilegii, cu propriul kernel gVisor Sentry, propriul `netstack` în spațiul utilizatorului (`10.0.2.15/24`) și propriul volum de bloc `ext4lite` (`ep-N.ext4`), iar toți partajează un cache de imagini OCI `erofs`, read-only și adresat prin conținut, prin page cache-ul gazdei.

![Arhitectura paralelă a rollout-urilor RL ale agenților cu gvx](/assets/images/gvx/gvx-rl-rollout-snapshot.png)

### De ce volumele `ext4lite` dintr-un singur fișier schimbă economia resetării RL

Deoarece sistemul de fișiere `ext4lite` al `gvx` stochează un volum `ext4` complet 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 de pe gazdă, gestionarea ciclului de viață al episodului devine o singură operație pe fișier:

1. **`env.reset()` de 100 de milisecunde prin `cp --reflink=always`**:
   - Construiești o dată fișierul `golden-docker.ext4`, care conține imaginile de bază preîncărcate (`python:3.11`, `postgres:16`, `redis:7` sau imaginile de bază specifice sarcinilor `SWE-bench`).
   - La începutul fiecărui episod RL, `cp --reflink=always golden-docker.ext4 ep-0042.ext4` (pe XFS, Btrfs, ZFS sau macOS APFS) creează în sub 100 de milisecunde o clonă copy-on-write independentă, fără a copia blocurile de disc de bază.
   - Doar blocurile de 4 KiB modificate în timpul acelui episod specific consumă spațiu suplimentar pe disc.
2. **Dezafectare fără reziduuri (`env.close()`)**:
   - Terminarea procesului `rungv` (`sb.Close()` în Go) și ștergerea legăturii `ep-0042.ext4` recuperează 100% din procesele, socket-urile de rețea, montările și stocarea episodului în ~10 milisecunde. Nimic nu este montat niciodată în tabela de montări a kernelului gazdă și nu este înregistrat în cgroup-urile gazdei.

---

## Flux de lucru pas cu pas: snapshot `.ext4` golden și resetare rapidă a episodului

Poți instala `rungv` pe fiecare nod worker de rollout fie descărcând binarul prebuilt atestat SLSA din [GitHub Releases](https://github.com/gfire-sigs/gvx/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: Creează volumul golden `docker:dind` o singură dată

```sh
mkdir -p /srv/rl-snapshots /srv/rl-episodes

# 1. Pornește o instanță seed docker:dind susținută de golden-docker.ext4
rungv run \
  -p unix:/srv/rl-snapshots/seed.sock:2375 \
  --mount type=volume,src=/srv/rl-snapshots/golden-docker.ext4,dst=/var/lib/docker \
  -e DOCKER_TLS_CERTDIR= \
  docker:dind --tls=false &
SEED_PID=$!

# 2. Preîncarcă imaginile de bază și cache-ul de build în golden-docker.ext4
export DOCKER_HOST="unix:///srv/rl-snapshots/seed.sock"
until docker info >/dev/null 2>&1; do sleep 0.5; done

docker pull python:3.11-slim
docker pull redis:7-alpine
docker pull postgres:16-alpine

# 3. Oprește elegant instanța seed, astfel încât ext4lite să scrie datele pe disc și să compacteze imaginea
kill -INT $SEED_PID
wait $SEED_PID
```

### Pasul 2: Lansează episoadele RL paralele în 100 de milisecunde

Fiecare worker paralel de rollout clonează `golden-docker.ext4` prin reflink și pornește o instanță `docker:dind` izolată, cu toate imaginile deja prezente în `/var/lib/docker`:

```sh
EPISODE_ID="ep-0042"
SOCK="/srv/rl-episodes/${EPISODE_ID}.sock"

# Clonă Copy-on-Write instantanee (~100 ms pe XFS/Btrfs/APFS)
cp --reflink=always \
  /srv/rl-snapshots/golden-docker.ext4 \
  "/srv/rl-episodes/${EPISODE_ID}.ext4"

# Lansează un episod izolat, fără privilegii, cu 4 GiB RAM și 2 CPU-uri
rungv run \
  -m 4g --cpus 2 \
  -p "unix:${SOCK}:2375" \
  --mount "type=volume,src=/srv/rl-episodes/${EPISODE_ID}.ext4,dst=/var/lib/docker" \
  -e DOCKER_TLS_CERTDIR= \
  docker:dind --tls=false &
EP_PID=$!

export DOCKER_HOST="unix://${SOCK}"
until docker info >/dev/null 2>&1; do sleep 0.2; done

# În interiorul worker-ului de rollout, imaginile sunt disponibile imediat, fără pull din rețea
docker images

# Dezafectarea episodului după calcularea recompensei (~10 ms)
kill -KILL $EP_PID
rm -f "/srv/rl-episodes/${EPISODE_ID}.ext4" "${SOCK}"
```

---

## Pool programatic de rollout-uri 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 rollout în Go prin gRPC), poți gestiona episoade de containere paralele direct prin `z.gfire.dev/gvx/pkg/gvx`:

```go
package main

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"z.gfire.dev/gvx/pkg/gvx"
)

func runEpisode(ctx context.Context, goldenExt4, scratchDir string, epID int) error {
	epExt4 := filepath.Join(scratchDir, fmt.Sprintf("ep-%04d.ext4", epID))
	defer os.Remove(epExt4)

	// 1. Clonează prin reflink volumul ext4lite golden
	cpCmd := exec.CommandContext(ctx, "cp", "--reflink=always", goldenExt4, epExt4)
	if err := cpCmd.Run(); err != nil {
		return fmt.Errorf("clone golden volume: %w", err)
	}

	// 2. Aplică un timeout de ceas (wall-clock) per episod
	epCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
	defer cancel()

	var stdout, stderr bytes.Buffer
	sb, err := gvx.New(epCtx, gvx.Config{
		Platform:               gvx.PlatformAuto,
		NetworkMode:            gvx.NetModeNAT,
		NetRaw:                 true,
		AllowPacketSocketWrite: true,
		AllowSUID:              true,
		OCIImage:               "docker:dind",
		Entrypoint:             []string{"sh", "-c"},
		EntrypointSet:          true,
		Cmd: []string{
			"dockerd-entrypoint.sh --tls=false >/var/log/dockerd.log 2>&1 & " +
				"until docker info >/dev/null 2>&1; do sleep 0.2; done && " +
				"docker run --rm python:3.11-slim python -c 'print(6 * 7)'",
		},
		Envv: []string{"DOCKER_TLS_CERTDIR="},
		Mounts: []gvx.Mount{
			{
				Kind:      gvx.MountVolume,
				ImagePath: epExt4,
				GuestPath: "/var/lib/docker",
			},
		},
		Resources: gvx.Resources{
			MemoryBytes: 4 << 30, // Limită de 4 GiB
			CPUMillis:   2000,    // 2.0 CPU-uri
		},
		Stdout: &stdout,
		Stderr: &stderr,
	})
	if err != nil {
		return fmt.Errorf("init episode %d: %w", epID, err)
	}
	defer sb.Close()

	exitCode, err := sb.Run(epCtx)
	if err != nil {
		return fmt.Errorf("run episode %d: %w", epID, err)
	}

	log.Printf("episode %d finished: exit=%d stdout=%q", epID, exitCode, stdout.String())
	return nil
}

func main() {
	ctx := context.Background()
	if err := runEpisode(ctx, "/srv/rl-snapshots/golden-docker.ext4", "/srv/rl-episodes", 1); err != nil {
		log.Fatal(err)
	}
}
```

---

## Comparație a runtime-urilor de sandbox pentru agenți RL

| Metrică / Cerință                                         | `dockerd` partajat de pe gazdă     | `docker:dind` privilegiat | Firecracker / KVM MicroVM       | `gVisor` standard (`runsc`)        | `gvx` (`rungv` / `pkg/gvx`)                 |
| :-------------------------------------------------------- | :--------------------------------- | :------------------------ | :------------------------------ | :--------------------------------- | :------------------------------------------ |
| **Necesită `root` pe gazdă sau `--privileged`**           | Da (`docker.sock` = root)          | Da (`--privileged`)       | Necesită `/dev/kvm` + configurare `TAP` | Da (sau `userns` + `mount`)        | **Nu (un singur proces fără privilegii)**   |
| **Funcționează în Pod-uri K8s fără privilegii / `runsc` existent** | Nu                         | Nu                        | Nu (fără `/dev/kvm`)            | Eșuează (`EBUSY` la seccomp imbricat) | **Da (fallback automat pe bucla futex)**    |
| **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 de VM separat               | Netns separat                      | **Izolat într-un `netstack` din proces**    |
| **Latența resetării episodului (`env.reset()`) la cald**  | 5 s până la 30 s (curățarea containerelor/volumelor) | 10 s până la 60 s (daemon rece) | ~150 ms (snapshot de 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 din această serie

1. **[gvx: Rularea containerelor Linux, Docker și Kubernetes într-un singur proces fără privilegii](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: Prezentare arhitecturală completă a `gvx` pe Windows (`whpx`, `systrap`), macOS (`hvf`) și Linux (`systrap`).
2. **[Rularea Docker, docker build și docker compose fără root sau user namespaces](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)**: 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 interiorul gVisor și al sandbox-urilor imbricate cu gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: Analiză detaliată a motivelor pentru care `docker.io` și `runsc` imbricat eșuează în interiorul `gVisor` (`runsc`) și a modului în care `gvx` trece pe bucla de syscall-uri futex `_RUN_SYSCALL_LOOP` la `EBUSY`.
4. **[Medii Docker izolate și rapide pentru rollout-urile RL ale agenților AI cu gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)** (acest articol)
