GoSuda

Nopeat eristetyt Docker-ympäristöt AI Agent RL Rollouts -tehtäviin gvx:n avulla

By Lemon Mint views ...

Turvallisuus- ja infrastruktuuripullonkaula koodausagenttien RL-rollouteissa

Ohjelmistotekniikan agenttien kouluttaminen ja arviointi useissa benchmarkeissa sekä vahvistusoppimisen (RL) kehyksissä, kuten SWE-bench, SWE-ReX, R2E-Gym, SWE-Gym, Verl ja OpenHands, edellyttää tuhansien samanaikaisten rollouttien suorittamista luottamattomalla, LLM:n generoimalla koodilla. Todellisissa ohjelmistotekniikan tehtävissä agentti ei pelkästään muokkaa tekstitiedostoja: se suorittaa docker build -komennon, nostaa tietokantoja ja mikropalveluita docker compose -komennolla, ajaa integraatiotestisarjoja ja tarkastelee konttien lokeja.

Rollout-mittakaavassa (128–1 024 samanaikaista episodia klusteria kohden) nykyinen konttiinfrastruktuuri kohtaa neljä järjestelmällistä turvallisuus- ja suorituskykypullonkaulaa:

  1. Isännän dockerd-lukkokiista ja tilan vuotaminen: Kun satoja samanaikaisia RL-työntekijöitä osoitetaan yhteen isäntäkoneen /var/run/docker.sock-tiedostoon, jokainen agentti saa juuritason vastaavan pääsyn isäntädeemoniin, kerrosten purku sarjallistuu, docker0-aliverkkojen varaukset loppuvat, isäntäkoneen iptables-sääntöihin ilmaantuu Another app is currently holding the xtables lock -virheitä ja orpokontit vuotavat episodien välillä.
  2. Privilegioidun DinD-kontin karkaamisriski: Tavallisten docker:dind-konttien ajaminen vaatii --privileged (CAP_SYS_ADMIN) -oikeuden, joka altistaa luottamattomat RL-rollout-ajot raakoihin isännän lohkolaitteisiin, cgroup-release-agenteihin ja ytimen moduuleihin.
  3. Laitteistovirtualisoinnin (/dev/kvm) rajoitteet: MicroVM-ajoympäristöt (Firecracker, QEMU, Kata Containers) eristävät sisemmän ytimen siististi, mutta ne vaativat /dev/kvm-laitteen. Tavalliset Kubernetes-podit ilman sisäkkäistä virtualisointia, bare-metal-pilveinstanssit, joissa KVM on poistettu käytöstä, sekä olemassa olevat gVisor (runsc) -hiekkalaatikot eivät voi käynnistää KVM-microVM-koneita.
  4. Hidas episodin nollauksen (env.reset()) viive: Uuden docker:dind-ympäristön käynnistäminen sekä peruskuvien hakeminen tai purkaminen jokaiselle RL-episodille vie 10–60 sekuntia, mikä jättää GPU-koulutustyöntekijät toimettomiksi odottamaan ympäristöjen nollauksia.

gvx (rungv-CLI ja z.gfire.dev/gvx/pkg/gvx Go SDK) tarjoaa turvallisen, vaivattoman ja tokenitehokkaan rollout-ajoympäristön, joka ratkaisee kaikki neljä pullonkaulaa. Jokainen rollout-ympäristö toimii yhtenä ei-privilegioituna, itselukittuvana käyttäjätilan prosessina (eristettynä Googlen gVisor Sentry -ytimellä, pkg/hostlockdown-paketilla ja muistissa toimivalla netstack-verkkopinolla), jonka koko /var/lib/docker-tila sijaitsee yhdessä copy-on-write .ext4-tiedostossa (ext4lite).

Vaivaton, tokenitehokas suoritus agenteille: Kun docker build tai docker compose toimii valmiina jokaisessa episodissa (tai agentti voi alustaa ne yhdellä kolmen komennon vuorolla rungv-työkalun avulla), RL-rollouteissa ei kulu kontekstiikkunan tokeneita tai vaiheiden budjetteja service docker start -komennon ulimit-virheiden, iptables-nft-virheiden tai runc-pivot_root-hylkäysten selvittämiseen. Jokainen trajektorian token menee varsinaisen ohjelmistotekniikan tehtävän ratkaisemiseen.

Nollaoikeuksinen turvallisuus luottamattomille agenttirollouteille: Jokainen gvx-episodi lukitsee oman isäntäkäyttöjärjestelmäprosessinsa ennen vieraskoodin ajamista (PR_SET_NO_NEW_PRIVS + BPF seccomp Linuxissa, Seatbelt deny default macOS:ssä, Restricted Token + Job Object Windowsissa). Vaikka RL-agentti suorittaisi episodissaan docker run --privileged -komennon tai tuhoavia rm -rf / -komentoja, nämä toiminnot pysyvät tiukasti rajattuina kyseisen episodin käyttäjätilan Sentry-ytimeen ja väliaikaiseen .ext4-volyymitiedostoon.


Rollout-arkkitehtuuri: rinnakkaiset ei-privilegioidut instanssit + reflink-tilannekuvat

Gvx:ssä jokainen rollout-työntekijä on tavallinen ei-privilegioitu 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-lohkovolyyminsa (ep-N.ext4). Ne jakavat kuitenkin luku-vain sisältöosoitteisen erofs-OCI-kuvavälimuistin isännän sivuvälimuistissa.

Rinnakkainen agentti-RL-rollout-arkkitehtuuri gvx:llä

Miksi yksitiedostoiset ext4lite-volyymit muuttavat RL-nollauksen taloutta

