Raske isolerte Docker-miljøer for AI-agent RL-rollouts med gvx

Flaskehalsen i sikkerhet og infrastruktur for RL-rollouts med kodeagenter
Trening og evaluering av programvareingeniør-agenter på tvers av benchmarks og rammeverk for reinforcement learning (RL), som SWE-bench, SWE-ReX, R2E-Gym, SWE-Gym, Verl og OpenHands, krever kjøring av tusenvis av samtidige rollouts av utrygg, LLM-generert kode. I reelle oppgaver innen programvareutvikling redigerer en agent ikke bare tekstfiler: Den kjører docker build, starter databaser og mikrotjenester via docker compose, kjører suiter med integrasjonstester og inspiserer containerlogger.
Ved rollout-skala (128 til 1 024 samtidige episoder per klynge) møter eksisterende containerinfrastruktur fire systemiske flaskehalser knyttet til sikkerhet og ytelse:
- Låskonflikt og tilstandslekkasje i vertens
dockerd: Når hundrevis av samtidige RL-arbeidere peker mot en enkelt vertsbasert/var/run/docker.sock, gir det hver agent root-tilsvarende tilgang til vertsdemonen, serialiserer uttrekking av image-lag, tømmer adresserommet fordocker0-subnett, utløser feilmeldingenAnother app is currently holding the xtables locki vertensiptablesog lekker foreldreløse containere på tvers av episoder. - Risiko for containerrømming via privilegert DinD: Kjøring av standard
docker:dind-containere krever--privileged(CAP_SYS_ADMIN), noe som eksponerer rå blokkenheter på verten, cgroup release agents og kjernemoduler for utrygge RL-rollouts. - Begrensninger ved maskinvarevirtualisering (
/dev/kvm): MicroVM-kjøretider (Firecracker,QEMU,Kata Containers) isolerer interne kjerner på en ren måte, men de krever/dev/kvm. Standard Kubernetes-pods uten nøstet virtualisering, bare-metal-skyinstanser der KVM er deaktivert, og eksisterendegVisor(runsc)-sandkasser kan ikke starte KVM-microVM-er. - Høy latens ved tilbakestilling av episoder (
env.reset()): Oppstart av et nyttdocker:dind-miljø og henting eller utpakking av basisbilder for hver RL-episode tar 10 til 60 sekunder, og GPU-treningsarbeidere står inaktive mens de venter på tilbakestilling av miljøene.
gvx (rungv-CLI og z.gfire.dev/gvx/pkg/gvx Go-SDK) tilbyr en sikker rollout-kjøretid med forsvar i dybden som løser alle fire flaskehalsene. Hvert rollout-miljø kjører som én enkelt, uprivilegert og selvlåst prosess i brukerrommet (isolert av Googles gVisor Sentry-kjerne, pkg/hostlockdown og et netstack i minnet), og hele tilstanden i /var/lib/docker ligger i én enkelt copy-on-write .ext4-fil (ext4lite).
Null-privilegert sikkerhet for utrygge agentutrullinger: Hver
gvx-episode låser sin egen vertsprosess i operativsystemet før gjestekode kjøres (PR_SET_NO_NEW_PRIVS+ BPFseccomppå Linux, Seatbeltdeny defaultpå macOS, Restricted Token + Job Object på Windows). Selv om en RL-agent kjørerdocker run --privilegedeller destruktiverm -rf /-kommandoer inne i sin episode, forblir disse operasjonene strengt begrenset til episodensSentry-kjerne i brukerrommet og til dens midlertidige.ext4-volumfil.
Rollout-arkitektur: Parallelle uprivilegerte instanser + Reflink-øyeblikksbilder
I gvx er hver rollout-arbeider en vanlig uprivilegert OS-prosess med sin egen gVisor Sentry-kjerne, sitt eget netstack i brukerrommet (10.0.2.15/24) og sitt eget ext4lite-blokkvolum (ep-N.ext4). Samtidig deler den en skrivebeskyttet, innholdsadressert erofs-OCI-bildecache på tvers av vertens sidecache.

