GoSuda

AI Ajanı RL Rollout'ları için gvx ile Hızlı ve İzole Docker Ortamları

By Lemon Mint views ...

Kodlama Ajanı RL Rollout'larında Güvenlik ve Altyapı Darboğazı

SWE-bench, SWE-ReX, R2E-Gym, SWE-Gym, Verl ve OpenHands gibi kıyaslamalar ve pekiştirmeli öğrenme (RL) çerçeveleri genelinde yazılım mühendisliği ajanlarının eğitilmesi ve değerlendirilmesi, güvenilmeyen, LLM tarafından üretilen kodun binlerce eşzamanlı rollout'ta çalıştırılmasını gerektirir. Gerçek dünya yazılım mühendisliği görevlerinde bir ajan yalnızca metin dosyalarını düzenlemez: docker build komutunu çalıştırır, docker compose aracılığıyla veritabanları ve mikro hizmetleri ayağa kaldırır, entegrasyon test paketlerini yürütür ve konteyner günlüklerini inceler.

Rollout ölçeğinde (küme başına 128 ila 1.024 eşzamanlı episode), mevcut konteyner altyapısı dört sistemik güvenlik ve performans darboğazıyla karşılaşır:

  1. Host dockerd Kilit Çekişmesi ve Durum Sızıntısı: Yüzlerce eşzamanlı RL worker'ını tek bir host /var/run/docker.sock üzerine yönlendirmek, her ajana host daemon'ına karşı root eşdeğeri erişim verir, imaj katmanı çıkarımını seri hale getirir, docker0 alt ağ tahsislerini tüketir, host iptables üzerinde Another app is currently holding the xtables lock hatalarını tetikler ve episode'lar arasında sahipsiz konteynerlerin sızmasına yol açar.
  2. Ayrıcalıklı DinD Konteyner Kaçış Riski: Standart docker:dind konteynerlerini çalıştırmak --privileged (CAP_SYS_ADMIN) gerektirir; bu da ham host blok aygıtlarını, cgroup release agent'larını ve çekirdek modüllerini güvenilmeyen RL rollout'larına açar.
  3. Donanım Sanallaştırma (/dev/kvm) Kısıtları: MicroVM çalışma zamanları (Firecracker, QEMU, Kata Containers) iç çekirdekleri temiz biçimde izole eder, ancak /dev/kvm gerektirirler. İç içe sanallaştırma olmayan standart Kubernetes pod'ları, KVM'nin devre dışı bırakıldığı bare-metal bulut örnekleri ve mevcut gVisor (runsc) sandbox'ları KVM microVM'lerini başlatamaz.
  4. Yavaş Episode Sıfırlama (env.reset()) Gecikmesi: Her RL episode için yeni bir docker:dind ortamı başlatmak ve temel imajları çekmek veya açmak 10 ila 60 saniye sürer; bu süre boyunca GPU eğitim worker'ları ortam sıfırlamalarını beklerken boşta kalır.

