Бързи изолирани Docker среди за AI Agent RL Rollouts с gvx

Тясното място в сигурността и инфраструктурата на RL rollouts за кодиращи агенти
Обучението и оценяването на софтуерни инженерни агенти в бенчмаркове и рамки за обучение с подкрепление (reinforcement learning, RL), като SWE-bench, SWE-ReX, R2E-Gym, SWE-Gym, Verl и OpenHands, изисква изпълнение на хиляди едновременни rollouts върху недоверен, генериран от LLM код. В реални задачи по софтуерно инженерство агентът не просто редактира текстови файлове: той изпълнява docker build, стартира бази данни и микроуслуги чрез docker compose, изпълнява пакети от интеграционни тестове и инспектира логовете на контейнерите.
При мащаб на rollouts (128 до 1,024 едновременни епизода на клъстер) съществуващата контейнерна инфраструктура се сблъсква с четири системни пречки по отношение на сигурността и производителността:
- Конкуренция за заключвания в хост
dockerdи изтичане на състояние: Насочването на стотици едновременни RL работници към един хост/var/run/docker.sockдава на всеки агент достъп, еквивалентен на root, до демона на хоста, сериализира извличането на слоеве от образи, изчерпва подмрежите наdocker0, предизвиква грешкиAnother app is currently holding the xtables lockв хостiptablesи оставя осиротели контейнери между епизодите. - Риск от бягство от контейнера при привилегирован DinD: Стандартните контейнери
docker:dindизискват--privileged(CAP_SYS_ADMIN), което излага сурови блокови устройства на хоста, cgroup release agents и kernel модули на недоверени RL rollouts. - Ограничения на хардуерната виртуализация (
/dev/kvm): MicroVM рантаймите (Firecracker,QEMU,Kata Containers) изолират вътрешните ядра коректно, но изискват/dev/kvm. Стандартните Kubernetes pods без вложена виртуализация, голите облачни инстанции, на които KVM е деактивиран, и съществуващитеgVisor(runsc) sandboxes не могат да стартират KVM microVMs. - Бавно нулиране на епизода (
env.reset()): Стартирането на нова средаdocker:dindи изтеглянето или разопаковането на базовите образи за всеки RL епизод отнема от 10 до 60 секунди, като GPU работниците за обучение остават в простой, докато чакат нулиране на средите.
gvx (CLI rungv и Go SDK z.gfire.dev/gvx/pkg/gvx) предоставя сигурен, нисък по усилие и ефективен по токени рантайм за rollouts, който решава и четирите пречки. Всяка среда за rollout се изпълнява като един непривилегирован, самоблокиран процес в потребителското пространство (изолиран от Sentry ядрото на Google gVisor, pkg/hostlockdown и вътрешен netstack в паметта), чието цялостно състояние /var/lib/docker се намира в един copy-on-write файл .ext4 (ext4lite).
Изпълнение с нисък разход на усилия и ефективно по токени за агенти: Когато
docker buildилиdocker composeработят от първия опит във всеки епизод (или агентът може да ги инициализира в един ход от 3 команди чрезrungv), RL rollouts не губят токени от контекстния прозорец или бюджета от стъпки за отстраняване на грешки приservice docker start,ulimit, грешки наiptables-nftили отхвърляне наruncpivot_root. Всеки токен в траекторията е насочен към решаването на реалната задача по софтуерно инженерство.
Нулева привилегия за сигурност при недоверени rollouts на агенти: Всеки епизод на
gvxзаключва собствения си хостов OS процес преди изпълнението на гостевия код (PR_SET_NO_NEW_PRIVS+ BPFseccompна Linux, Seatbeltdeny defaultна macOS, Restricted Token + Job Object на Windows). Дори ако RL агент изпълниdocker run --privilegedили деструктивни командиrm -rf /в своя епизод, тези операции остават строго ограничени до user-space ядротоSentryна този епизод и до неговия ефимерен файл-том.ext4.
Архитектура на rollouts: Паралелни непривилегировани инстанции + reflink snapshots
В gvx всеки работник за rollout е обикновен непривилегирован OS процес със собствено ядро gVisor Sentry, собствен user-space netstack (10.0.2.15/24) и собствен блоков том ext4lite (ep-N.ext4), като споделя само за четене адресируем по съдържание кеш на OCI образи erofs през страничния кеш на хоста.

