GoSuda

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

By Lemon Mint views ...

Hızlı Çözüm: Yetkisiz Bir gVisor (runsc) Sandbox İçinde docker:dind Dosyasını Güvenli Biçimde Çalıştırma

Bulut tabanlı YZ ajanı hesaplama ortamları (Google Agent Compute Engine sandbox'ları gibi), Kubernetes agent-sandbox dağıtımları (RuntimeClass: gvisor) ve pekiştirmeli öğrenme değerlendirme altyapıları, güvenilmeyen iş yüklerini özellikle ana makine çekirdeğini korumak amacıyla gVisor (runsc) konteynerleri içinde izole eder. Varsayılan yetkisiz bir runsc konteyneri içindeki bir iş yükü apt-get ile docker.io veya docker-ce kurmaya, service docker start veya dockerd komutunu başlatmaya ve docker build çalıştırmaya çalıştığında, konteyner içinde root (UID 0) yetkisine sahip olsa bile başarısız olur.

gvx (rungv) ile docker:dind, docker build ve docker compose komutlarını yetkisiz bir gVisor (runsc) konteyneri içinde doğrudan güvenli biçimde çalıştırabilirsiniz (veya gvx'i gvx içine yerleştirebilirsiniz). Bunu yaparken dış RuntimeClass yapılandırmasını zayıflatmaz, ana makine soketlerini açığa çıkarmaz ve --privileged yetkisi vermezsiniz.

Düşük Çaba Gerektiren, Token Verimli Çözüm: YZ Ajanları İçin: Gerçek bir gVisor destekli bulut ajanı ortamında docker.io kurmaya ve /etc/init.d/docker ulimit hatalarını, iptables-nft hatalarını, iptables-legacy ham soket reddlerini, overlay2 bağlama hatalarını ve docker build runc / pivot_root çökmelerini manuel olarak aşmaya çalışmak, genellikle tamamen başarısız olmadan önce 20'den fazla araç çağrısı turunu ve on binlerce token'ı tüketir. gvx (rungv) ise düşük çaba gerektiren, token verimli ve doğrudan kullanılabilir bir çözümdür: ajan tek bir turda 3 komut çalıştırarak çalışan bir kullanıcı alanı Docker daemon'ı başlatır ve proje dosyalarını değiştirmeden hemen docker build ve docker compose komutlarını yürütür.

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ı atlamaz. Bunun yerine gvx, kendi hostlockdown (PR_SET_NO_NEW_PRIVS + seccomp) mekanizmasına, kendi bellek içi netstack yapısına ve tek dosyalık bir ext4lite birimine sahip ikinci, iç içe bir 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 ve ham paketler hiçbir zaman iç gvx kullanıcı alanı çekirdeğinden çıkmaz.

 1# Mevcut bir gVisor (runsc) konteyneri veya Kubernetes Pod'u içinde:
 2# rungv'i SLSA ile doğrulanmış bir GitHub sürüm arşivinden kurun veya go install ile yükleyin:
 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 konteynerlerini iç gvx soketi üzerinden ç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 ... (konteynerleştirilmiş)
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 YZ kodlama ajanı standart bir runsc konteyneri içinde apt-get install -y docker.io (veya docker-ce) komutunu çalıştırıp dockerd başlatmaya çalıştığında, art arda gelen beş teknik engelle karşılaşır.

1. Init Betiği (systemctl / service docker start ulimit) ve Çekirdek Modülü (modprobe 4.4.0) Hataları

apt-get install -y docker.io (veya docker-ce) komutundan sonra daemon'ı systemctl start docker veya service docker start ile başlatma girişimi hemen başarısız olur, çünkü PID 1 systemd değildir ve /etc/init.d/docker betiği ulimit -n değerini sandbox'ın sert RLIMIT_NOFILE üst sınırının üzerine çıkarmaya çalışır. dockerd doğrudan çağrıldığında ise başlatma betikleri, gVisor'ın sanal çekirdek sürümü (4.4.0) için overlay, bridge, br_netfilter, ip_tables ve nf_nat modüllerini yüklemek üzere modprobe çağırır; oysa /lib/modules/4.4.0 dizini mevcut değildir:

1System has not been booted with systemd as init system (PID 1). Can't operate.
2Failed to connect to bus: Host is down
3/etc/init.d/docker: 62: ulimit: error setting limit (Operation not permitted)
4level=warning msg="Running modprobe bridge br_netfilter failed with message: modprobe: WARNING: Module bridge not found in directory /lib/modules/4.4.0\nmodprobe: WARNING: Module br_netfilter not found in directory /lib/modules/4.4.0\n, error: exit status 1"
5modprobe: FATAL: Module overlay not found in directory /lib/modules/4.4.0
6modprobe: FATAL: Module br_netfilter not found in directory /lib/modules/4.4.0
7modprobe: FATAL: Module ip_tables not found in directory /lib/modules/4.4.0
8modprobe: FATAL: Module nf_nat not found in directory /lib/modules/4.4.0

2. iptables-nft (nf_tables Netlink), iptables-legacy Ham Soket Reddleri 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 sistemini kullanır). Ayrıca kullanıcı veya ajan iptables-legacy aracına geçse bile (update-alternatives --set iptables /usr/sbin/iptables-legacy), varsayılan yetkisiz gVisor sandbox'ları (--net-raw=false) AF_INET, SOCK_RAW soketlerini EPERM ile reddeder. Bu durum, UID 0 olarak çalışılsa bile iptables-legacy aracının Permission denied (you must be root) hatasıyla başarısız olmasına yol açar:

1failed to start daemon: Error initializing network controller: error obtaining controller instance: failed to register "bridge" driver: failed to create NAT chain DOCKER: iptables failed: iptables --wait -t nat -N DOCKER: iptables v1.8.10 (nf_tables): Could not fetch rule set generation id: Invalid argument
2iptables v1.8.10 (legacy): can't initialize iptables table `nat': Permission denied (you must be root)
3Perhaps iptables or your kernel needs to be upgraded.
4iptables v1.8.10 (legacy): can't initialize iptables table 'nat': Table does not exist (do you need to insmod?)
5iptables: No chain/target/match by that name.
6Unable 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.)
7Error initializing network controller: error obtaining controller instance: failed to create bridge "docker0": operation not permitted