Koska gvx:n ext4lite-tiedostojärjestelmä tallentaa koko JBD2-journaloidun ext4-volyymin (mukaan lukien Dockerin overlayfs-kerroshakemistot, trusted.overlay.*-laajennetut attribuutit, whiteout-laitesolmut ja kuvametatiedot) yhteen tavalliseen isäntätiedostoon, episodin elinkaaren hallinta muuttuu yhdeksi tiedostotoimenpiteeksi:

  1. 100 millisekunnin env.reset() komennolla cp --reflink=always:
    • Rakennat kerran golden-docker.ext4-tiedoston, joka sisältää esiladatut peruskuvat (python:3.11, postgres:16, redis:7 tai tehtäväkohtaiset SWE-bench-peruskuvat).
    • Jokaisen RL-episodin alussa 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 kyseisen episodin aikana muokatut 4 KiB:n lohkot kuluttavat lisätilaa levyltä.
  2. Nollajäämä-purku (env.close()):
    • rungv-prosessin lopettaminen (Go-kielessä sb.Close()) ja ep-0042.ext4-tiedoston poistaminen vapauttavat 100 % episodin prosesseista, verkkosocketeista, liitoksista ja tallennustilasta noin 10 millisekunnissa. Mitään ei koskaan liitetä isännän ytimen liitostauluun tai rekisteröidä isännän cgroupeihin.

Vaihe vaiheelta -työnkulku: Golden .ext4-tilannekuva 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 -sivulta (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 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, jonka tallennustilana on 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 perusrakenteet ja build-välimuisti golden-docker.ext4-tiedoston sisään
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ää kuvan
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 kuvat ovat jo valmiina hakemistossa /var/lib/docker:

 1EPISODE_ID="ep-0042"
 2SOCK="/srv/rl-episodes/${EPISODE_ID}.sock"
 3
 4# Välitön Copy-on-Write-kloonaus (~100 ms XFS/Btrfs/APFS:llä)
 5cp --reflink=always \
 6  /srv/rl-snapshots/golden-docker.ext4 \
 7  "/srv/rl-episodes/${EPISODE_ID}.ext4"
 8
 9# Käynnistä eristetty ei-privilegioitu episodi 4 GiB:n RAM-muistilla ja 2 prosessorilla
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# Työntekijän sisällä kuvat ovat heti käytettävissä ilman verkkohakua
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 z.gfire.dev/gvx/pkg/gvx Go SDK:lla

Jos RL-ympäristöpalvelimesi tai arviointiharjoittimesi on kirjoitettu Go-kielellä (tai kutsuu Go-rollout-daemonia 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äkellon aikakatkaisu
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 enimmäismäärä
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 agenttien RL-hiekkalaatikkoajoympäristöistä

Mittari / vaatimusJaettu isäntä dockerdPrivilegioitu docker:dindFirecracker / KVM MicroVMVakio gVisor (runsc)gvx (rungv / pkg/gvx)
Isäntä root tai --privileged vaaditaanKyllä (docker.sock = root)Kyllä (--privileged)Vaatii /dev/kvm + TAP-asetuksetKyllä (tai userns + mount)Ei (yksi ei-privilegioitu prosessi)
Toimii ei-privilegioiduissa K8s-podeissa / olemassa olevassa runsc:ssäEiEiEi (ei /dev/kvm)Epäonnistuu (EBUSY sisäkkäisessä seccompissa)Kyllä (automaattinen futex-silmukkavaihtoehto)
Tukee sisäistä docker build ja docker compose -toimintoaKäyttää jaettua isäntädeemoniaKylläKylläEpäonnistuu ilman isäntä-DinD-viritystäKyllä (overlayfs ext4lite:llä)
Episodikohtainen verkkoeristysJaettu docker0 / xtables-lukkoErillinen nimiavaruusErillinen VM-verkkokorttiErillinen netnsEristetty prosessin sisäinen netstack
Lämmin episodin nollauksen viive (env.reset())5–30 s (kontin/volyymin siivous)10–60 s (kylmä deemoni)~150 ms (VM-tilannekuva)5–20 s~100 ms (cp --reflink=always .ext4)
Ristialustainen paikallinen kehitys (Windows / macOS / Linux)Vaatii Linux-VM:nVaatii Linux-VM:nVain Linux KVMVain LinuxNatiivisti Windowsissa, macOS:ssä ja Linuxissa

Lisälukemista tästä sarjasta

  1. gvx: Linux-kontit, Docker ja Kubernetes yhdessä ei-privilegioidussa prosessissa: Täydellinen arkkitehtuurikatsaus gvx:stä Windowsissa (whpx, systrap), macOS:ssä (hvf) ja Linuxissa (systrap).
  2. Docker, docker build ja docker compose ilman juurioikeuksia tai käyttäjänimiavaruuksia: Nopea kolmivaiheinen käynnistysopas ja vianmääritysohje tilanteisiin, joissa sudo, unshare(CLONE_NEWUSER), mount(2) ja /dev/fuse on estetty.
  3. Docker-in-Docker gVisorin ja sisäkkäisten hiekkalaatikoiden sisällä gvx:llä: Syvällinen katsaus siihen, miksi docker.io ja sisäkkäinen runsc epäonnistuvat gVisorin (runsc) sisällä ja miten gvx siirtyy _RUN_SYSCALL_LOOP futex-silmukkaan EBUSY-virheen ilmetessä.
  4. Nopeat eristetyt Docker-ympäristöt AI-agenttien RL-rollouteille gvx:llä (tämä artikkeli)