GoSuda

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

By Lemon Mint views ...

Тясното място в сигурността и инфраструктурата при 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 системни проблема в сигурността и производителността:

  1. Заключване на host dockerd и изтичане на състояние: Насочването на стотици едновременни RL работници към един host /var/run/docker.sock дава на всеки агент достъп, еквивалентен на root, до host демона, сериализира извличането на image слоевете, изчерпва адресите на подмрежата docker0, предизвиква грешки Another app is currently holding the xtables lock в host iptables и оставя осиротели контейнери между епизодите.
  2. Риск от бягство от привилегирован DinD контейнер: Стандартните контейнери docker:dind изискват --privileged (CAP_SYS_ADMIN), което излага суровите host блокови устройства, cgroup release agents и kernel модулите на непроверени RL rollouts.
  3. Ограничения на хардуерната виртуализация (/dev/kvm): MicroVM runtimes (Firecracker, QEMU, Kata Containers) изолират вътрешните ядра чисто, но изискват /dev/kvm. Стандартните Kubernetes pods без вложена виртуализация, bare-metal облачни инстанции, където KVM е деактивиран, и съществуващите gVisor (runsc) sandboxes не могат да стартират KVM microVMs.
  4. Бавно нулиране на епизода (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 + BPF seccomp на Linux, Seatbelt deny 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 системата.

Паралелна архитектура на RL rollouts за агенти с gvx

Защо файловите томове ext4lite променят икономиката на нулирането в RL

Тъй като файловата система ext4lite на gvx съхранява цял JBD2-журналиран ext4 том (включително директориите на слоевете на Docker overlayfs, разширените атрибути trusted.overlay.*, whiteout device nodes и метаданните на образите) в един обикновен host файл, управлението на жизнения цикъл на епизода се превръща в една файлова операция:

  1. 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 блоковете, модифицирани по време на този конкретен епизод, заемат допълнително дисково пространство.
  2. Изчистване без остатъци (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:dindFirecracker / 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

Още четива от тази серия

  1. gvx: Изпълнение на Linux контейнери, Docker и Kubernetes в един непривилегирован процес: Пълна архитектурна обиколка на gvx в Windows (whpx, systrap), macOS (hvf) и Linux (systrap).
  2. Изпълнение на Docker, docker build и docker compose без root или user namespaces: Бързо ръководство за начално зареждане в три стъпки и отстраняване на неизправности, когато sudo, unshare(CLONE_NEWUSER), mount(2) и /dev/fuse са блокирани.
  3. Изпълнение на Docker-in-Docker в gVisor и вложени sandboxes с gvx: Задълбочен анализ на причините, поради които docker.io и вложеният runsc се провалят в gVisor (runsc), и как gvx се връща към futex цикъла _RUN_SYSCALL_LOOP при EBUSY.
  4. Бързи изолирани Docker среди за RL rollouts на AI агенти с gvx (тази публикация)