Nopeat eristetyt Docker-ympäristöt AI-agenttien RL-rollouteille gvx:n avulla

Turvallisuus- ja infrastruktuuripullonkaula koodausagenttien RL-rollouteissa
Ohjelmistotekniikan agenttien koulutus ja arviointi useissa benchmarkeissa ja vahvistusoppimisen (RL) kehyksissä, kuten SWE-bench, SWE-ReX, R2E-Gym, SWE-Gym, Verl ja OpenHands, edellyttää tuhansien samanaikaisten rollout-suoritusten ajamista luotettavaan koodiin, jonka LLM on generoinut. Todellisissa ohjelmistotehtävissä agentti ei pelkästään muokkaa tekstitiedostoja: se ajaa docker build -komennon, käynnistää tietokantoja ja mikropalveluita komennolla docker compose, suorittaa integraatiotestisarjoja ja tarkastelee konttien lokeja.
Rollout-mittakaavassa (128–1 024 samanaikaista episodia klusteria kohden) nykyinen konttiinfrastruktuuri kohtaa neljä järjestelmällistä turvallisuus- ja suorituskykypullonkaulaa:
- Isäntäkoneen
dockerd-lukkiintuminen ja tilan vuotaminen: Useiden satojen samanaikaisten RL-työntekijöiden osoittaminen yhteen isäntäkoneen/var/run/docker.sock-tiedostoon antaa jokaiselle agentille juurioikeudet vastaavan käyttöoikeuden isäntädaemoniin, sarjallistaa image-kerrosten purkamisen, ehtyydocker0-aliverkon osoitteista, aiheuttaa virheitäAnother app is currently holding the xtables lockisäntäkoneeniptables-työkalussa ja vuotaa orpoja kontteja episodien välillä. - Privilegoidun DinD-kontin karkaamisriski: Vakiomuotoisten
docker:dind-konttien ajaminen vaatii--privileged-lipun (CAP_SYS_ADMIN), joka altistaa raakoja isäntäkoneen lohkolaitteita, cgroup-release-agenteja ja ytimen moduuleja luottamattomille RL-rollouteille. - Laitteistovirtualisoinnin (
/dev/kvm) rajoitteet: MicroVM-ajonaikat (Firecracker,QEMU,Kata Containers) eristävät sisäisen ytimen siististi, mutta ne vaativat/dev/kvm-laitteen. Tavalliset Kubernetes-podit ilman sisäkkäistä virtualisointia, bare-metal-pilvi-instanssit, joissa KVM on poistettu käytöstä, sekä olemassa olevatgVisor(runsc) -hiekkalaatikot eivät voi käynnistää KVM-microVM-koneita. - Hidas episodin nollaus (
env.reset()) -viive: Uudendocker:dind-ympäristön käynnistäminen sekä peruskuvien lataaminen tai purkaminen jokaiselle RL-episodille kestää 10–60 sekuntia, ja GPU-koulutustyöntekijät jäävät tyhjäkäynnille odottamaan ympäristön nollauksia.
gvx (rungv-CLI ja z.gfire.dev/gvx/pkg/gvx Go SDK) tarjoaa turvallisen, syvällisesti puolustetun rollout-ajonaikan, joka ratkaisee kaikki neljä pullonkaulaa. Jokainen rollout-ympäristö toimii yhtenä ilman etuoikeuksia ajettavana, itselukittuvana käyttäjätilan prosessina (eristettynä Googlen gVisor Sentry-ytimellä, pkg/hostlockdown-paketilla ja muistissa olevalla netstack-verkkopinolla), jonka koko /var/lib/docker-tila sijaitsee yhdessä copy-on-write-.ext4-tiedostossa (ext4lite).
Nollaoikeuksinen turvallisuus luottamattomille agenttien rollouteille: Jokainen
gvx-episodi lukitsee oman isäntäkäyttöjärjestelmäprosessinsa ennen vieraskoodin ajamista (PR_SET_NO_NEW_PRIVS+ BPFseccompLinuxilla, Seatbeltdeny defaultmacOS:llä, Restricted Token + Job Object Windowsilla). Vaikka RL-agentti suorittaisi episodissaan komennondocker run --privilegedtai tuhoavan komennonrm -rf /, nämä toiminnot pysyvät tiukasti rajattuina kyseisen episodin käyttäjätilanSentry-ytimeen ja väliaikaiseen.ext4-volyymitiedostoon.
Rollout-arkkitehtuuri: rinnakkaiset ilman etuoikeuksia ajettavat instanssit + reflink-snapshotit
gvx:ssä jokainen rollout-työntekijä on tavallinen ilman etuoikeuksia toimiva käyttöjärjestelmäprosessi, jolla on oma gVisor Sentry-ydin, oma käyttäjätilan netstack (10.0.2.15/24) ja oma ext4lite-lohkovolyymi (ep-N.ext4). Kaikki jakavat kuitenkin yhden luku-vain, sisältöosoitteellisen erofs OCI-image-välimuistin isäntäkoneen sivumuistissa.