Защо еднофайловите томове ext4lite променят икономиката на RL нулирането
Тъй като файловата система ext4lite на gvx съхранява цял том ext4 с JBD2 журнал (включително директориите на слоевете overlayfs на Docker, разширените атрибути trusted.overlay.*, whiteout device nodes и метаданните на образите) в един обикновен файл на хоста, управлението на жизнения цикъл на епизода се свежда до една файлова операция:
- 100-милисекундно
env.reset()чрезcp --reflink=always:- Веднъж създавате файла
golden-docker.ext4, съдържащ предварително изтеглени базови образи (python:3.11,postgres:16,redis:7или специфични за задачата базови образи наSWE-bench). - В началото на всеки RL епизод
cp --reflink=always golden-docker.ext4 ep-0042.ext4(на XFS, Btrfs, ZFS или macOS APFS) създава независимо Copy-on-Write клониране за под 100 милисекунди, без копиране на основните дискови блокове. - Само 4 KiB блоковете, променени по време на конкретния епизод, заемат допълнително дисково пространство.
- Веднъж създавате файла
- Прекратяване без остатъци (
env.close()):- Прекратяването на процеса
rungv(sb.Close()в Go) и премахването наep-0042.ext4освобождава 100% от процесите, мрежовите сокети, монтиранията и хранилището на епизода за около 10 милисекунди. Нищо никога не се монтира в таблицата на монтиранията на хост ядрото и не се регистрира в хостовите cgroups.
- Прекратяването на процеса
Стъпка по стъпка: Golden .ext4 snapshot и бързо нулиране на епизода
Можете да инсталирате rungv на всеки работен възел за rollouts или чрез изтегляне на SLSA-атестирания предварително компилиран двоичен файл от 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), или чрез изпълнение на CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest.
Стъпка 1: Създайте golden docker:dind том еднократно
1mkdir -p /srv/rl-snapshots /srv/rl-episodes
2
3# 1. Стартирайте seed инстанс на docker:dind с 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. Предварително заредете базовите образи и кеша за build в 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. Спрете seed инстанса плавно, за да може ext4lite да запише и компактира образа
20kill -INT $SEED_PID
21wait $SEED_PID
Стъпка 2: Стартирайте паралелни RL епизоди за 100 милисекунди
Всеки паралелен работник за rollout клонира golden-docker.ext4 чрез reflink и стартира изолиран инстанс docker:dind с всички образи, вече налични в /var/lib/docker:
1EPISODE_ID="ep-0042"
2SOCK="/srv/rl-episodes/${EPISODE_ID}.sock"
3
4# Мигновено клониране Copy-on-Write (~100 ms на XFS/Btrfs/APFS)
5cp --reflink=always \
6 /srv/rl-snapshots/golden-docker.ext4 \
7 "/srv/rl-episodes/${EPISODE_ID}.ext4"
8
9# Стартиране на изолиран непривилегирован епизод с 4 GiB RAM и 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# Вътре в работника за rollout образите са налични веднага, без мрежово изтегляне
22docker images
23
24# Прекратяване на епизода след изчисляване на наградата (~10 ms)
25kill -KILL $EP_PID
26rm -f "/srv/rl-episodes/${EPISODE_ID}.ext4" "${SOCK}"
Програмируем пул от rollouts с Go SDK z.gfire.dev/gvx/pkg/gvx
Ако вашият сървър за RL среди или harness за оценка е написан на Go (или извиква демон за rollouts на Go през gRPC), можете да управлявате паралелните контейнерни епизоди директно чрез 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 клониране на golden 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. Прилагане на таймаут за епизода по стенен часовник
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
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}
Сравнение на рантаймите за sandbox на RL агенти
| Показател / Изискване | Споделен хост dockerd | Привилегирован docker:dind | Firecracker / KVM MicroVM | Стандартен gVisor (runsc) | gvx (rungv / pkg/gvx) |
|---|---|---|---|---|---|
Изисква root на хоста или --privileged | Да (docker.sock = root) | Да (--privileged) | Нужни са /dev/kvm + настройка на TAP | Да (или userns + mount) | Не (един непривилегирован процес) |
Работи в непривилегировани K8s Pods / съществуващ runsc | Не | Не | Не (няма /dev/kvm) | Неуспешно (EBUSY при вложен seccomp) | Да (автоматичен резервен futex loop) |
Поддържа вътрешни docker build и docker compose | Споделя демона на хоста | Да | Да | Неуспешно без настройка на host DinD | Да (overlayfs върху ext4lite) |
| Мрежова изолация по епизод | Споделени docker0 / xtables заключване | Отделно пространство от имена | Отделен NIC на VM | Отделен netns | Изолиран netstack в процеса |
Латентност на нулиране на топъл епизод (env.reset()) | 5s до 30s (изчистване на контейнер/том) | 10s до 60s (студен демон) | ~150 ms (VM snapshot) | 5s до 20s | ~100 ms (cp --reflink=always на .ext4) |
| Междуплатформена локална разработка (Windows / macOS / Linux) | Изисква Linux VM | Изисква Linux VM | Само Linux KVM | Само Linux | Нативно на Windows, macOS и Linux |
Още за тази серия
- gvx: Изпълнение на Linux контейнери, Docker и Kubernetes в един непривилегирован процес: Пълен архитектурен преглед на
gvxв Windows (whpx,systrap), macOS (hvf) и Linux (systrap). - Изпълнение на Docker, docker build и docker compose без root или user namespaces: Бърз 3-стъпков начален план и ръководство за отстраняване на грешки, когато
sudo,unshare(CLONE_NEWUSER),mount(2)и/dev/fuseса блокирани. - Изпълнение на Docker-in-Docker в gVisor и вложени sandboxes с gvx: Задълбочен преглед на това защо
docker.ioи вложениятrunscсе провалят вgVisor(runsc) и какgvxпреминава към futex цикъла_RUN_SYSCALL_LOOPприEBUSY. - Бързи изолирани Docker среди за RL rollouts на AI агенти с gvx (тази публикация)