Rýchle izolované prostredia Docker pre RL rollouts AI agentov s gvx

Bezpečnostné a infraštruktúrne úzke hrdlo v RL rolloutoch kódovacích agentov
Trénovanie a vyhodnocovanie softvérových inžinierskych agentov naprieč benchmarkami a frameworkmi pre reinforcement learning (RL), ako sú SWE-bench, SWE-ReX, R2E-Gym, SWE-Gym, Verl a OpenHands, vyžaduje spúšťanie tisícov súbežných rolloutov nedôveryhodného kódu generovaného LLM. Pri reálnych úlohách softvérového inžinierstva agent nielen upravuje textové súbory: spúšťa docker build, zapína databázy a mikroslužby pomocou docker compose, vykonáva integračné testovacie sady a kontroluje logy kontajnerov.
Pri rozsahu rolloutov (128 až 1 024 súbežných epizód na klaster) existujúca infraštruktúra kontajnerov naráža na štyri systémové bezpečnostné a výkonnostné úzke hrdlá:
- Zámková kolízia a únik stavu hostiteľského
dockerd: Nasmerovanie stoviek súbežných RL workerov na jediný/var/run/docker.sockhostiteľa dáva každému agentovi prístup ekvivalentný rootovi k hostiteľskému démonovi, serializuje extrakciu vrstiev obrazov, vyčerpáva alokácie podsietídocker0, spúšťa chybyAnother app is currently holding the xtables lockv hostiteľskomiptablesa necháva osirelé kontajnery naprieč epizódami. - Únik z kontajnera pri privilegovanom DinD: Štandardné kontajnery
docker:dindvyžadujú--privileged(CAP_SYS_ADMIN), ktorý vystavuje nedôveryhodným RL rolloutom surové blokové zariadenia hostiteľa, agenty uvoľnenia cgroup a kernelové moduly. - Obmedzenia hardvérovej virtualizácie (
/dev/kvm): MicroVM runtime (Firecracker,QEMU,Kata Containers) čisto izolujú vnútorné jadrá, no vyžadujú/dev/kvm. Štandardné Kubernetes pody bez vnorenej virtualizácie, bare-metal cloudové inštancie, kde je KVM vypnuté, a existujúce sandboxygVisor(runsc) nemôžu spúšťať microVM KVM. - Vysoká latencia resetu epizódy (
env.reset()): Spustenie nového prostrediadocker:dinda sťahovanie alebo rozbaľovanie základných obrazov pre každú RL epizódu trvá 10 až 60 sekúnd, čo necháva GPU trénovacie workery nečinné počas čakania na reset prostredia.
gvx (CLI rungv a Go SDK z.gfire.dev/gvx/pkg/gvx) poskytuje bezpečný rollout runtime s obranou do hĺbky, ktorý rieši všetky štyri úzke hrdlá. Každé prostredie rolloutu beží ako jediný proces v používateľskom priestore bez privilégií a s vlastným uzamknutím (izolovaný jadrom Google gVisor Sentry, balíkom pkg/hostlockdown a pamäťovým netstack), pričom celý stav /var/lib/docker sa nachádza vnútri jediného súboru .ext4 s copy-on-write (ext4lite).
Bezpečnosť bez privilégií pre rollouty nedôveryhodných agentov: Každá epizóda
gvxuzamkne vlastný hostiteľský OS proces pred spustením hosťovského kódu (PR_SET_NO_NEW_PRIVS+ BPFseccompna Linuxe, Seatbeltdeny defaultna macOS, Restricted Token + Job Object na Windows). Aj keď RL agent spustídocker run --privilegedalebo deštruktívne príkazyrm -rf /vo svojej epizóde, tieto operácie zostávajú prísne obmedzené na jadroSentryv používateľskom priestore danej epizódy a na jej dočasný súbor zväzku.ext4.
Architektúra rolloutov: Paralelné inštancie bez privilégií + Reflink snímky
V gvx je každý rollout worker bežný OS proces bez privilégií s vlastným jadrom gVisor Sentry, vlastným netstack v používateľskom priestore (10.0.2.15/24) a vlastným blokovým zväzkom ext4lite (ep-N.ext4), pričom zdieľa len na čítanie určenú content-addressable cache obrazov OCI erofs cez page cache hostiteľa.

