---
id: 517bf76b20da9b1bac643f4bac1bfd6b
author: Lemon Mint
title: Бързи изолирани Docker среди за AI Agent RL Rollouts с gvx
description: Как да се изпълняват сигурно стотици паралелни, силно изолирани непривилегировани Docker и Kubernetes среди за SWE-bench и за rollouts при обучение с подсилване на кодиращи агенти, с нулиране на ext4lite томове с copy-on-write за 100 милисекунди.
language: bg
date: 2026-11-06T12:02:55.95126412Z
path: /blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04
go_package: z.gfire.dev/gvx
go_repourl: https://github.com/gfire-sigs/gvx.git
category: ai
---

## Тясното място в сигурността и инфраструктурата при 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](https://github.com/gfire-sigs/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](/assets/images/gvx/gvx-rl-rollout-snapshot.png)

### Защо файловите томове `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](https://github.com/gfire-sigs/gvx/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` том еднократно

```sh
mkdir -p /srv/rl-snapshots /srv/rl-episodes

# 1. Стартирайте seed инстанция на docker:dind, подкрепена от golden-docker.ext4
rungv run \
  -p unix:/srv/rl-snapshots/seed.sock:2375 \
  --mount type=volume,src=/srv/rl-snapshots/golden-docker.ext4,dst=/var/lib/docker \
  -e DOCKER_TLS_CERTDIR= \
  docker:dind --tls=false &
SEED_PID=$!

# 2. Предварително заредете базовите образи и build кеша в golden-docker.ext4
export DOCKER_HOST="unix:///srv/rl-snapshots/seed.sock"
until docker info >/dev/null 2>&1; do sleep 0.5; done

docker pull python:3.11-slim
docker pull redis:7-alpine
docker pull postgres:16-alpine

# 3. Спрете грациозно seed инстанцията, така че ext4lite да изчисти буферите и да компактира образа
kill -INT $SEED_PID
wait $SEED_PID
```

### Стъпка 2: Стартирайте паралелни RL епизоди за 100 милисекунди

Всеки паралелен работник за rollouts клонира `golden-docker.ext4` чрез reflink и стартира изолирана инстанция `docker:dind` с всички образи, вече налични в `/var/lib/docker`:

```sh
EPISODE_ID="ep-0042"
SOCK="/srv/rl-episodes/${EPISODE_ID}.sock"

# Мигновено Copy-on-Write клониране (~100 ms на XFS/Btrfs/APFS)
cp --reflink=always \
  /srv/rl-snapshots/golden-docker.ext4 \
  "/srv/rl-episodes/${EPISODE_ID}.ext4"

# Стартиране на изолиран непривилегирован епизод с 4 GiB RAM и 2 CPU
rungv run \
  -m 4g --cpus 2 \
  -p "unix:${SOCK}:2375" \
  --mount "type=volume,src=/srv/rl-episodes/${EPISODE_ID}.ext4,dst=/var/lib/docker" \
  -e DOCKER_TLS_CERTDIR= \
  docker:dind --tls=false &
EP_PID=$!

export DOCKER_HOST="unix://${SOCK}"
until docker info >/dev/null 2>&1; do sleep 0.2; done

# Вътре в работника за rollouts образите са незабавно налични без мрежово изтегляне
docker images

# Teardown на епизода след изчисляване на наградата (~10 ms)
kill -KILL $EP_PID
rm -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`:

```go
package main

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"z.gfire.dev/gvx/pkg/gvx"
)

func runEpisode(ctx context.Context, goldenExt4, scratchDir string, epID int) error {
	epExt4 := filepath.Join(scratchDir, fmt.Sprintf("ep-%04d.ext4", epID))
	defer os.Remove(epExt4)

	// 1. Reflink клониране на golden ext4lite тома
	cpCmd := exec.CommandContext(ctx, "cp", "--reflink=always", goldenExt4, epExt4)
	if err := cpCmd.Run(); err != nil {
		return fmt.Errorf("clone golden volume: %w", err)
	}

	// 2. Прилагане на таймаут по стенен часовник за всеки епизод
	epCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
	defer cancel()

	var stdout, stderr bytes.Buffer
	sb, err := gvx.New(epCtx, gvx.Config{
		Platform:               gvx.PlatformAuto,
		NetworkMode:            gvx.NetModeNAT,
		NetRaw:                 true,
		AllowPacketSocketWrite: true,
		AllowSUID:              true,
		OCIImage:               "docker:dind",
		Entrypoint:             []string{"sh", "-c"},
		EntrypointSet:          true,
		Cmd: []string{
			"dockerd-entrypoint.sh --tls=false >/var/log/dockerd.log 2>&1 & " +
				"until docker info >/dev/null 2>&1; do sleep 0.2; done && " +
				"docker run --rm python:3.11-slim python -c 'print(6 * 7)'",
		},
		Envv: []string{"DOCKER_TLS_CERTDIR="},
		Mounts: []gvx.Mount{
			{
				Kind:      gvx.MountVolume,
				ImagePath: epExt4,
				GuestPath: "/var/lib/docker",
			},
		},
		Resources: gvx.Resources{
			MemoryBytes: 4 << 30, // 4 GiB лимит
			CPUMillis:   2000,    // 2.0 CPU
		},
		Stdout: &stdout,
		Stderr: &stderr,
	})
	if err != nil {
		return fmt.Errorf("init episode %d: %w", epID, err)
	}
	defer sb.Close()

	exitCode, err := sb.Run(epCtx)
	if err != nil {
		return fmt.Errorf("run episode %d: %w", epID, err)
	}

	log.Printf("episode %d finished: exit=%d stdout=%q", epID, exitCode, stdout.String())
	return nil
}

func main() {
	ctx := context.Background()
	if err := runEpisode(ctx, "/srv/rl-snapshots/golden-docker.ext4", "/srv/rl-episodes", 1); err != nil {
		log.Fatal(err)
	}
}
```

---

## Сравнение на 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**        |

---

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

1. **[gvx: Изпълнение на Linux контейнери, Docker и Kubernetes в един непривилегирован процес](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: Пълна архитектурна обиколка на `gvx` в Windows (`whpx`, `systrap`), macOS (`hvf`) и Linux (`systrap`).
2. **[Изпълнение на Docker, docker build и docker compose без root или user namespaces](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)**: Бързо ръководство за начално зареждане в три стъпки и отстраняване на неизправности, когато `sudo`, `unshare(CLONE_NEWUSER)`, `mount(2)` и `/dev/fuse` са блокирани.
3. **[Изпълнение на Docker-in-Docker в gVisor и вложени sandboxes с gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: Задълбочен анализ на причините, поради които `docker.io` и вложеният `runsc` се провалят в `gVisor` (`runsc`), и как `gvx` се връща към futex цикъла `_RUN_SYSCALL_LOOP` при `EBUSY`.
4. **[Бързи изолирани Docker среди за RL rollouts на AI агенти с gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)** (тази публикация)
