---
id: 517bf76b20da9b1bac643f4bac1bfd6b
author: Lemon Mint
title: Rychlá izolovaná prostředí Docker pro rollouty AI agentů při učení s posilováním pomocí gvx
description: Jak bezpečně provozovat stovky paralelních, silně izolovaných neprivilegovaných prostředí Docker a Kubernetes pro SWE-bench a rollouty posilovaného učení (reinforcement learning) kódovacích agentů se 100milisekundovými resety svazků ext4lite s copy-on-write
language: cs
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
---

## Úzké hrdlo zabezpečení a infrastruktury v RL rolloutech kódujících agentů

Trénování a vyhodnocování agentů pro softwarové inženýrství napříč benchmarky a frameworky pro reinforcement learning (RL), jako jsou **SWE-bench**, **SWE-ReX**, **R2E-Gym**, **SWE-Gym**, **Verl** a **OpenHands**, vyžaduje spouštění tisíců souběžných rolloutů **nedůvěryhodného kódu generovaného LLM**. V reálných úlohách softwarového inženýrství agent nejen upravuje textové soubory: spouští `docker build`, zprovozňuje databáze a mikroslužby pomocí `docker compose`, provádí sady integračních testů a kontroluje logy kontejnerů.

Při škále rolloutů (128 až 1 024 souběžných epizod na jeden cluster) narází stávající kontejnerová infrastruktura na čtyři systémová bezpečnostní a výkonnostní úzká hrdla:

1. **Zámková kontence hostitelského `dockerd` a únik stavu**: Nasměrování stovek souběžných RL workerů na jediný host `/var/run/docker.sock` dává každému agentovi přístup ekvivalentní rootovi k démonu hostitele, serializuje extrakci vrstev obrazů, vyčerpává podsítě `docker0`, vyvolává chyby `Another app is currently holding the xtables lock` v `iptables` hostitele a napříč epizodami zanechává osiřelé kontejnery.
2. **Únik z kontejneru u privilegovaného DinD**: Spouštění standardních kontejnerů `docker:dind` vyžaduje `--privileged` (`CAP_SYS_ADMIN`), což nechráněným RL rolloutům zpřístupňuje surová bloková zařízení hostitele, cgroup release agenty a jaderné moduly.
3. **Omezení hardwarové virtualizace (`/dev/kvm`)**: MicroVM runtime (`Firecracker`, `QEMU`, `Kata Containers`) čistě izolují vnitřní jádra, ale vyžadují `/dev/kvm`. Standardní pody Kubernetes bez vnořené virtualizace, bare-metal cloudové instance, kde je KVM vypnuto, a stávající sandboxy `gVisor` (`runsc`) nemohou spouštět microVM KVM.
4. **Pomalý reset epizody (`env.reset()`)**: Spuštění nového prostředí `docker:dind` a stahování nebo rozbalování základních obrazů pro každou RL epizodu trvá 10 až 60 sekund, takže GPU trénovací workery nečinně čekají na resety prostředí.

