GoSuda

gVisor İçinde ve İç İçe Sandbox'larda gvx ile Docker-in-Docker Çalıştırma

By Lemon Mint views ...

Hızlı Çözüm: Kısıtlı Ayrıcalıklara Sahip Bir gVisor (runsc) Sandbox İçinde docker:dind'i Güvenli Şekilde Çalıştırma

Bulut AI agent platformları, Kubernetes agent-sandbox dağıtımları (RuntimeClass: gvisor) ve pekiştirmeli öğrenme değerlendirme altyapıları, güvenilmeyen iş yüklerini özellikle ana makinenin çekirdeğini korumak amacıyla gVisor (runsc) konteynerlerinin içinde izole eder. Varsayılan, ayrıcalıksız bir runsc konteyneri içindeki bir iş yükü apt-get ile docker.io veya docker-ce kurmaya ve dockerd'yi başlatmaya çalıştığında, konteyner içinde root (UID 0) yetkisine sahip olsa bile işlem başarısız olur.

gvx (rungv) ile docker:dind, docker build ve docker compose komutlarını ayrıcalıksız bir gVisor (runsc) konteyneri içinde doğrudan güvenli şekilde çalıştırabilirsiniz (veya gvx'i gvx içinde iç içe kullanabilirsiniz). Bunu yaparken dış RuntimeClass yapılandırmasını zayıflatmanıza, ana makine soketlerini açmanıza veya --privileged yetkisi vermenize gerek yoktur.

Dış Sandbox İzolasyonunun Korunması ve Güçlendirilmesi: runsc içinde rungv çalıştırmak, dış gVisor sandbox'ı tarafından uygulanan hiçbir kısıtlamayı aşmaz. Bunun yerine gvx, kendi hostlockdown (PR_SET_NO_NEW_PRIVS + seccomp) yapılandırmasına, kendi bellek içi netstack bileşenine ve tek dosyalık bir ext4lite birimine sahip ikinci bir iç içe kullanıcı alanı gVisor Sentry çekirdeği oluşturur. İç dockerd ve runc konteynerleri çift katmanlı olarak sandbox'lanır: sistem çağrıları, bağlamalar (mount) ve ham paketler iç gvx kullanıcı alanı çekirdeğinin dışına çıkmaz.

 1# Mevcut bir gVisor (runsc) konteyneri veya Kubernetes Pod'u içinde:
 2# rungv'yi SLSA ile doğrulanmış bir GitHub sürüm arşivinden veya go install ile kurun:
 3CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest
 4
 5mkdir -p ~/.gvx
 6rungv run \
 7  -p unix:~/.gvx/docker.sock:2375 \
 8  -v gvx-docker:/var/lib/docker \
 9  -e DOCKER_TLS_CERTDIR= \
10  docker:dind --tls=false &
11
12# Standart Docker build ve konteyner işlemlerini iç gvx soketine karşı çalıştırın
13export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
14until docker info >/dev/null 2>&1; do sleep 0.5; done
15docker info --format '{{.Driver}} {{.OperatingSystem}}'
16# Çıktı: overlayfs Alpine Linux ... (containerized)
17
18docker build -t nested-app:test .
19docker run --rm nested-app:test

gVisor (runsc) İçinde Doğrudan Docker Kurulumunun Başarısız Olmasının Beş Nedeni

Bir kullanıcı veya AI kodlama agent'ı standart bir runsc konteynerinde apt-get install -y docker.io (veya docker-ce) komutunu çalıştırıp dockerd'yi başlatmaya çalıştığında, art arda gelen beş teknik engelle karşılaşır.

1. Sanal Çekirdek 4.4.0 Üzerinde Kernel Modülü Yükleme (modprobe)

Paket kurulumu ve dockerd başlatma sırasında, başlatma betikleri overlay, br_netfilter, ip_tables, nf_nat ve xt_conntrack modüllerini yüklemek için modprobe çalıştırır. Dış gVisor Sentry, init_module(2) ve finit_module(2) çağrılarını engeller ve /lib/modules/4.4.0 dizini bulunmayan sanal bir çekirdek sürümü (4.4.0) bildirir:

1modprobe: FATAL: Module overlay not found in directory /lib/modules/4.4.0
2modprobe: FATAL: Module br_netfilter not found in directory /lib/modules/4.4.0
3modprobe: FATAL: Module ip_tables not found in directory /lib/modules/4.4.0
4modprobe: FATAL: Module nf_nat not found in directory /lib/modules/4.4.0

2. iptables-nft (nf_tables Netlink) ve docker0 Köprü Hataları

Ubuntu 22.04, Ubuntu 24.04 ve Debian 11/12 üzerinde apt-get install docker.io komutu, varsayılan /usr/sbin/iptables alternatifi olarak iptables-nft seçer (bu, nf_tables netlink alt sistemi üzerine kuruludur). Ağ yönetici ayrıcalıkları veya nf_tables netlink desteği olmayan standart bir gVisor konteynerinde, dockerd DOCKER NAT zincirini ve docker0 köprüsünü başlatırken çöker:

1failed to start daemon: Error initializing network controller: error obtaining controller instance: failed to create NAT chain DOCKER: iptables failed: iptables -t nat -N DOCKER
2iptables v1.8.10 (nf_tables): Could not fetch rule set generation id: Invalid argument
3iptables v1.8.10 (legacy): can't initialize iptables table 'nat': Table does not exist (do you need to insmod?)
4iptables: No chain/target/match by that name.
5Unable to enable SKIP DNAT rule: (iptables failed: iptables --wait -t nat -I DOCKER -i docker0 -j RETURN: iptables: No chain/target/match by that name.)
6Error initializing network controller: error obtaining controller instance: failed to create bridge "docker0": operation not permitted

3. gofer / 9p Kök Dosya Sisteminde overlay2 Hatası ve vfs Disk Tükenmesi

Dış runsc yöneticisi /var/lib/docker yolunda özel olarak yapılandırılmış bir tmpfs veya filestore bağlaması önceden ayarlamadığı sürece, konteyner kök dosya sistemi gVisor'ın gofer (9p/lisafs) bileşeni veya iç içe bir overlay bağlaması tarafından desteklenir. /var/lib/docker üzerine overlay2 bağlamak başarısız olur:

1level=error msg="[graphdriver] prior storage driver overlay2 failed: driver not supported"
2failed to mount overlay: invalid argument
3Error starting daemon: error initializing graphdriver: driver not supported: overlay2

Kullanıcılar veya agent'lar dockerd --storage-driver=vfs ile başlatarak bu hatayı aşmaya çalıştığında, Docker Copy-on-Write katman paylaşımını tamamen devre dışı bırakır. Bir Dockerfile içindeki her RUN talimatı, tüm kök dosya sisteminin tam bir özyinelemeli kopyasını (dirCopy) oluşturur. Bu da 10 build adımında 500 MB'lık bir imajı 5 GB yazma işlemine çıkarır ve sandbox'ın no space left on device hatası veya G/Ç zaman aşımıyla sonlanmasına neden olur.

4. İç İçe runc Cgroup ve /proc Bağlama Reddi

dockerd --iptables=false --bridge=none --storage-driver=vfs seçenekleriyle zorla başlatılsa bile, docker run veya docker build komutu iç konteyneri oluşturmak için runc'yi çağırır. Varsayılan ayrıcalıksız bir gVisor konteynerinde runc, cgroup bağlarken, /proc dosya sistemini yeniden bağlarken veya iç seccomp filtrelerini yüklerken başarısız olur:

1docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process:
2error during container init: error mounting "cgroup" to rootfs at "/sys/fs/cgroup": mount cgroup:/sys/fs/cgroup/cpuset (via /proc/self/fd/6), flags: 0xf, data: cpuset: operation not permitted
3cgroups: cgroup mountpoint does not exist: unknown
4runc create failed: systemd not running on this host: cannot use systemd cgroup manager
5error during container init: error mounting "proc" to rootfs at "/proc": mount proc:/proc (via /proc/self/fd/6), flags: 0xe: operation not permitted
6error during container init: error loading seccomp filter into kernel: operation not permitted

5. İç İçe runsc, SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY) Hatasıyla Başarısız Olur

