---
id: 517bf76b20da9b1bac643f4bac1bfd6b
author: Lemon Mint
title: Snabba isolerade Docker-miljöer för AI-agenters RL-rollouts med gvx
description: Hur man säkert kör hundratals parallella, starkt isolerade ej privilegierade Docker- och Kubernetes-miljöer för SWE-bench och förstärkningsinlärning av kodagenters rollouts med 100-millisekunders copy-on-write-återställning av ext4lite-volymer.
language: sv
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
---

## Flaskhalsen i säkerhet och infrastruktur för RL-rollouts med kodagenter

Träning och utvärdering av mjukvaruutvecklingsagenter över benchmarks och ramverk för förstärkningsinlärning (reinforcement learning, RL), såsom **SWE-bench**, **SWE-ReX**, **R2E-Gym**, **SWE-Gym**, **Verl** och **OpenHands**, kräver exekvering av tusentals samtidiga rollouts av **ej betrodd, LLM-genererad kod**. I verkliga uppgifter inom mjukvaruutveckling redigerar en agent inte bara textfiler: den kör `docker build`, startar databaser och mikrotjänster via `docker compose`, exekverar integrationstestsviter och inspekterar containerloggar.

Vid rollout-skala (128 till 1 024 samtidiga episoder per kluster) träffar befintlig containerinfrastruktur fyra systemiska flaskhalsar för säkerhet och prestanda:

1. **Låskonkurrens i värdens `dockerd` och tillståndsläckage**: När hundratals samtidiga RL-arbetare riktas mot en enda `/var/run/docker.sock` på värden får varje agent root-motsvarande åtkomst till värdens daemon, extraktionen av bildlager serialiseras, adressutrymmet i subnätet `docker0` tar slut, felen `Another app is currently holding the xtables lock` utlöses i värdens `iptables` och föräldralösa containrar läcker mellan episoderna.
2. **Risk för containerrymning via privilegierad DinD**: Att köra standardcontainrar med `docker:dind` kräver `--privileged` (`CAP_SYS_ADMIN`), vilket exponerar råa blockenheter på värden, cgroup release agents och kärnmoduler för ej betrodda RL-rollouts.
3. **Begränsningar för hårdvarubaserad virtualisering (`/dev/kvm`)**: MicroVM-runtimes (`Firecracker`, `QEMU`, `Kata Containers`) isolerar inre kärnor på ett rent sätt, men de kräver `/dev/kvm`. Standardpods i Kubernetes utan nästlad virtualisering, bare-metal-molninstanser där KVM är inaktiverat och befintliga `gVisor` (`runsc`)-sandlådor kan inte starta KVM-microVMs.
4. **Långsam latens vid episodåterställning (`env.reset()`)**: Att starta en ny `docker:dind`-miljö och hämta eller packa upp basbilder för varje RL-episod tar 10 till 60 sekunder, vilket lämnar GPU-träningsarbetare overksamma medan de väntar på miljöåterställningar.

