Бързи изолирани 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 едновременни епизода на клъстер) съществуващата контейнерна инфраструктура се сблъсква с four системни проблема в сигурността и производителността:
- Заключване на host
dockerdи изтичане на състояние: Насочването на стотици едновременни RL работници към един host/var/run/docker.sockдава на всеки агент достъп, еквивалентен на root, до host демона, сериализира извличането на image слоевете, изчерпва адресите на подмрежатаdocker0, предизвиква грешкиAnother app is currently holding the xtables lockв hostiptablesи оставя осиротели контейнери между епизодите. - Риск от бягство от привилегирован DinD контейнер: Стандартните контейнери
docker:dindизискват--privileged(CAP_SYS_ADMIN), което излага суровите host блокови устройства, cgroup release agents и kernel модулите на непроверени RL rollouts. - Ограничения на хардуерната виртуализация (
/dev/kvm): MicroVM runtimes (Firecracker,QEMU,Kata Containers) изолират вътрешните ядра чисто, но изискват/dev/kvm. Стандартните Kubernetes pods без вложена виртуализация, bare-metal облачни инстанции, където 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).
Сигурност без привилегии за непроверени rollouts на агенти: Всеки епизод на
gvxзаключва собствения си host OS процес преди изпълнението на гост кода (PR_SET_NO_NEW_PRIVS+ BPFseccompна Linux, Seatbeltdeny defaultна macOS, Restricted Token + Job Object на Windows). Дори ако RL агент изпълниdocker run --privilegedили деструктивни командиrm -rf /в рамките на своя епизод, тези операции остават строго ограничени до потребителското пространствоSentryядро на този епизод и до неговия ефимерен.ext4файл-том.
Архитектура на rollouts: Паралелни непривилегировани инстанции + Reflink снимки
В gvx всеки работник за rollouts е обикновен непривилегирован OS процес със собствено ядро Sentry на gVisor, собствен netstack в потребителското пространство (10.0.2.15/24) и собствен ext4lite блоков том (ep-N.ext4), като споделя само за четене съдържателно адресируем erofs OCI image кеш през page cache на host системата.

Защо файловите томове ext4lite променят икономиката на нулирането в RL
Тъй като файловата система ext4lite на gvx съхранява цял JBD2-журналиран ext4 том (включително директориите на слоевете на Docker overlayfs, разширените атрибути trusted.overlay.*, whiteout device nodes и метаданните на образите) в един обикновен host файл, управлението на жизнения цикъл на епизода се превръща в една файлова операция:
- 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 милисекунди. Нищо никога не се монтира в таблицата на монтиранията на host ядрото и не се регистрира в host cgroups.
- Прекратяването на процеса
Стъпка по стъпка: Golden .ext4 снимка и бързо нулиране на епизода
Можете да инсталирате 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 милисекунди
Всеки паралелен работник за rollouts клонира 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# Вътре в работника за rollouts образите са незабавно налични без мрежово изтегляне
22docker images
23
24# Teardown на епизода след изчисляване на наградата (~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 (или извиква Go rollout daemon през 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}
Сравнение на runtimes за изолирани среди за RL на агенти
| Метрика / Изискване | Споделен host dockerd | Привилегирован docker:dind | Firecracker / KVM MicroVM | Стандартен gVisor (runsc) | gvx (rungv / pkg/gvx) |
|---|---|---|---|---|---|
Изисква host root или --privileged | Да (docker.sock = root) | Да (--privileged) | Изисква /dev/kvm + настройка на TAP | Да (или userns + mount) | Не (единичен непривилегирован процес) |
Работи в непривилегировани K8s Pods / съществуващ runsc | Не | Не | Не (няма /dev/kvm) | Неуспешно (EBUSY при вложен seccomp) | Да (автоматичен fallback към futex loop) |
Поддържа вътрешни docker build и docker compose | Споделя host демона | Да | Да | Неуспешно без host DinD настройка | Да (overlayfs върху ext4lite) |
| Мрежова изолация по епизод | Споделен docker0 / xtables lock | Отделно пространство от имена | Отделен NIC на VM | Отделен netns | Изолиран вътрешен netstack |
Латентност на нулиране на топъл епизод (env.reset()) | 5 s до 30 s (prune на контейнери/томове) | 10 s до 60 s (студен демон) | ~150 ms (VM snapshot) | 5 s до 20 s | ~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: Бързо ръководство за начално зареждане в три стъпки и отстраняване на неизправности, когато
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 (тази публикация)