Bir kullanıcı systrap platformunu kullanarak runsc içinde başka bir runsc örneği çalıştırmaya çalışırsa, başlatma hemen iptal edilir:

1creating subprocess: sysmsg: failed to initialize thread: failed to install seccomp notify rules: device or resource busy

Hem Linux çekirdeği hem de gVisor Sentry, görev hiyerarşisi başına en fazla bir SECCOMP_FILTER_FLAG_NEW_LISTENER (SECCOMP_RET_USER_NOTIF) dinleyicisine izin verir. Dış sandbox bu dinleyici yuvasını zaten kullandığı için, iç seccomp(2) çağrısı EBUSY (device or resource busy) döndürür.


gvx'in gVisor İçinde İç İçe Çalıştırmayı Nasıl Çözdüğü

gvx, beş engelin tamamını kendi kullanıcı alanı çekirdeği ve platform katmanı içinde ele alır. Bu sayede rungv, dış bir gVisor (runsc) konteyneri içinde veya başka bir gvx konteyneri içinde (yani gvx içinde gvx) özyinelemeli olarak çalışabilir.

gVisor İçinde İç İçe gvx Çalıştırma

1. EBUSY Hatasından _RUN_SYSCALL_LOOP Futex Döngüsüne Otomatik systrap Geri Dönüşü