3. gofer / 9p Rootfs Üzerinde overlay2 Hatası ve vfs Disk Tükenmesi

Dış runsc yöneticisi /var/lib/docker konumunda özel bir tmpfs veya filestore bağlamasını açıkça önceden yapılandırmadıysa, konteyner rootfs'i gVisor'ın gofer (9p/lisafs) yapısı veya iç içe bir overlay bağlaması üzerinde bulunur. /var/lib/docker üzerine overlay2 bağlanması 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 ajanlar bu hatayı dockerd --storage-driver=vfs ile başlatarak 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ı, kök dosya sisteminin tamamının tam özyinelemeli kopyasını (dirCopy) oluşturur. Bu da 10 build adımına yayılan 500 MB'lık bir imajı 5 GB yazma işlemine dönüştürür ve sandbox'ın no space left on device hatasıyla ya da G/Ç zaman aşımıyla sonlanmasına neden olur.

4. docker build (BuildKit) ve İç İçe runc Hataları (pivot_root, /proc, Cgroup'lar ve DNS)

dockerd dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs ile zorla başlatılsa bile, BuildKit veya runc build konteynerini başlattığında docker build -t app . veya docker run komutu hemen başarısız olur:

 1ERROR: failed to solve: process "/bin/sh -c ..." did not complete successfully:
 2docker: 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:
 3error during container init: error jailing process inside rootfs: pivot_root .: invalid argument
 4error 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
 5cgroups: cgroup mountpoint does not exist: unknown
 6runc create failed: systemd not running on this host: cannot use systemd cgroup manager
 7error during container init: error mounting "proc" to rootfs at "/proc": mount proc:/proc (via /proc/self/fd/6), flags: 0xe: operation not permitted
 8error during container init: error loading seccomp filter into kernel: operation not permitted
 9Temporary failure resolving 'deb.debian.org'
10Temporary failure resolving 'archive.ubuntu.com'

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, bir 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 tuttuğu 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 ve rungv'in dış bir gVisor (runsc) konteyneri içinde, hatta başka bir gvx konteyneri içinde özyinelemeli olarak çalışmasını sağlar (gvx içinde gvx).

Dış 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 durumunu paylaşılan bellek ve SYS_FUTEX üzerinden Sentry ile değiş tokuş eder.
  • Bir stub syscallThread (syscall_thread.go): Sentry adına, adres alanı sistem çağrılarını (mmap, munmap, clone) stub alt süreci içinde ç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(), Sentry'nin ioctl(SECCOMP_IOCTL_NOTIF_SEND) ve ioctl(SECCOMP_IOCTL_NOTIF_RECV) aracılığıyla istekleri gönderebilmesi için bir SECCOMP_RET_USER_NOTIF dinleyicisi kurar (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)).
  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) fonksiyonunu çalıştırır ve paylaşılan 2 sayfalık syscallSentryMessage (stub içinde PROT_READ) ile syscallStubMessage (stub içinde PROT_READ | PROT_WRITE) bölgesi üzerinde SYS_FUTEX (FUTEX_WAIT) ile uyku moduna geçer.

