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 едновременни епизода на клъстер) съществуващата контейнерна инфраструктура се сблъсква с четири системни пречки по отношение на сигурността и производителността:

  1. Конкуренция за заключвания в хост dockerd и изтичане на състояние: Насочването на стотици едновременни RL работници към един хост /var/run/docker.sock дава на всеки агент достъп, еквивалентен на root, до демона на хоста, сериализира извличането на слоеве от образи, изчерпва подмрежите на docker0, предизвиква грешки Another app is currently holding the xtables lock в хост iptables и оставя осиротели контейнери между епизодите.
  2. Риск от бягство от контейнера при привилегирован DinD: Стандартните контейнери docker:dind изискват --privileged (CAP_SYS_ADMIN), което излага сурови блокови устройства на хоста, cgroup release agents и kernel модули на недоверени RL rollouts.
  3. Ограничения на хардуерната виртуализация (/dev/kvm): MicroVM рантаймите (Firecracker, QEMU, Kata Containers) изолират вътрешните ядра коректно, но изискват /dev/kvm. Стандартните Kubernetes pods без вложена виртуализация, голите облачни инстанции, на които 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).

Изпълнение с нисък разход на усилия и ефективно по токени за агенти: Когато docker build или docker compose работят от първия опит във всеки епизод (или агентът може да ги инициализира в един ход от 3 команди чрез rungv), RL rollouts не губят токени от контекстния прозорец или бюджета от стъпки за отстраняване на грешки при service docker start, ulimit, грешки на iptables-nft или отхвърляне на runc pivot_root. Всеки токен в траекторията е насочен към решаването на реалната задача по софтуерно инженерство.

Нулева привилегия за сигурност при недоверени rollouts на агенти: Всеки епизод на gvx заключва собствения си хостов OS процес преди изпълнението на гостевия код (PR_SET_NO_NEW_PRIVS + BPF seccomp на Linux, Seatbelt deny 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 през страничния кеш на хоста.

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

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

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

  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 милисекунди. Нищо никога не се монтира в таблицата на монтиранията на хост ядрото и не се регистрира в хостовите 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:dindFirecracker / 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

Още за тази серия

  1. gvx: Изпълнение на Linux контейнери, Docker и Kubernetes в един непривилегирован процес: Пълен архитектурен преглед на gvx в Windows (whpx, systrap), macOS (hvf) и Linux (systrap).
  2. Изпълнение на Docker, docker build и docker compose без root или user namespaces: Бърз 3-стъпков начален план и ръководство за отстраняване на грешки, когато 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 (тази публикация)