gvx (rungv CLI ve z.gfire.dev/gvx/pkg/gvx Go SDK), dört darboğazın tamamını çözen güvenli, katmanlı savunmaya sahip bir rollout çalışma zamanı sunar. Her rollout ortamı, tek başına çalışan, ayrıcalıksız ve kendini kilitleyen bir kullanıcı alanı süreci olarak çalışır (Google'ın gVisor Sentry çekirdeği, pkg/hostlockdown ve bellek içi netstack ile izole edilmiştir); bu sürecin tüm /var/lib/docker durumu tek bir copy-on-write .ext4 dosyasının (ext4lite) içinde bulunur.

Güvenilmeyen Ajan Rollout'ları için Sıfır Ayrıcalık Güvenliği: Her gvx episode'u, misafir kodu çalıştırmadan önce kendi host işletim sistemi sürecini kilitler (Linux'ta PR_SET_NO_NEW_PRIVS + BPF seccomp, macOS'ta Seatbelt deny default, Windows'ta Restricted Token + Job Object). Bir RL ajanı episode'u içinde docker run --privileged veya yıkıcı rm -rf / komutlarını çalıştırsa bile, bu işlemler kesin olarak o episode'un kullanıcı alanı Sentry çekirdeği ve geçici .ext4 hacim dosyasıyla sınırlı kalır.


Rollout Mimarisi: Paralel Ayrıcalıksız Örnekler + Reflink Anlık Görüntüleri

gvx içinde her rollout worker'ı, kendi gVisor Sentry çekirdeğine, kendi kullanıcı alanı netstack yapılandırmasına (10.0.2.15/24) ve kendi ext4lite blok hacmine (ep-N.ext4) sahip sıradan bir ayrıcalıksız işletim sistemi sürecidir; host sayfa önbelleğini ise salt okunur, içerik adresli bir erofs OCI imaj önbelleğiyle paylaşır.

gvx ile Paralel Ajan RL Rollout Mimarisi

Tek Dosyalı ext4lite Hacimleri RL Sıfırlama Ekonomisini Neden Değiştirir

gvx'in ext4lite dosya sistemi, tüm JBD2 günlüklemeli ext4 hacmini (Docker'ın overlayfs katman dizinleri, trusted.overlay.* genişletilmiş öznitelikleri, whiteout aygıt düğümleri ve imaj meta verileri dahil) tek bir normal host dosyasında sakladığından, episode yaşam döngüsü yönetimi tek bir dosya işlemine dönüşür:

  1. cp --reflink=always ile 100 Milisaniyelik env.reset():
    • Önceden çekilmiş temel imajları (python:3.11, postgres:16, redis:7 veya göreve özgü SWE-bench temel imajları) içeren bir golden-docker.ext4 dosyasını bir kez oluşturursunuz.
    • Her RL episode'unun başında, cp --reflink=always golden-docker.ext4 ep-0042.ext4 komutu (XFS, Btrfs, ZFS veya macOS APFS üzerinde), altta yatan disk bloklarını kopyalamadan 100 milisaniyenin altında bağımsız bir Copy-on-Write klonu oluşturur.
    • Yalnızca o belirli episode sırasında kirlenen 4 KiB'lik bloklar ek disk alanı tüketir.
  2. Artık Bırakmayan Kapatma (env.close()):
    • rungv sürecinin sonlandırılması (Go'da sb.Close()) ve ep-0042.ext4 dosyasının bağlantısının kaldırılması, episode'un tüm süreçlerini, ağ soketlerini, bağlama noktalarını ve depolamasını yaklaşık 10 milisaniyede %100 geri kazanır. Host çekirdeğinin bağlama tablosuna hiçbir şey bağlanmaz ve host cgroup'larına hiçbir şey kaydedilmez.

Adım Adım İş Akışı: Golden .ext4 Anlık Görüntüsü ve Hızlı Episode Sıfırlama

rungv aracını her rollout worker düğümüne, ya GitHub Releases adresinden SLSA ile doğrulanmış önceden derlenmiş ikiliyi indirerek (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) ya da CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest komutunu çalıştırarak kurabilirsiniz.

Adım 1: Golden docker:dind Hacmini Bir Kez Oluşturun

 1mkdir -p /srv/rl-snapshots /srv/rl-episodes
 2
 3# 1. golden-docker.ext4 ile desteklenen bir tohum docker:dind örneği başlatın
 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. golden-docker.ext4 içinde temel imajları ve derleme önbelleğini önceden doldurun
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. ext4lite'ın imajı boşaltıp sıkıştırması için tohum örneğini zarifçe durdurun
20kill -INT $SEED_PID
21wait $SEED_PID

Adım 2: Paralel RL Episode'larını 100 Milisaniyede Başlatın

Her paralel rollout worker'ı golden-docker.ext4 dosyasını reflink ile klonlar ve tüm imajları zaten /var/lib/docker içinde bulunan yalıtılmış bir docker:dind örneğini başlatır:

 1EPISODE_ID="ep-0042"
 2SOCK="/srv/rl-episodes/${EPISODE_ID}.sock"
 3
 4# Anlık Copy-on-Write klonu (XFS/Btrfs/APFS üzerinde ~100 ms)
 5cp --reflink=always \
 6  /srv/rl-snapshots/golden-docker.ext4 \
 7  "/srv/rl-episodes/${EPISODE_ID}.ext4"
 8
 9# 4 GiB RAM ve 2 CPU ile yalıtılmış ayrıcalıksız episode'u başlatın
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 worker'ının içinde imajlar ağ üzerinden çekilmeden hemen kullanılabilir
22docker images
23
24# Ödül hesaplamasından sonra episode'un kapatılması (~10 ms)
25kill -KILL $EP_PID
26rm -f "/srv/rl-episodes/${EPISODE_ID}.ext4" "${SOCK}"

z.gfire.dev/gvx/pkg/gvx Go SDK'sını Kullanan Programatik Rollout Havuzu

RL ortam sunucunuz veya değerlendirme altyapınız Go ile yazılmışsa (ya da gRPC üzerinden bir Go rollout daemon'ını çağırıyorsa), paralel konteyner episode'larını doğrudan z.gfire.dev/gvx/pkg/gvx üzerinden yönetebilirsiniz:

 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. Golden ext4lite hacminin reflink klonunu oluşturun
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. Episode başına duvar saati zaman aşımı uygulayın
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 sınırı
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}

Agent RL Sandbox Çalışma Zamanlarının Karşılaştırması

Metrik / GereksinimPaylaşımlı Host dockerdAyrıcalıklı docker:dindFirecracker / KVM MicroVMStandart gVisor (runsc)gvx (rungv / pkg/gvx)
Host root veya --privileged gerekli miEvet (docker.sock = root)Evet (--privileged)/dev/kvm + TAP kurulumu gerekirEvet (veya userns + mount)Hayır (tek ayrıcalıksız süreç)
Ayrıcalıksız K8s Pod'ları / mevcut runsc içinde çalışır mıHayırHayırHayır (/dev/kvm yok)Başarısız (iç içe seccomp üzerinde EBUSY)Evet (otomatik futex döngüsü yedeği)
İç docker build ve docker compose desteğiHost daemon'ını paylaşırEvetEvetHost DinD ayarı olmadan başarısızEvet (ext4lite üzerinde overlayfs)
Episode başına ağ yalıtımıPaylaşımlı docker0 / xtables kilidiAyrı namespaceAyrı VM NICAyrı netnsSüreç içi yalıtılmış netstack
Sıcak episode sıfırlama gecikmesi (env.reset())5 sn ila 30 sn (konteyner/hacim temizliği)10 sn ila 60 sn (soğuk daemon)~150 ms (VM anlık görüntüsü)5 sn ila 20 sn~100 ms (cp --reflink=always .ext4)
Platformlar arası yerel geliştirme (Windows / macOS / Linux)Linux VM gerekirLinux VM gerekirYalnızca Linux KVMYalnızca LinuxWindows, macOS ve Linux üzerinde yerel

Bu Serideki Diğer Okumalar

  1. gvx: Linux Konteynerlerini, Docker'ı ve Kubernetes'i Tek Bir Ayrıcalıksız Süreçte Çalıştırmak: Windows (whpx, systrap), macOS (hvf) ve Linux (systrap) üzerinde gvx için eksiksiz bir mimari inceleme.
  2. Root veya Kullanıcı Namespace'leri Olmadan Docker, docker build ve docker compose Çalıştırmak: sudo, unshare(CLONE_NEWUSER), mount(2) ve /dev/fuse engellendiğinde hızlı 3 adımlı başlangıç ve sorun giderme kılavuzu.
  3. gvx ile gVisor ve İç İçe Sandbox'lar İçinde Docker-in-Docker Çalıştırmak: docker.io ve iç içe runsc'nin gVisor (runsc) içinde neden başarısız olduğuna ve gvx'in EBUSY durumunda _RUN_SYSCALL_LOOP futex döngüsüne nasıl geri döndüğüne dair ayrıntılı inceleme.
  4. gvx ile AI Ajanı RL Rollout'ları için Hızlı Yalıtılmış Docker Ortamları (bu yazı)