Miksi yksitiedostoiset ext4lite-volyymit muuttavat RL-nollauksen talousmallia
Koska gvx:n ext4lite-tiedostojärjestelmä tallentaa koko JBD2-journaloidun ext4-volyymin (mukaan lukien Dockerin overlayfs-kerroshakemistot, trusted.overlay.*-laajennetut attribuutit, whiteout-laitesolmut ja image-metatiedot) yhteen tavalliseen isäntätiedostoon, episodin elinkaaren hallinta muuttuu yhdeksi tiedostotoiminnoksi:
- 100 millisekunnin
env.reset()komennollacp --reflink=always:- Rakennat
golden-docker.ext4-tiedoston kerran. Se sisältää ennalta ladatut perus-imaget (python:3.11,postgres:16,redis:7tai tehtäväkohtaisetSWE-bench-perus-imaget). - Jokaisen RL-episodin alussa komento
cp --reflink=always golden-docker.ext4 ep-0042.ext4(XFS:llä, Btrfs:llä, ZFS:llä tai macOS APFS:llä) luo itsenäisen copy-on-write-kloonin alle 100 millisekunnissa kopioimatta taustalla olevia levylohkoja. - Vain tuon episodin aikana muokatut 4 KiB:n lohkot kuluttavat lisätilaa levyllä.
- Rakennat
- Jäännöksetön purku (
env.close()):rungv-prosessin lopettaminen (sb.Close()Go-kielessä) ja tiedostonep-0042.ext4poistaminen vapauttavat 100 % episodin prosesseista, verkkoyhteyksistä, liitoksista ja tallennustilasta noin 10 millisekunnissa. Mitään ei koskaan liitetä isäntäkoneen ytimen liitostauluun eikä rekisteröidä isäntäkoneen cgroupeihin.
Vaiheittainen työnkulku: golden-.ext4-snapshot ja nopea episodin nollaus
Voit asentaa rungv-työkalun jokaiseen rollout-työntekijäsolmuun joko lataamalla SLSA-todennetun valmiiksi käännetyn binäärin GitHub Releases -sivustolta (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) tai ajamalla komennon CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest.
Vaihe 1: Luo golden-docker:dind-volyymi kerran
1mkdir -p /srv/rl-snapshots /srv/rl-episodes
2
3# 1. Käynnistä siemen-docker:dind-instanssi, jota tukee 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. Esilataa perus-imaget ja build-välimuisti golden-docker.ext4-tiedoston sisälle
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. Pysäytä siemeninstanssi hallitusti, jotta ext4lite huuhtelee ja tiivistää imagen
20kill -INT $SEED_PID
21wait $SEED_PID
Vaihe 2: Käynnistä rinnakkaiset RL-episodit 100 millisekunnissa
Jokainen rinnakkainen rollout-työntekijä kloonaa golden-docker.ext4-tiedoston reflinkin avulla ja käynnistää eristetyn docker:dind-instanssin, jossa kaikki imaget ovat jo hakemistossa /var/lib/docker:
1EPISODE_ID="ep-0042"
2SOCK="/srv/rl-episodes/${EPISODE_ID}.sock"
3
4# Välitön copy-on-write-kloni (~100 ms XFS/Btrfs/APFS-järjestelmillä)
5cp --reflink=always \
6 /srv/rl-snapshots/golden-docker.ext4 \
7 "/srv/rl-episodes/${EPISODE_ID}.ext4"
8
9# Käynnistä eristetty episodi ilman etuoikeuksia, 4 GiB RAM:lla ja 2 CPU:lla
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# Rollout-työntekijän sisällä imaget ovat heti saatavilla ilman verkkolatausta
22docker images
23
24# Episodin purku palkkion laskennan jälkeen (~10 ms)
25kill -KILL $EP_PID
26rm -f "/srv/rl-episodes/${EPISODE_ID}.ext4" "${SOCK}"
Ohjelmallinen rollout-allas käyttäen z.gfire.dev/gvx/pkg/gvx Go SDK:ta
Jos RL-ympäristöpalvelimesi tai arviointiharjoittimesi on kirjoitettu Go-kielellä tai kutsuu Go-rollout-taustaprosessia gRPC:n yli, voit hallita rinnakkaisia konttiepisodeja suoraan paketin z.gfire.dev/gvx/pkg/gvx kautta:
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. Kloonaa golden ext4lite-volyymi reflinkillä
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. Aseta episodikohtainen seinäkellioaikainen aikaraja
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, // 4 GiB katto
55 CPUMillis: 2000, // 2,0 CPU:ta
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}
Vertailu RL-agenttien hiekkalaatikkoympäristöistä
| Mittari / vaatimus | Jaettu isäntäkoneen dockerd | Privilegoitu docker:dind | Firecracker / KVM MicroVM | Vakio gVisor (runsc) | gvx (rungv / pkg/gvx) |
|---|---|---|---|---|---|
Isäntäkoneen root tai --privileged vaaditaan | Kyllä (docker.sock = root) | Kyllä (--privileged) | Vaatii /dev/kvm + TAP-asetukset | Kyllä (tai userns + mount) | Ei (yksi prosessi ilman etuoikeuksia) |
Toimii rajoitetuissa K8s-podeissa / olemassa olevassa runsc:ssä | Ei | Ei | Ei (ei /dev/kvm:ää) | Epäonnistuu (EBUSY sisäkkäisessä seccompissa) | Kyllä (automaattinen futex-silmukkavaraus) |
Tukee sisäistä docker build- ja docker compose-käyttöä | Jakaa isäntädaemonin | Kyllä | Kyllä | Epäonnistuu ilman isäntä-DinD-säätöjä | Kyllä (overlayfs ext4lite-tiedostolla) |
| Episodikohtainen verkkoeristys | Jaettu docker0 / xtables-lukko | Erillinen nimiavaruus | Erillinen VM-verkkokortti | Erillinen netns | Eristetty prosessinsisäinen netstack |
Lämmin episodin nollauksen viive (env.reset()) | 5 s – 30 s (kontin/volyymin siivous) | 10 s – 60 s (kylmä daemon) | ~150 ms (VM-snapshot) | 5 s – 20 s | ~100 ms (cp --reflink=always .ext4) |
| Alustojen välinen paikallinen kehitys (Windows / macOS / Linux) | Vaatii Linux-VM | Vaatii Linux-VM | Vain Linux KVM | Vain Linux | Natiivi Windowsilla, macOS:llä ja Linuxilla |
Lisälukemista tästä sarjasta
- gvx: Linux-konttien, Dockerin ja Kubernetesin ajaminen yhdessä ilman etuoikeuksia toimivassa prosessissa: Täydellinen arkkitehtuurikatsaus
gvx:stä Windowsilla (whpx,systrap), macOS:llä (hvf) ja Linuxilla (systrap). - Docker, docker build ja docker compose ilman juurioikeuksia tai käyttäjänimiavaruuksia: Nopea kolmen vaiheen käynnistysopas ja vianmääritysopas tilanteisiin, joissa
sudo,unshare(CLONE_NEWUSER),mount(2)ja/dev/fuseon estetty. - Docker-in-Docker gVisorin ja sisäkkäisten hiekkalaatikoiden sisällä gvx:llä: Syvällinen tarkastelu siitä, miksi
docker.ioja sisäkkäinenrunscepäonnistuvatgVisorin(runsc) sisällä, ja mitengvxsiirtyy varavaihtoehtoon_RUN_SYSCALL_LOOPfutex-silmukkaanEBUSY-virheen ilmetessä. - Nopeat eristetyt Docker-ympäristöt AI-agenttien RL-rollouteille gvx:llä (tämä artikkeli)