**[gvx](https://github.com/gfire-sigs/gvx)** (CLI `rungv` a Go SDK `z.gfire.dev/gvx/pkg/gvx`) poskytuje **bezpečný rollout runtime s obrannou hloubkou**, který řeší všechna čtyři úzká hrdla. Každé prostředí rolloutu běží jako **jediný neprivilegovaný, samozamykající se uživatelský proces** (izolovaný jaderem gVisor `Sentry` od Googlu, balíčkem `pkg/hostlockdown` a `netstack` v paměti), jehož celý stav `/var/lib/docker` se nachází uvnitř **jediného copy-on-write souboru `.ext4` (`ext4lite`)**.

> **Bezpečnost bez privilegií pro nedůvěryhodné rollouty agentů**: Každá epizoda `gvx` uzamkne svůj vlastní hostitelský proces OS dříve, než spustí hostující kód (`PR_SET_NO_NEW_PRIVS` + BPF `seccomp` na Linuxu, Seatbelt `deny default` na macOS, Restricted Token + Job Object na Windows). I když RL agent spustí `docker run --privileged` nebo destruktivní příkazy `rm -rf /` uvnitř své epizody, tyto operace zůstávají přísně omezeny na uživatelské jádro `Sentry` dané epizody a její efemérní svazek souboru `.ext4`.

---

## Architektura rolloutů: paralelní neprivilegované instance + reflink snapshoty

V `gvx` je každý rollout worker běžný neprivilegovaný proces OS se svým vlastním jádrem gVisor `Sentry`, vlastním uživatelským `netstack` (`10.0.2.15/24`) a vlastním blokovým svazkem `ext4lite` (`ep-N.ext4`), přičemž sdílí jen pro čtení určenou, obsahově adresovatelnou cache obrazů `erofs` OCI v page cache hostitele.

![Architektura paralelního RL rolloutu agentů s gvx](/assets/images/gvx/gvx-rl-rollout-snapshot.png)

### Proč svazky `ext4lite` v jediném souboru mění ekonomiku resetů RL

Protože filesystém `ext4lite` ukládá celý svazek `ext4` s žurnálem JBD2 (včetně adresářů vrstev `overlayfs` Dockeru, rozšířených atributů `trusted.overlay.*`, whiteout device nodes a metadat obrazů) uvnitř jediného běžného souboru hostitele, správa životního cyklu epizody se stává jedinou operací nad souborem:

1. **100milisekundový `env.reset()` pomocí `cp --reflink=always`**:
   - Jednou sestavíte soubor `golden-docker.ext4`, který obsahuje předem stažené základní obrazy (`python:3.11`, `postgres:16`, `redis:7` nebo obrazy základů specifické pro úlohy `SWE-bench`).
   - Na začátku každé RL epizody příkaz `cp --reflink=always golden-docker.ext4 ep-0042.ext4` (na XFS, Btrfs, ZFS nebo macOS APFS) vytvoří nezávislý klon Copy-on-Write za méně než 100 milisekund, aniž by kopíroval podkladové bloky disku.
   - Dodatečné místo na disku zabírají pouze 4 KiB bloky, které byly během dané epizody změněny.
2. **Úklid bez zbytků (`env.close()`)**:
   - Ukončení procesu `rungv` (`sb.Close()` v Go) a odlinkování `ep-0042.ext4` uvolní 100 % procesů, síťových socketů, připojení a úložiště epizody za ~10 milisekund. Nic se nikdy nepřipojí do mount table jádra hostitele ani se nezaregistruje v cgroup hostitele.

---

## Postup krok za krokem: zlatý snapshot `.ext4` a rychlý reset epizody

Příkaz `rungv` můžete nainstalovat na každý uzel rollout workeru buď stažením prebuilt binárky s atestací SLSA z [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`), nebo spuštěním `CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest`.

### Krok 1: Jednorázově vytvořte zlatý svazek `docker:dind`

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

# 1. Spusťte seed instanci docker:dind založenou na 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. Předpřipravte základní obrazy a cache sestavení uvnitř 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. Ukončete seed instanci elegantně, aby ext4lite provedl flush a kompaktaci obrazu
kill -INT $SEED_PID
wait $SEED_PID
```

### Krok 2: Spusťte paralelní RL epizody během 100 milisekund

Každý paralelní rollout worker klonuje `golden-docker.ext4` pomocí reflinku a spustí izolovanou instanci `docker:dind`, ve které jsou všechny obrazy již přítomny v `/var/lib/docker`:

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

# Okamžitý klon Copy-on-Write (~100 ms na XFS/Btrfs/APFS)
cp --reflink=always \
  /srv/rl-snapshots/golden-docker.ext4 \
  "/srv/rl-episodes/${EPISODE_ID}.ext4"

# Spuštění izolované neprivilegované epizody se 4 GiB RAM a 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

# Uvnitř rollout workeru jsou obrazy okamžitě dostupné bez stahování ze sítě
docker images

# Ukončení epizody po výpočtu odměny (~10 ms)
kill -KILL $EP_PID
rm -f "/srv/rl-episodes/${EPISODE_ID}.ext4" "${SOCK}"
```

---

## Programatický pool rolloutů pomocí Go SDK `z.gfire.dev/gvx/pkg/gvx`

Pokud je váš RL environment server nebo evaluační harness napsán v Go (nebo volá rollout daemon v Go přes gRPC), můžete paralelní epizody kontejnerů spravovat přímo prostřednictvím `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. Klonování golden ext4lite svazku pomocí reflinku
	cpCmd := exec.CommandContext(ctx, "cp", "--reflink=always", goldenExt4, epExt4)
	if err := cpCmd.Run(); err != nil {
		return fmt.Errorf("clone golden volume: %w", err)
	}

	// 2. Nastavení časového limitu epizody podle nástěnných hodin
	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, // limit 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)
	}
}
```

---

## Srovnání běhových prostředí pro sandboxy RL agentů

| Metrika / požadavek                                       | Sdílený hostitelský `dockerd`      | Privilegovaný `docker:dind` | Firecracker / KVM MicroVM          | Standardní `gVisor` (`runsc`)           | `gvx` (`rungv` / `pkg/gvx`)                  |
| :-------------------------------------------------------- | :--------------------------------- | :-------------------------- | :--------------------------------- | :-------------------------------------- | :------------------------------------------- |
| **Vyžaduje `root` hostitele nebo `--privileged`**         | Ano (`docker.sock` = root)         | Ano (`--privileged`)        | Potřebuje `/dev/kvm` + nastavení `TAP` | Ano (nebo `userns` + `mount`)       | **Ne (jediný neprivilegovaný proces)**       |
| **Funguje uvnitř neprivilegovaných K8s Podů / stávajícího `runsc`** | Ne                       | Ne                          | Ne (bez `/dev/kvm`)                | Selhává (`EBUSY` u vnořeného seccomp)   | **Ano (automatický záložní futex loop)**     |
| **Podporuje vnitřní `docker build` a `docker compose`**   | Sdílí démona hostitele             | Ano                         | Ano                                | Selhává bez ladění hostitelského DinD   | **Ano (`overlayfs` na `ext4lite`)**          |
| **Síťová izolace na úrovni epizody**                      | Sdílený `docker0` / zámek `xtables` | Oddělený namespace          | Oddělená NIC VM                    | Oddělený netns                          | **Izolovaný `netstack` v procesu**           |
| **Latence resetu teplé epizody (`env.reset()`)**          | 5 s až 30 s (prune kontejnerů/svazků) | 10 s až 60 s (studený démon) | ~150 ms (snapshot VM)              | 5 s až 20 s                             | **~100 ms (`cp --reflink=always` `.ext4`)**  |
| **Lokální vývoj napříč platformami (Windows / macOS / Linux)** | Vyžaduje VM s Linuxem          | Vyžaduje VM s Linuxem       | Pouze Linux KVM                    | Pouze Linux                             | **Nativně na Windows, macOS a Linuxu**       |

---

## Další čtení v této sérii

1. **[gvx: Spouštění kontejnerů Linuxu, Dockeru a Kubernetes v jediném neprivilegovaném procesu](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: Kompletní architektonický průvodce `gvx` napříč Windows (`whpx`, `systrap`), macOS (`hvf`) a Linuxem (`systrap`).
2. **[Spouštění Dockeru, docker build a docker compose bez rootu nebo user namespaces](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)**: Rychlý třikrokový návod pro bootstrap a řešení potíží, když jsou blokovány `sudo`, `unshare(CLONE_NEWUSER)`, `mount(2)` a `/dev/fuse`.
3. **[Docker-in-Docker uvnitř gVisor a vnořených sandboxů s gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: Hloubková analýza toho, proč `docker.io` a vnořený `runsc` selhávají uvnitř `gVisor` (`runsc`) a jak `gvx` přechází na záložní futex loop `_RUN_SYSCALL_LOOP` při `EBUSY`.
4. **[Rychlá izolovaná Docker prostředí pro RL rollouty AI agentů s gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)** (tento příspěvek)