Hvorfor enkeltfilsvolumer med ext4lite endrer økonomien ved RL-tilbakestilling
Fordi ext4lite-filsystemet i gvx lagrer et helt JBD2-journalført ext4-volum (inkludert Dockers overlayfs-lagkataloger, utvidede attributter trusted.overlay.*, whiteout-enhetsnoder og bildemetadata) i én vanlig fil på verten, blir livssyklushåndtering av episoder en enkelt filoperasjon:
- 100 millisekunders
env.reset()viacp --reflink=always:- Du bygger én gang en fil
golden-docker.ext4som inneholder forhåndshentede basisbilder (python:3.11,postgres:16,redis:7eller oppgavespesifikke basisbilder forSWE-bench). - Ved starten av hver RL-episode oppretter
cp --reflink=always golden-docker.ext4 ep-0042.ext4(på XFS, Btrfs, ZFS eller macOS APFS) en uavhengig Copy-on-Write-klone på under 100 millisekunder uten å kopiere underliggende diskblokker. - Bare de 4 KiB-blokkene som er endret under den spesifikke episoden, bruker ekstra diskplass.
- Du bygger én gang en fil
- Nedrivning uten rester (
env.close()):- Når
rungv-prosessen avsluttes (sb.Close()i Go) ogep-0042.ext4fjernes, frigjøres 100 % av episodens prosesser, nettverkssokler, monteringer og lagring på ca. 10 millisekunder. Ingenting blir noen gang montert i vertskjernens monteringstabell eller registrert i vertens cgroups.
- Når
Trinn-for-trinn-arbeidsflyt: Golden .ext4-øyeblikksbilde og rask tilbakestilling av episoder
Du kan installere rungv på hver rollout-arbeiderknute enten ved å laste ned den SLSA-attesterte, ferdigbygde binærfilen fra 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) eller ved å kjøre CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest.
Steg 1: Opprett det golden docker:dind-volumet én gang
1mkdir -p /srv/rl-snapshots /srv/rl-episodes
2
3# 1. Start en seed-instans av docker:dind basert på 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. Forhåndsfyll basisbilder og byggecache inne i 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. Stopp seed-instansen på en kontrollert måte, slik at ext4lite tømmer buffere og komprimerer bildet
20kill -INT $SEED_PID
21wait $SEED_PID
Steg 2: Start parallelle RL-episoder på 100 millisekunder
Hver parallell rollout-arbeider kloner golden-docker.ext4 via reflink og starter en isolert docker:dind-instans med alle bilder allerede til stede i /var/lib/docker:
1EPISODE_ID="ep-0042"
2SOCK="/srv/rl-episodes/${EPISODE_ID}.sock"
3
4# Umiddelbar Copy-on-Write-klone (~100 ms på XFS/Btrfs/APFS)
5cp --reflink=always \
6 /srv/rl-snapshots/golden-docker.ext4 \
7 "/srv/rl-episodes/${EPISODE_ID}.ext4"
8
9# Start isolert uprivilegert episode med 4 GiB RAM og 2 CPU-er
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# Inne i rollout-arbeideren er bildene umiddelbart tilgjengelige uten nedlasting over nettverket
22docker images
23
24# Nedrivning av episoden etter beregning av belønning (~10 ms)
25kill -KILL $EP_PID
26rm -f "/srv/rl-episodes/${EPISODE_ID}.ext4" "${SOCK}"
Programmatisk rollout-pool med Go-SDK-en z.gfire.dev/gvx/pkg/gvx
Hvis RL-miljøserveren eller evalueringsrammeverket ditt er skrevet i Go (eller kaller en Go-rollout-demon over gRPC), kan du styre parallelle containerepisoder direkte gjennom 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. Lag en reflink-klone av det golden ext4lite-volumet
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. Sett en tidsgrense i sanntid 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, // Maksimalt 4 GiB
55 CPUMillis: 2000, // 2,0 CPU-er
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}
Sammenligning av kjøretider for sandkasser i agent-RL
| Metrikk / krav | Delt vert-dockerd | Privilegert docker:dind | Firecracker / KVM MicroVM | Standard gVisor (runsc) | gvx (rungv / pkg/gvx) |
|---|---|---|---|---|---|
Krever vert-root eller --privileged | Ja (docker.sock = root) | Ja (--privileged) | Krever /dev/kvm + TAP-oppsett | Ja (eller userns + mount) | Nei (én enkelt uprivilegert prosess) |
Fungerer i uprivilegerte K8s-pods / eksisterende runsc | Nei | Nei | Nei (ingen /dev/kvm) | Feiler (EBUSY ved nøstet seccomp) | Ja (automatisk reserveløsning med futex-løkke) |
Støtter indre docker build og docker compose | Deler vertsdemonen | Ja | Ja | Feiler uten justering av vert-DinD | Ja (overlayfs på ext4lite) |
| Nettverksisolering per episode | Delt docker0 / xtables-lås | Eget navnerom | Eget VM-nettverkskort | Eget netns | Isolert netstack i prosessen |
Latens ved varm tilbakestilling av episode (env.reset()) | 5 s til 30 s (beskjæring av container/volum) | 10 s til 60 s (kald demon) | ~150 ms (VM-øyeblikksbilde) | 5 s til 20 s | ~100 ms (cp --reflink=always .ext4) |
| Lokal utvikling på tvers av plattformer (Windows / macOS / Linux) | Krever Linux-VM | Krever Linux-VM | Kun Linux KVM | Kun Linux | Innebygd støtte på Windows, macOS og Linux |
Videre lesning i denne serien
- gvx: Kjøring av Linux-containere, Docker og Kubernetes i én uprivilegert prosess: Fullstendig gjennomgang av arkitekturen i
gvxpå Windows (whpx,systrap), macOS (hvf) og Linux (systrap). - Kjøring av Docker, docker build og docker compose uten root eller brukernavnerom: Rask veiledning i tre trinn for oppstart og feilsøking når
sudo,unshare(CLONE_NEWUSER),mount(2)og/dev/fuseer blokkert. - Kjøring av Docker-in-Docker inne i gVisor og nøstede sandkasser med gvx: Grundig gjennomgang av hvorfor
docker.ioog nøstetrunscfeiler inne igVisor(runsc), og hvordangvxfaller tilbake til futex-løkken_RUN_SYSCALL_LOOPvedEBUSY. - Raske isolerte Docker-miljøer for RL-rollouts med AI-agenter og gvx (dette innlegget)