Linux systrap içinde her stub alt süreci iki tür iş parçacığı çalıştırır:

  • Konuk çalıştırma iş parçacıkları (sysmsg): Konuk sistem çağrılarını SECCOMP_RET_TRAP (SIGSYS) ve ikili olarak yamalanmış jmp rel32 trampolinleri (syshandler) aracılığıyla yakalar. Kayıt durumlarını paylaşımlı bellek ve SYS_FUTEX üzerinden Sentry ile değiş tokuş eder.
  • Bir stub syscallThread (syscall_thread.go): Sentry adına, stub alt süreci içinde adres alanı sistem çağrılarını (mmap, munmap, clone) çalıştırır.

Upstream gVisor'da syscallThread, stub_amd64.s ve stub_arm64.s dosyalarında iki uyandırma modunu destekler:

  1. Seccomp User-Notify Döngüsü (_RUN_SECCOMP_LOOP = 6, t.seccompNotify != nil olduğunda): syscallThread.init() sırasında t.installSeccompNotify(), bir SECCOMP_RET_USER_NOTIF dinleyicisi kurar (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)). Böylece Sentry, istekleri ioctl(SECCOMP_IOCTL_NOTIF_SEND) ve ioctl(SECCOMP_IOCTL_NOTIF_RECV) ile gönderebilir.
  2. Futex Sistem Çağrısı Döngüsü (_RUN_SYSCALL_LOOP = 5, t.seccompNotify == nil olduğunda): syscallThread, syscall_loop (stub_amd64.s / stub_arm64.s) çalıştırır. Paylaşımlı 2 sayfalık syscallSentryMessage (stub içinde PROT_READ) ve syscallStubMessage (stub içinde PROT_READ | PROT_WRITE) bölgesi üzerinde SYS_FUTEX (FUTEX_WAIT) ile uykuya geçer.

Upstream gVisor, seccompNotifyIsSupported değeri doğru olduğunda t.installSeccompNeotify() fonksiyonunu koşulsuz olarak çağırır ve seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, ...) EBUSY döndürürse stub oluşturmayı iptal eder.

gvx içinde (pkg/sentry/platform/systrap/seccomp_notify_xplat.go ve syscall_thread.go), syscallThread.init() şu fonksiyonu çağırır: t.installSeccompNotifyIfFree():

 1// pkg/sentry/platform/systrap/seccomp_notify_xplat.go
 2var seccompNotifyBusy atomic.Bool
 3
 4func (t *syscallThread) installSeccompNotifyIfFree() (*os.File, error) {
 5	if seccompNotifyBusy.Load() {
 6		return nil, nil
 7	}
 8	f, err := t.installSeccompNotify()
 9	if errors.Is(err, unix.EBUSY) {
10		if seccompNotifyBusy.CompareAndSwap(false, true) {
11			log.Warningf("Another seccomp listener exists in the filter chain of the host process. " +
12				"The stub threads use the slower futex loop instead of seccomp-unotify.")
13		}
14		return nil, nil
15	}
16	return f, err
17}

installSeccompNotify() fonksiyonu, dış bir gVisor (runsc) veya dış gvx sandbox'ı süreç filtre zincirinin tek dinleyici yuvasını zaten kullandığı için unix.EBUSY döndürdüğünde:

  • installSeccompNotifyIfFree() seccompNotifyBusy (atomic.Bool) değerini ayarlar, süreç için bir uyarı kaydeder ve bu stub ile sonraki tüm stub'lar için t.seccompNotify == nil durumunu korur.
  • syscallThread.detach() (syscall_thread_amd64.go ve syscall_thread_arm64.go) içinde t.seccompNotify == nil ise, _RUN_SECCOMP_LOOP (6) yerine amd64 üzerinde regs.Rbx = _RUN_SYSCALL_LOOP (5) veya arm64 üzerinde regs.Regs[9] = _RUN_SYSCALL_LOOP ayarlanır.
  • syscallThread.syscall() (syscall_thread.go) içinde iç Sentry, stub sistem çağrılarını yalnızca paylaşımlı bellek ve SYS_FUTEX üzerinden gönderir:
1// pkg/sentry/platform/systrap/syscall_thread.go
2// Syscall thread'ine yeni bir sistem çağrısı isteği olduğunu bildir.
3atomic.AddUint32(&sentryMsg.state, 1)
4futexWakeUint32(&sentryMsg.state)
5
6// Yanıtı bekle.
7futexWaitWake(&sentryMsg.state, atomic.LoadUint32(&sentryMsg.state))

SECCOMP_RET_TRAP filtreleri konuk iş parçacıklarında keyfi derinlikte yığılabildiği ve syscallThread, ikinci bir kullanıcı bildirim dinleyicisi talep etmeden _RUN_SYSCALL_LOOP moduna geri döndüğü için gvx, runsc içinde sorunsuz çalışır ve özyinelemeli iç içe kullanımı (gvx içinde gvx) destekler.