Prečo jednosúborové zväzky ext4lite menia ekonomiku resetov RL
Keďže súborový systém ext4lite v gvx ukladá celý zväzok ext4 s žurnálom JBD2 (vrátane adresárov vrstiev overlayfs Dockeru, rozšírených atribútov trusted.overlay.*, whiteout device uzlov a metadát obrazov) do jedného bežného súboru na hostiteľovi, správa životného cyklu epizódy sa stáva jedinou operáciou so súborom:
- 100-milisekundový
env.reset()pomocoucp --reflink=always:- Súbor
golden-docker.ext4vytvoríte raz; obsahuje vopred stiahnuté základné obrazy (python:3.11,postgres:16,redis:7alebo základné obrazySWE-benchšpecifické pre úlohu). - Na začiatku každej RL epizódy
cp --reflink=always golden-docker.ext4 ep-0042.ext4(na XFS, Btrfs, ZFS alebo macOS APFS) vytvorí nezávislý copy-on-write klon za menej ako 100 milisekúnd bez kopírovania podkladových diskových blokov. - Dodatočné miesto na disku zaberajú iba 4 KiB bloky, ktoré sa počas danej epizódy zmenili.
- Súbor
- Ukončenie bez zvyškov (
env.close()):- Ukončenie procesu
rungv(sb.Close()v Go) a odstránenie súboruep-0042.ext4uvoľní 100 % procesov, sieťových socketov, prípojok a úložiska epizódy za ~10 milisekúnd. Nič sa nikdy nepripojí do tabuľky prípojok hostiteľského jadra ani sa nezaregistruje v cgroups hostiteľa.
- Ukončenie procesu
Postup krok za krokom: Zlatá .ext4 snímka a rýchly reset epizódy
Nástroj rungv môžete nainštalovať na každý uzol rollout workera buď stiahnutím prekompilovanej binárky s attestáciou SLSA z vydaní na GitHube (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), alebo spustením CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest.
Krok 1: Vytvorenie zlatého zväzku docker:dind len raz
1mkdir -p /srv/rl-snapshots /srv/rl-episodes
2
3# 1. Spustenie seed inštancie docker:dind podloženej súborom 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. Predbežné naplnenie základných obrazov a vyrovnávacej pamäte zostavení vnútri 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. Elegantné zastavenie seed inštancie, aby ext4lite zapísal a zhutnil obraz
20kill -INT $SEED_PID
21wait $SEED_PID
Krok 2: Spustenie paralelných RL epizód za 100 milisekúnd
Každý paralelný rollout worker klonuje golden-docker.ext4 cez reflink a spustí izolovanú inštanciu docker:dind, v ktorej sú všetky obrazy už prítomné v /var/lib/docker:
1EPISODE_ID="ep-0042"
2SOCK="/srv/rl-episodes/${EPISODE_ID}.sock"
3
4# Okamžitý Copy-on-Write klon (~100 ms na XFS/Btrfs/APFS)
5cp --reflink=always \
6 /srv/rl-snapshots/golden-docker.ext4 \
7 "/srv/rl-episodes/${EPISODE_ID}.ext4"
8
9# Spustenie izolovanej epizódy bez privilégií so 4 GiB RAM a 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# Vnútri rollout workera sú obrazy okamžite dostupné bez sťahovania zo siete
22docker images
23
24# Ukončenie epizódy po výpočte odmeny (~10 ms)
25kill -KILL $EP_PID
26rm -f "/srv/rl-episodes/${EPISODE_ID}.ext4" "${SOCK}"
Programový pool rolloutov pomocou Go SDK z.gfire.dev/gvx/pkg/gvx
Ak je váš server RL prostredia alebo evaluačný harness napísaný v Go (alebo volá rollout démon v Go cez gRPC), môžete paralelné epizódy kontajnerov spravovať priamo cez 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. Reflink klonovanie zlatého zväzku 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. Uplatnenie časového limitu epizódy podľa reálneho času
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 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}
Porovnanie behových prostredí sandboxu pre RL agentov
| Metrika / požiadavka | Zdieľaný hostiteľský dockerd | Privilegovaný docker:dind | Firecracker / KVM MicroVM | Štandardný gVisor (runsc) | gvx (rungv / pkg/gvx) |
|---|---|---|---|---|---|
Vyžaduje root na hostiteľovi alebo --privileged | Áno (docker.sock = root) | Áno (--privileged) | Vyžaduje /dev/kvm + nastavenie TAP | Áno (alebo userns + mount) | Nie (jediný proces bez privilégií) |
Funguje vnútri K8s Podov bez privilégií / existujúceho runsc | Nie | Nie | Nie (bez /dev/kvm) | Zlyháva (EBUSY pri vnorenom seccomp) | Áno (automatický záložný futex loop) |
Podporuje vnútorné docker build a docker compose | Zdieľa hostiteľský démon | Áno | Áno | Zlyháva bez úpravy hostiteľského DinD | Áno (overlayfs na ext4lite) |
| Sieťová izolácia na úrovni epizódy | Zdieľaný docker0 / zámok xtables | Oddelený namespace | Oddelené sieťové rozhranie VM | Oddelený netns | Izolovaný netstack vnútri procesu |
Latencia resetu epizódy za behu (env.reset()) | 5 s až 30 s (čistenie kontajnerov/zväzkov) | 10 s až 60 s (studený démon) | ~150 ms (snímka VM) | 5 s až 20 s | ~100 ms (cp --reflink=always .ext4) |
| Multiplatformový lokálny vývoj (Windows / macOS / Linux) | Vyžaduje VM s Linuxom | Vyžaduje VM s Linuxom | Iba Linux KVM | Iba Linux | Natívne na Windows, macOS a Linuxe |
Ďalšie čítanie v tejto sérii
- gvx: Spúšťanie Linux kontajnerov, Dockeru a Kubernetes v jedinom procese bez privilégií: Kompletný architektonický prehľad
gvxnaprieč Windows (whpx,systrap), macOS (hvf) a Linuxom (systrap). - Spúšťanie Dockeru, docker build a docker compose bez roota alebo používateľských namespace-ov: Rýchly trojkrokový návod na spustenie a riešenie problémov, keď sú blokované
sudo,unshare(CLONE_NEWUSER),mount(2)a/dev/fuse. - Spúšťanie Docker-in-Docker vnútri gVisor a vnorených sandboxov pomocou gvx: Podrobný pohľad na to, prečo
docker.ioa vnorenýrunsczlyhávajú vnútrigVisor(runsc) a akogvxprechádza na futex slučku_RUN_SYSCALL_LOOPpriEBUSY. - Rýchle izolované Docker prostredia pre RL rollouty AI agentov pomocou gvx (tento príspevok)