---
id: 517bf76b20da9b1bac643f4bac1bfd6b
author: Lemon Mint
title: Hurtige isolerede Docker-miljøer til AI Agent RL Rollouts med gvx
description: Sådan kører du sikkert hundredvis af parallelle, stærkt isolerede, uprivilegerede Docker- og Kubernetes-miljøer til SWE-bench- og coding agent-forstærkningslæringsrollouts med 100-millisekunders copy-on-write ext4lite-volumennulstillinger.
language: da
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
---

## Flaskehalsen for sikkerhed og infrastruktur i RL-rollouts for kodningsagenter

Træning og evaluering af softwareingeniøragenter på tværs af benchmarks og reinforcement learning (RL)-frameworks såsom **SWE-bench**, **SWE-ReX**, **R2E-Gym**, **SWE-Gym**, **Verl** og **OpenHands** kræver udførelse af tusindvis af samtidige rollouts af **ikke-betroet, LLM-genereret kode**. I virkelige softwareudviklingsopgaver redigerer en agent ikke blot tekstfiler: den kører `docker build`, starter databaser og mikroservices via `docker compose`, eksekverer integrationstestsuiter og inspicerer containerlogs.

Ved rollout-skala (128 til 1.024 samtidige episoder pr. cluster) rammer eksisterende containerinfrastruktur fire systemiske sikkerheds- og ydelsesflaskehalse:

1. **Lågekonflikt i værtens `dockerd` og tilstandslækage**: Når hundredvis af samtidige RL-workers peger på en enkelt værts `/var/run/docker.sock`, får hver agent root-ækvivalent adgang til værtsdæmonen, udpakning af image-lag serialiseres, `docker0`-subnetallokeringer udtømmes, der opstår fejl med `Another app is currently holding the xtables lock` i værtens `iptables`, og forældreløse containere lækker på tværs af episoder.
2. **Risiko for containerflugt ved privilegeret DinD**: Kørsel af standard `docker:dind`-containere kræver `--privileged` (`CAP_SYS_ADMIN`), hvilket eksponerer rå værts-blokenheder, cgroup release agents og kernemoduler over for ikke-betroede RL-rollouts.
3. **Begrænsninger ved hardwarevirtualisering (`/dev/kvm`)**: MicroVM-runtimes (`Firecracker`, `QEMU`, `Kata Containers`) isolerer den indre kerne rent, men de kræver `/dev/kvm`. Standard Kubernetes-pods uden nested virtualisering, bare-metal-cloudinstanser hvor KVM er deaktiveret, og eksisterende `gVisor` (`runsc`)-sandboxes kan ikke starte KVM-microVM'er.
4. **Høj latens ved episodenulstilling (`env.reset()`)**: Start af et nyt `docker:dind`-miljø og hentning eller udpakning af basisimager for hver RL-episode tager 10 til 60 sekunder, hvilket lader GPU-træningsworkers stå inaktive mens de venter på nulstilling af miljøet.