Upstream gVisor, seccompNotifyIsSupported doğru olduğunda koşulsuz olarak t.installSeccompNotify() ç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 dosyalarında), syscallThread.init() t.installSeccompNotifyIfFree() fonksiyonunu çağırır:

 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 tuttuğu için unix.EBUSY döndürdüğünde:

  • installSeccompNotifyIfFree() seccompNotifyBusy (atomic.Bool) değerini ayarlar, süreç için tek bir uyarı günlüğe yazar ve o stub ile sonraki tüm stub'lar için t.seccompNotify == nil durumunu korur.
  • syscallThread.detach() içinde (syscall_thread_amd64.go ve syscall_thread_arm64.go), t.seccompNotify == nil olduğunda _RUN_SECCOMP_LOOP (6) yerine amd64 üzerinde regs.Rbx = _RUN_SYSCALL_LOOP (5), arm64 üzerinde ise regs.Regs[9] = _RUN_SYSCALL_LOOP atanır.
  • syscallThread.syscall() (syscall_thread.go) içinde iç Sentry, stub sistem çağrılarını yalnızca paylaşılan bellek ve SYS_FUTEX üzerinden gönderir:
1// pkg/sentry/platform/systrap/syscall_thread.go
2// Notify the syscall thread about a new syscall request.
3atomic.AddUint32(&sentryMsg.state, 1)
4futexWakeUint32(&sentryMsg.state)
5
6// Wait for reply.
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 geçmeyi (gvx içinde gvx) destekler.

2. Tek Dosyalı ext4lite Birimleri Üzerinde 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 sistemiyle destekler; bu dosya sistemi tek bir normal dosyada (gvx-docker.ext4) saklanır.

Dış runsc için gvx-docker.ext4 yalnızca 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ı silme (whiteout) düğümleri (0:0), d_type dizin girişleri 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 Bağımsız netstack

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

  • dockerd, docker0 köprüsünü ve veth çiftlerini gvx'in kendi kullanıcı alanı netstack yapısı içinde oluşturur.
  • Tüm iptables-legacy kuralları (-t nat -N DOCKER, MASQUERADE, conntrack, addrtype), gvx'in dahili netfilter uygulamasına karşı yürütülü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ğırmaz.

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

Her iki senaryoyu (runsc içinde rungv veya rungv içinde rungv) aşağıdaki komutlarla 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 yerel overlayfs kullandığını (vfs değil) 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 yürütü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 Yetkisiz Süreçte Linux Konteynerleri, Docker ve Kubernetes Çalıştırma: gvx'in Windows (whpx, systrap), macOS (hvf) ve Linux (systrap) üzerindeki genel bakışı.
  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'larda Docker-in-Docker Çalıştırma (bu yazı)
  4. gvx ile YZ Ajanı RL Rollout'ları İçin Hızlı İzole Docker Ortamları: gvx ve tek dosyalı ext4lite kopyala-yaz anlık görüntülerinin, 100 milisaniyelik sıfırlamalarla yüzlerce paralel SWE-bench ve RL rollout ortamını nasıl çalıştırdığı.