2. Tek Dosyalık ext4lite Birimlerinde Yerel overlayfs

Dış gVisor konteynerinin gofer (9p/lisafs) kök dosya sistemi üzerine overlayfs bağlamaktan kaçınmak için -v gvx-docker:/var/lib/docker parametresi, /var/lib/docker dizinini gvx'in dahili ext4lite dosya sistemi ile destekler. Bu dosya sistemi tek bir normal dosyada (gvx-docker.ext4) saklanır.

Dış runsc açısından gvx-docker.ext4, pread64(2) ve pwrite64(2) ile erişilen tek bir normal dosyadır. gvx içinde ise ext4lite, trusted.overlay.* ve security.capability genişletilmiş öznitelikleri, karakter aygıtı silinme (whiteout) düğümleri (0:0), d_type dizin girdileri ve RENAME_EXCHANGE desteğiyle JBD2 günlüklemeli bir ext4 dosya sistemi sağlar. Bu sayede iç dockerd, kendi yerel overlayfs depolama sürücüsünü kullanabilir.

3. docker0 ve iptables-legacy ile Kendi İçinde Yeterli netstack

gvx içinde dockerd, dış runsc konteynerinin ağ arayüzleri veya netfilter tabloları ile etkileşime girmez. Bunun yerine:

  • dockerd, docker0 köprüsünü ve veth çiftlerini gvx'in kendi kullanıcı alanı netstack bileşeni içinde oluşturur.
  • Tüm iptables-legacy kuralları (-t nat -N DOCKER, MASQUERADE, conntrack, addrtype), gvx'in dahili netfilter uygulamasına karşı çalışır.
  • İç içe runc konteynerleri, /proc, /sys ve /sys/fs/cgroup hiyerarşilerini gvx'in bellek içi VFS2 bağlama tablosunda bağlar. Dış runsc mount(2) sistem çağrısını hiçbir zaman çağırmazlar.

gVisor (veya gvx İçinde gvx) İçinde İç İçe docker:dind'i Doğrulama

Aşağıdaki komutlarla her iki senaryoyu (runsc içinde rungv veya rungv içinde rungv) doğrulayabilirsiniz:

 1# Dış bir gVisor (runsc) konteyneri veya dış bir rungv konteyneri içinde:
 2mkdir -p ~/.gvx
 3rungv run \
 4  -p unix:~/.gvx/docker.sock:2375 \
 5  -v gvx-docker:/var/lib/docker \
 6  -e DOCKER_TLS_CERTDIR= \
 7  docker:dind --tls=false &
 8DIND_PID=$!
 9
10export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
11until docker info >/dev/null 2>&1; do sleep 0.5; done
12
13# İç Docker daemon'ının vfs yerine yerel overlayfs kullandığını doğrulayın
14docker info --format 'Storage Driver: {{.Driver}}'
15# Çıktı: Storage Driver: overlayfs
16
17# Çok katmanlı bir build çalıştırın ve iç içe bir konteyner başlatın
18mkdir -p /tmp/nested-test && cd /tmp/nested-test
19cat <<'EOF' > Dockerfile
20FROM alpine:3.20
21RUN adduser -D -u 1001 appuser && mkdir -p /app && chown appuser:appuser /app
22USER appuser
23WORKDIR /app
24RUN echo "nested-build-ok" > status.txt
25CMD ["cat", "/app/status.txt"]
26EOF
27
28docker build -t nested-test:latest .
29docker run --rm nested-test:latest
30# Çıktı: nested-build-ok
31
32kill -INT $DIND_PID

Bu Serideki Diğer Yazılar

  1. gvx: Tek Bir Ayrıcalıksız Süreçte Linux Konteynerlerini, Docker'ı ve Kubernetes'i Çalıştırma: gvx'in Windows (whpx, systrap), macOS (hvf) ve Linux (systrap) üzerindeki genel görünümü.
  2. Root veya Kullanıcı Ad Alanları Olmadan Docker, docker build ve docker compose Çalıştırma: root, sudo, unshare(CLONE_NEWUSER), mount(2) ve /dev/fuse engellendiğinde gvx'in docker:dind'i nasıl çalıştırdığı.
  3. gvx ile gVisor ve İç İçe Sandbox'lar İçinde Docker-in-Docker Çalıştırma (bu yazı)
  4. gvx ile AI Agent RL Rollout'ları için Hızlı ve İzole Docker Ortamları: gvx ve tek dosyalı ext4lite copy-on-write anlık görüntülerini kullanarak yüzlerce paralel SWE-bench ve RL rollout ortamını 100 milisaniyelik sıfırlamalarla nasıl çalıştıracağınız.