**[gvx](https://github.com/gfire-sigs/gvx)** (`rungv` CLI og `z.gfire.dev/gvx/pkg/gvx` Go SDK) leverer en **sikker rollout-runtime med defense-in-depth**, der løser alle fire flaskehalse. Hvert rollout-miljø kører som **en enkelt uprivilegeret, selvlåsende brugerrumsproces** (isoleret af Googles gVisor `Sentry`-kerne, `pkg/hostlockdown` og et in-memory `netstack`), hvis samlede `/var/lib/docker`-tilstand befinder sig inde i **en enkelt copy-on-write `.ext4`-fil (`ext4lite`)**.

> **Nul-privilegium-sikkerhed for ikke-betroede agent-rollouts**: Hver `gvx`-episode låser sin egen værts-OS-proces ned, før gæstekode køres (`PR_SET_NO_NEW_PRIVS` + BPF `seccomp` på Linux, Seatbelt `deny default` på macOS, Restricted Token + Job Object på Windows). Selv hvis en RL-agent udfører `docker run --privileged` eller destruktive `rm -rf /`-kommandoer inde i sin episode, forbliver disse operationer strengt begrænset til den pågældende episodes brugerrums-`Sentry`-kerne og dens midlertidige `.ext4`-volumenfil.

---

## Rollout-arkitektur: Parallelle uprivilegerede instanser + reflink-snapshots

I `gvx` er hver rollout-worker en almindelig uprivilegeret OS-proces med sin egen gVisor `Sentry`-kerne, sit eget brugerrums-`netstack` (`10.0.2.15/24`) og sit eget `ext4lite`-blokvolumen (`ep-N.ext4`), mens den deler en skrivebeskyttet, content-addressable `erofs` OCI-image-cache på tværs af værtens page cache.

![Parallel arkitektur til agent-RL-rollouts med gvx](/assets/images/gvx/gvx-rl-rollout-snapshot.png)

### Hvorfor enkeltfils-`ext4lite`-volumener ændrer økonomien ved RL-nulstillinger

Fordi `gvx`'s `ext4lite`-filsystem gemmer et helt JBD2-journaliseret `ext4`-volumen (inklusive Dockers `overlayfs`-lagmapper, `trusted.overlay.*`-udvidede attributter, whiteout-enhedsnoder og image-metadata) i én almindelig værtsfil, bliver episodens livscyklusstyring en enkelt filoperation:

1. **100-millisekunders `env.reset()` via `cp --reflink=always`**:
   - Du bygger én gang en `golden-docker.ext4`-fil, der indeholder forhåndshentede basisimager (`python:3.11`, `postgres:16`, `redis:7` eller opgavespecifikke `SWE-bench`-basisimager).
   - Ved starten af hver RL-episode opretter `cp --reflink=always golden-docker.ext4 ep-0042.ext4` (på XFS, Btrfs, ZFS eller macOS APFS) en uafhængig Copy-on-Write-klon på under 100 millisekunder uden at kopiere de underliggende diskblokke.
   - Kun de 4 KiB-blokke, der er ændret under den pågældende episode, bruger yderligere diskplads.
2. **Nedrivning uden rester (`env.close()`)**:
   - Afslutning af `rungv`-processen (`sb.Close()` i Go) og fjernelse af `ep-0042.ext4` frigiver 100% af episodens processer, netværkssockets, mounts og lager på ~10 millisekunder. Intet bliver nogensinde monteret i værtskernens mount-tabel eller registreret i værts-cgroups.

---

## Trin-for-trin-arbejdsgang: Gyldent `.ext4`-snapshot og hurtig episodenulstilling

Du kan installere `rungv` på hver rollout-workernode enten ved at downloade den SLSA-attesterede præbyggede binær fra [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`) eller ved at køre `CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest`.

### Trin 1: Opret det gyldne `docker:dind`-volumen én gang

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

# 1. Start en seed-docker:dind-instans, der er baseret på 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. Forudfyld basisimager og build-cache inde i 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. Stop seed-instansen elegant, så ext4lite skriver og komprimerer imaget
kill -INT $SEED_PID
wait $SEED_PID
```

### Trin 2: Start parallelle RL-episoder på 100 millisekunder

Hver parallel rollout-worker kloner `golden-docker.ext4` via reflink og starter en isoleret `docker:dind`-instans med alle images allerede til stede i `/var/lib/docker`:

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

# Øjeblikkelig Copy-on-Write-klon (~100 ms på XFS/Btrfs/APFS)
cp --reflink=always \
  /srv/rl-snapshots/golden-docker.ext4 \
  "/srv/rl-episodes/${EPISODE_ID}.ext4"

# Start isoleret, uprivilegeret episode med 4 GiB RAM og 2 CPU'er
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

# Inde i rollout-workeren er images straks tilgængelige uden netværkshentning
docker images

# Nedlukning af episoden efter beregning af belønning (~10 ms)
kill -KILL $EP_PID
rm -f "/srv/rl-episodes/${EPISODE_ID}.ext4" "${SOCK}"
```

---

## Programmatisk rollout-pool med `z.gfire.dev/gvx/pkg/gvx` Go SDK

Hvis dit RL-miljøserver eller din evalueringsharness er skrevet i Go (eller kalder en Go rollout-daemon over gRPC), kan du styre parallelle containerepisoder direkte via `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-klon det gyldne ext4lite-volumen
	cpCmd := exec.CommandContext(ctx, "cp", "--reflink=always", goldenExt4, epExt4)
	if err := cpCmd.Run(); err != nil {
		return fmt.Errorf("clone golden volume: %w", err)
	}

	// 2. Anvend en timeout pr. episode baseret på vægurstid
	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 loft
			CPUMillis:   2000,    // 2.0 CPU'er
		},
		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)
	}
}
```

---

## Sammenligning af runtimes til RL-sandboxes for agenter

| Metrik / krav                                             | Delt værts-`dockerd`              | Privilegeret `docker:dind` | Firecracker / KVM MicroVM          | Standard `gVisor` (`runsc`)        | `gvx` (`rungv` / `pkg/gvx`)                 |
| :-------------------------------------------------------- | :-------------------------------- | :------------------------- | :--------------------------------- | :--------------------------------- | :------------------------------------------ |
| **Værts-`root` eller `--privileged` påkrævet**            | Ja (`docker.sock` = root)         | Ja (`--privileged`)        | Kræver `/dev/kvm` + `TAP`-opsætning | Ja (eller `userns` + `mount`)      | **Nej (enkelt uprivilegeret proces)**       |
| **Virker i uprivilegerede K8s-Pods / eksisterende `runsc`** | Nej                             | Nej                        | Nej (ingen `/dev/kvm`)             | Fejler (`EBUSY` ved nested seccomp) | **Ja (automatisk futex-loop-fallback)**     |
| **Understøtter indre `docker build` og `docker compose`** | Deler værtsdæmonen                | Ja                         | Ja                                 | Fejler uden værts-DinD-tuning      | **Ja (`overlayfs` på `ext4lite`)**          |
| **Netværksisolation pr. episode**                         | Delt `docker0` / `xtables`-lås    | Separat namespace          | Separat VM-NIC                     | Separat netns                      | **Isoleret in-process `netstack`**          |
| **Latens ved varm episodenulstilling (`env.reset()`)**    | 5 s til 30 s (container/volumen-oprydning) | 10 s til 60 s (kold daemon) | ~150 ms (VM-snapshot)              | 5 s til 20 s                       | **~100 ms (`cp --reflink=always` `.ext4`)** |
| **Lokal udvikling på tværs af platforme (Windows / macOS / Linux)** | Kræver Linux-VM           | Kræver Linux-VM            | Kun Linux KVM                      | Kun Linux                          | **Native på Windows, macOS og Linux**       |

---

## Yderligere læsning i denne serie

1. **[gvx: Kørsel af Linux-containere, Docker og Kubernetes i én uprivilegeret proces](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: Komplet arkitekturgennemgang af `gvx` på tværs af Windows (`whpx`, `systrap`), macOS (`hvf`) og Linux (`systrap`).
2. **[Kørsel af Docker, docker build og docker compose uden root eller user namespaces](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)**: Hurtig bootstrap i 3 trin og fejlfindingsguide, når `sudo`, `unshare(CLONE_NEWUSER)`, `mount(2)` og `/dev/fuse` er blokeret.
3. **[Kørsel af Docker-in-Docker inde i gVisor og nested sandboxes med gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: Dybdegående gennemgang af, hvorfor `docker.io` og nested `runsc` fejler inde i `gVisor` (`runsc`), og hvordan `gvx` falder tilbage til `_RUN_SYSCALL_LOOP`-futex-loopet ved `EBUSY`.
4. **[Hurtige isolerede Docker-miljøer til AI-agent-RL-rollouts med gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)** (denne artikel)