**[gvx](https://github.com/gfire-sigs/gvx)** (`rungv` CLI och `z.gfire.dev/gvx/pkg/gvx` Go SDK) tillhandahåller en **säker rollout-runtime med försvar på flera nivåer** som löser alla fyra flaskhalsarna. Varje rollout-miljö körs som **en enda oprivilegierad, självlåst process i användarutrymmet** (isolerad av Googles gVisor-kärna `Sentry`, `pkg/hostlockdown` och ett minnesbaserat `netstack`) vars hela tillstånd i `/var/lib/docker` finns inuti **en enda kopiera-vid-skrivning-fil `.ext4` (`ext4lite`)**.

> **Nollprivilegiesäkerhet för ej betrodda agent-rollouts**: Varje `gvx`-episod låser ned sin egen värdprocess i operativsystemet innan gästkod körs (`PR_SET_NO_NEW_PRIVS` + BPF `seccomp` på Linux, Seatbelt `deny default` på macOS, Restricted Token + Job Object på Windows). Även om en RL-agent kör `docker run --privileged` eller destruktiva `rm -rf /`-kommandon inom sin episod, förblir dessa operationer strikt begränsade till episodens kärna i användarutrymmet (`Sentry`) och dess tillfälliga volymfil `.ext4`.

---

## Rollout-arkitektur: Parallella oprivilegierade instanser + reflink-ögonblicksbilder

I `gvx` är varje rollout-arbetare en vanlig oprivilegierad OS-process med sin egen gVisor-kärna `Sentry`, sitt eget `netstack` i användarutrymmet (`10.0.2.15/24`) och sin egen blockvolym `ext4lite` (`ep-N.ext4`). Samtidigt delar arbetarna en skrivskyddad, innehållsadresserbar OCI-bildcache i formatet `erofs` över värdens sidcache.

![Parallell RL-rollout-arkitektur för agenter med gvx](/assets/images/gvx/gvx-rl-rollout-snapshot.png)

### Varför volymer i form av enskilda `ext4lite`-filer förändrar ekonomin för RL-återställning

Eftersom `ext4lite`-filsystemet i `gvx` lagrar en hel JBD2-journalförd `ext4`-volym (inklusive Dockers lagerkataloger för `overlayfs`, utökade attribut `trusted.overlay.*`, whiteout-enhetsnoder och bildmetadata) i en enda vanlig värdfil, blir hanteringen av episodens livscykel en enda filoperation:

1. **100 millisekunders `env.reset()` via `cp --reflink=always`**:
   - Du bygger en gång en fil `golden-docker.ext4` som innehåller förhämtade basbilder (`python:3.11`, `postgres:16`, `redis:7` eller uppgiftsspecifika basbilder för `SWE-bench`).
   - I början av varje RL-episod skapar `cp --reflink=always golden-docker.ext4 ep-0042.ext4` (på XFS, Btrfs, ZFS eller macOS APFS) en oberoende klon med kopiera-vid-skrivning på under 100 millisekunder, utan att kopiera underliggande diskblock.
   - Endast de 4 KiB-block som ändras under just den episoden tar upp ytterligare diskutrymme.
2. **Nedtagning utan kvarlämnade spår (`env.close()`)**:
   - Att avsluta processen `rungv` (`sb.Close()` i Go) och ta bort `ep-0042.ext4` frigör 100 % av episodens processer, nätverkssockets, monteringar och lagring på ungefär 10 millisekunder. Ingenting monteras någonsin i värdkärnans monteringstabell eller registreras i värdens cgroups.

---

## Steg-för-steg-arbetsflöde: Gyllene ögonblicksbild `.ext4` och snabb episodåterställning

Du kan installera `rungv` på varje rollout-arbetarnod antingen genom att ladda ner den SLSA-attesterade förkompilerade binären från [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 genom att köra `CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest`.

### Steg 1: Skapa den gyllene volymen `docker:dind` en gång

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

# 1. Starta en startinstans av docker:dind med stöd från 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. Förfyll basbilder och byggcache inuti 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. Stoppa startinstansen på ett kontrollerat sätt så att ext4lite spolar och kompakterar avbildningen
kill -INT $SEED_PID
wait $SEED_PID
```

### Steg 2: Starta parallella RL-episoder på 100 millisekunder

Varje parallell rollout-arbetare klonar `golden-docker.ext4` via reflink och startar en isolerad `docker:dind`-instans med alla bilder redan närvarande i `/var/lib/docker`:

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

# Omedelbar klon med kopiera-vid-skrivning (~100 ms på XFS/Btrfs/APFS)
cp --reflink=always \
  /srv/rl-snapshots/golden-docker.ext4 \
  "/srv/rl-episodes/${EPISODE_ID}.ext4"

# Starta isolerad oprivilegierad episod med 4 GiB RAM och 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

# Inuti rollout-arbetaren finns bilderna omedelbart tillgängliga utan nätverkshämtning
docker images

# Nedtagning av episoden efter beräkning av belöningen (~10 ms)
kill -KILL $EP_PID
rm -f "/srv/rl-episodes/${EPISODE_ID}.ext4" "${SOCK}"
```

---

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

Om din RL-miljöserver eller utvärderingsharness är skriven i Go (eller anropar en Go-daemon för rollouts över gRPC) kan du hantera parallella containerepisoder direkt 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-klona den gyllene ext4lite-volymen
	cpCmd := exec.CommandContext(ctx, "cp", "--reflink=always", goldenExt4, epExt4)
	if err := cpCmd.Run(); err != nil {
		return fmt.Errorf("clone golden volume: %w", err)
	}

	// 2. Tillämpa en tidsgräns per episod (väggklocka)
	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 tak
			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)
	}
}
```

---

## Jämförelse av runtimes för sandlådor för RL-agenter

| Mätvärde / krav                                           | Delad värd-`dockerd`              | Privilegierad `docker:dind` | Firecracker / KVM MicroVM          | Standard-`gVisor` (`runsc`)          | `gvx` (`rungv` / `pkg/gvx`)                     |
| :-------------------------------------------------------- | :-------------------------------- | :-------------------------- | :--------------------------------- | :----------------------------------- | :---------------------------------------------- |
| **Kräver värdens `root` eller `--privileged`**            | Ja (`docker.sock` = root)         | Ja (`--privileged`)         | Kräver `/dev/kvm` + `TAP`-konfiguration | Ja (eller `userns` + `mount`)   | **Nej (en enda oprivilegierad process)**        |
| **Fungerar i oprivilegierade K8s-pods / befintlig `runsc`** | Nej                             | Nej                         | Nej (ingen `/dev/kvm`)             | Misslyckas (`EBUSY` vid nästlad seccomp) | **Ja (automatisk reservlösning med futex-loop)** |
| **Stöder inre `docker build` och `docker compose`**       | Delar värddaemon                  | Ja                          | Ja                                 | Misslyckas utan DinD-justering på värden | **Ja (`overlayfs` på `ext4lite`)**             |
| **Nätverksisolering per episod**                          | Delat `docker0` / `xtables`-lås   | Separat namnrymd            | Separat VM-nätverkskort            | Separat netns                        | **Isolerat `netstack` i processen**             |
| **Latens för återställning av varm episod (`env.reset()`)** | 5 s till 30 s (beskärning av container/volym) | 10 s till 60 s (kall daemon) | ~150 ms (VM-ögonblicksbild) | 5 s till 20 s                  | **~100 ms (`cp --reflink=always` `.ext4`)**     |
| **Plattformsoberoende lokal utveckling (Windows / macOS / Linux)** | Kräver Linux-VM            | Kräver Linux-VM             | Endast Linux KVM                   | Endast Linux                         | **Native på Windows, macOS och Linux**          |

---

## Fler artiklar i denna serie

1. **[gvx: Köra Linux-containrar, Docker och Kubernetes i en enda oprivilegierad process](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: Fullständig arkitekturgenomgång av `gvx` på Windows (`whpx`, `systrap`), macOS (`hvf`) och Linux (`systrap`).
2. **[Köra Docker, docker build och docker compose utan root eller användarnamnrymder](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)**: Snabb tregegsuppstart och felsökningsguide när `sudo`, `unshare(CLONE_NEWUSER)`, `mount(2)` och `/dev/fuse` är blockerade.
3. **[Köra Docker-in-Docker inuti gVisor och nästlade sandlådor med gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: Fördjupning i varför `docker.io` och nästlad `runsc` misslyckas inuti `gVisor` (`runsc`) och hur `gvx` faller tillbaka på futex-loopen `_RUN_SYSCALL_LOOP` vid `EBUSY`.
4. **[Snabba isolerade Docker-miljöer för RL-rollouts med AI-agenter med gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)** (detta inlägg)
