---
id: 83fb2c108243a7df80db3a6e78290daf
author: Lemon Mint
title: Rularea Docker-in-Docker în interiorul gVisor și al sandbox-urilor imbricate cu gvx
description: De ce instalarea docker.io sau rularea runsc în interiorul unui container gVisor eșuează din cauza modprobe, iptables-nft, overlay2, cgroups și a erorii EBUSY de la seccomp, și cum gvx rulează în siguranță docker:dind imbricat într-un kernel izolat din spațiul utilizator, fără --privileged.
language: ro
date: 2026-11-06T12:02:55.931762327Z
path: /blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03
go_package: z.gfire.dev/gvx
go_repourl: https://github.com/gfire-sigs/gvx.git
category: lowlevel
---

## Soluție rapidă: rularea securizată a `docker:dind` într-un sandbox `gVisor` (`runsc`) neprivilegiat

Platformele cloud de agenți AI, implementările Kubernetes `agent-sandbox` (`RuntimeClass: gvisor`) și harnessurile de evaluare pentru învățarea prin întărire izolează sarcinile de lucru nesigure în interiorul containerelor **gVisor (`runsc`)**, tocmai pentru a proteja nucleul gazdei. Când o sarcină de lucru dintr-un container `runsc` neprivilegiat implicit încearcă să instaleze `docker.io` sau `docker-ce` prin `apt-get` și să pornească `dockerd`, operațiunea eșuează, chiar dacă procesul are `root` (`UID 0`) în interiorul containerului.

Cu **[gvx](https://github.com/gfire-sigs/gvx)** (`rungv`), puteți rula **în siguranță** `docker:dind`, `docker build` și `docker compose` direct într-un container `gVisor` (`runsc`) neprivilegiat (sau puteți imbrica `gvx` în `gvx`), **fără a slăbi `RuntimeClass` extern, fără a expune socket-uri ale gazdei și fără a acorda `--privileged`**.

> **Păstrarea și consolidarea izolării sandboxului extern**: Rularea `rungv` în interiorul `runsc` nu ocolește nicio restricție impusă de sandboxul extern `gVisor`. În schimb, `gvx` creează un al doilea nucleu `Sentry` gVisor imbricat, în spațiul utilizatorului, cu propriul `hostlockdown` (`PR_SET_NO_NEW_PRIVS` + `seccomp`), propriul `netstack` în memorie și un volum `ext4lite` dintr-un singur fișier. Containerele `dockerd` și `runc` interioare sunt izolate în sandbox dublu: apelurile lor de sistem, montările și pachetele brute nu părăsesc niciodată nucleul `gvx` interior, care rulează în spațiul utilizatorului.

```sh
# În interiorul unui container gVisor (runsc) existent sau al unui Pod Kubernetes:
# Instalați rungv dintr-o arhivă de lansare GitHub atestată SLSA sau prin go install:
CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest

mkdir -p ~/.gvx
rungv run \
  -p unix:~/.gvx/docker.sock:2375 \
  -v gvx-docker:/var/lib/docker \
  -e DOCKER_TLS_CERTDIR= \
  docker:dind --tls=false &

# Rulați compilări Docker standard și containere prin socket-ul gvx interior
export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
until docker info >/dev/null 2>&1; do sleep 0.5; done
docker info --format '{{.Driver}} {{.OperatingSystem}}'
# Ieșire: overlayfs Alpine Linux ... (containerized)

docker build -t nested-app:test .
docker run --rm nested-app:test
```

---

## Cinci motive pentru care instalarea directă a Docker eșuează în interiorul `gVisor` (`runsc`)

Când un utilizator sau un agent de programare AI rulează `apt-get install -y docker.io` (sau `docker-ce`) într-un container `runsc` standard și încearcă să pornească `dockerd`, întâlnește cinci bariere tehnice succesive.

### 1. Încărcarea modulelor nucleului (`modprobe`) față de nucleul virtual `4.4.0`

În timpul instalării pachetului și al pornirii `dockerd`, scripturile de inițializare rulează `modprobe` pentru a încărca `overlay`, `br_netfilter`, `ip_tables`, `nf_nat` și `xt_conntrack`. Sentry-ul gVisor extern blochează `init_module(2)` și `finit_module(2)` și raportează o versiune virtuală a nucleului (`4.4.0`) pentru care nu există directorul `/lib/modules/4.4.0`:

```text
modprobe: FATAL: Module overlay not found in directory /lib/modules/4.4.0
modprobe: FATAL: Module br_netfilter not found in directory /lib/modules/4.4.0
modprobe: FATAL: Module ip_tables not found in directory /lib/modules/4.4.0
modprobe: FATAL: Module nf_nat not found in directory /lib/modules/4.4.0
```

### 2. Eșecurile `iptables-nft` (netlink `nf_tables`) și ale punții `docker0`

Pe Ubuntu 22.04, Ubuntu 24.04 și Debian 11/12, `apt-get install docker.io` selectează `iptables-nft` (bazat pe subsistemul netlink `nf_tables`) ca alternativă implicită pentru `/usr/sbin/iptables`. Într-un container gVisor standard, fără privilegii de administrare a rețelei sau fără suport netlink `nf_tables`, `dockerd` se blochează în timpul inițializării lanțului NAT `DOCKER` și a punții `docker0`:

```text
failed to start daemon: Error initializing network controller: error obtaining controller instance: failed to create NAT chain DOCKER: iptables failed: iptables -t nat -N DOCKER
iptables v1.8.10 (nf_tables): Could not fetch rule set generation id: Invalid argument
iptables v1.8.10 (legacy): can't initialize iptables table 'nat': Table does not exist (do you need to insmod?)
iptables: No chain/target/match by that name.
Unable 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.)
Error initializing network controller: error obtaining controller instance: failed to create bridge "docker0": operation not permitted
```

### 3. Eșecul `overlay2` pe rootfs-ul `gofer` / `9p` și epuizarea discului în modul `vfs`

Cu excepția cazului în care administratorul extern `runsc` a configurat explicit un punct de montare `tmpfs` sau filestore dedicat pentru `/var/lib/docker`, rootfs-ul containerului este susținut de `gofer` (`9p`/`lisafs`) gVisor sau de o montare overlay imbricată. Montarea `overlay2` peste `/var/lib/docker` eșuează:

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

Când utilizatorii sau agenții încearcă să ocolească această eroare pornind `dockerd --storage-driver=vfs`, Docker dezactivează complet partajarea straturilor Copy-on-Write. Fiecare instrucțiune `RUN` dintr-un `Dockerfile` efectuează o copiere recursivă completă (`dirCopy`) a întregului sistem de fișiere rădăcină, transformând o imagine de 500 MB, în zece pași de compilare, în 5 GB de scrieri și determinând terminarea sandboxului cu `no space left on device` sau cu un timeout de I/O.

### 4. Respingerea montărilor cgroup și `/proc` ale `runc` imbricat

Chiar dacă `dockerd` este forțat să pornească cu `dockerd --iptables=false --bridge=none --storage-driver=vfs`, rularea `docker run` sau `docker build` invocă `runc` pentru a crea containerul interior. Într-un container gVisor neprivilegiat implicit, `runc` eșuează la montarea cgroup-urilor, la remontarea `/proc` sau la încărcarea filtrelor seccomp interioare:

```text
docker: 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:
error 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
cgroups: cgroup mountpoint does not exist: unknown
runc create failed: systemd not running on this host: cannot use systemd cgroup manager
error during container init: error mounting "proc" to rootfs at "/proc": mount proc:/proc (via /proc/self/fd/6), flags: 0xe: operation not permitted
error during container init: error loading seccomp filter into kernel: operation not permitted
```

### 5. `runsc` imbricat eșuează cu `SECCOMP_FILTER_FLAG_NEW_LISTENER` (`EBUSY`)

Dacă un utilizator încearcă să ruleze o altă instanță `runsc` în interiorul `runsc`, folosind platforma `systrap`, inițializarea se oprește imediat:

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

Atât nucleul Linux, cât și Sentry-ul gVisor permit **cel mult un** listener `SECCOMP_FILTER_FLAG_NEW_LISTENER` (`SECCOMP_RET_USER_NOTIF`) pe ierarhia de taskuri. Deoarece sandboxul extern deține deja slotul pentru listener, apelul interior `seccomp(2)` returnează `EBUSY` (`device or resource busy`).

---

## Cum rezolvă `gvx` execuția imbricată în interiorul `gVisor`

`gvx` abordează toate cele cinci bariere în propriul nucleu din spațiul utilizatorului și în propriul strat de platformă, permițând lui `rungv` să ruleze în interiorul unui container `gVisor` (`runsc`) extern sau chiar recursiv în interiorul unui alt container `gvx` (`gvx` în `gvx`).

![Execuția gvx imbricată în interiorul gVisor extern](/assets/images/gvx/gvx-nested-seccomp-futex.png)

### 1. Revenire automată de la `systrap` cu `EBUSY` la bucla futex `_RUN_SYSCALL_LOOP`

În Linux `systrap`, fiecare subproces de tip stub rulează două categorii de thread-uri:

- **Thread-uri de execuție ale oaspetelui (`sysmsg`)**: captează apelurile de sistem ale oaspetelui prin `SECCOMP_RET_TRAP` (`SIGSYS`) și prin trampoline `jmp rel32` modificate binar (`syshandler`), schimbând starea registrelor cu Sentry-ul prin memorie partajată și `SYS_FUTEX`.
- **Un `syscallThread` stub (`syscall_thread.go`)**: execută în subprocesul stub apelurile de sistem ale spațiului de adrese (`mmap`, `munmap`, `clone`) în numele Sentry-ului.

În gVisor-ul upstream, `syscallThread` acceptă două moduri de trezire în `stub_amd64.s` și `stub_arm64.s`:

1. **Bucla de notificare utilizator seccomp (`_RUN_SECCOMP_LOOP = 6`, când `t.seccompNotify != nil`)**: în timpul `syscallThread.init()`, `t.installSeccompNotify()` instalează un listener `SECCOMP_RET_USER_NOTIF` (`seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)`), astfel încât Sentry-ul să poată trimite cereri prin `ioctl(SECCOMP_IOCTL_NOTIF_SEND)` și `ioctl(SECCOMP_IOCTL_NOTIF_RECV)`.
2. **Bucla de apeluri de sistem futex (`_RUN_SYSCALL_LOOP = 5`, când `t.seccompNotify == nil`)**: `syscallThread` rulează `syscall_loop` (`stub_amd64.s` / `stub_arm64.s`), așteptând pe `SYS_FUTEX` (`FUTEX_WAIT`) peste regiunea partajată de 2 pagini formată din `syscallSentryMessage` (`PROT_READ` în stub) și `syscallStubMessage` (`PROT_READ | PROT_WRITE` în stub).

Gvisor-ul upstream apelează necondiționat `t.installSeccompNotify()` ori de câte ori `seccompNotifyIsSupported` este adevărat și abandonează crearea stubului dacă `seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, ...)` returnează `EBUSY`.

În `gvx` (`pkg/sentry/platform/systrap/seccomp_notify_xplat.go` și `syscall_thread.go`), `syscallThread.init()` apelează `t.installSeccompNotifyIfFree()`:

```go
// pkg/sentry/platform/systrap/seccomp_notify_xplat.go
var seccompNotifyBusy atomic.Bool

func (t *syscallThread) installSeccompNotifyIfFree() (*os.File, error) {
	if seccompNotifyBusy.Load() {
		return nil, nil
	}
	f, err := t.installSeccompNotify()
	if errors.Is(err, unix.EBUSY) {
		if seccompNotifyBusy.CompareAndSwap(false, true) {
			log.Warningf("Another seccomp listener exists in the filter chain of the host process. " +
				"The stub threads use the slower futex loop instead of seccomp-unotify.")
		}
		return nil, nil
	}
	return f, err
}
```

Când `installSeccompNotify()` returnează `unix.EBUSY` deoarece un sandbox extern `gVisor` (`runsc`) sau `gvx` deține deja slotul unic de listener din lanțul de filtre al procesului:

- `installSeccompNotifyIfFree()` setează `seccompNotifyBusy` (`atomic.Bool`), înregistrează un singur avertisment pentru proces și lasă `t.seccompNotify == nil` pentru acest stub și pentru toate stub-urile ulterioare.
- În `syscallThread.detach()` (`syscall_thread_amd64.go` și `syscall_thread_arm64.go`), `t.seccompNotify == nil` setează `regs.Rbx = _RUN_SYSCALL_LOOP` (`5`) pe `amd64` (sau `regs.Regs[9] = _RUN_SYSCALL_LOOP` pe `arm64`) în loc de `_RUN_SECCOMP_LOOP` (`6`).
- În `syscallThread.syscall()` (`syscall_thread.go`), Sentry-ul interior trimite apelurile de sistem ale stub-ului exclusiv prin memorie partajată și `SYS_FUTEX`:

```go
// pkg/sentry/platform/systrap/syscall_thread.go
// Notifică thread-ul de apeluri de sistem despre o nouă cerere de apel de sistem.
atomic.AddUint32(&sentryMsg.state, 1)
futexWakeUint32(&sentryMsg.state)

// Așteaptă răspunsul.
futexWaitWake(&sentryMsg.state, atomic.LoadUint32(&sentryMsg.state))
```

Deoarece filtrele `SECCOMP_RET_TRAP` ale thread-urilor oaspete se pot suprapune arbitrar de adânc, iar `syscallThread` revine la `_RUN_SYSCALL_LOOP` fără a solicita un al doilea listener de notificare utilizator, `gvx` rulează corect în interiorul `runsc` și suportă imbricarea recursivă (`gvx` în `gvx`).

### 2. `overlayfs` nativ pe volume `ext4lite` dintr-un singur fișier

Pentru a evita montarea `overlayfs` peste sistemul de fișiere rădăcină `gofer` (`9p`/`lisafs`) al containerului gVisor extern, `-v gvx-docker:/var/lib/docker` susține `/var/lib/docker` cu sistemul de fișiere intern `ext4lite` al `gvx`, stocat într-un singur fișier obișnuit (`gvx-docker.ext4`).

Pentru `runsc` extern, `gvx-docker.ext4` este doar un fișier obișnuit, accesat prin `pread64(2)` și `pwrite64(2)`. În interiorul `gvx`, `ext4lite` oferă un sistem de fișiere `ext4` cu jurnal JBD2, atribute extinse `trusted.overlay.*` și `security.capability`, noduri de tip whiteout pentru dispozitive caracter (`0:0`), intrări de director `d_type` și `RENAME_EXCHANGE`, permițând `dockerd` interior să folosească driverul nativ de stocare `overlayfs`.

### 3. `netstack` autonom cu `docker0` și `iptables-legacy`

În interiorul `gvx`, `dockerd` nu interacționează cu interfețele de rețea sau cu tabelele netfilter ale containerului `runsc` extern. În schimb:

- `dockerd` își creează puntea `docker0` și perechile `veth` în interiorul `netstack`-ului propriu, din spațiul utilizatorului, al `gvx`.
- Toate regulile `iptables-legacy` (`-t nat -N DOCKER`, `MASQUERADE`, `conntrack`, `addrtype`) sunt executate împotriva implementării netfilter interne a `gvx`.
- Containerele `runc` imbricate își montează ierarhiile `/proc`, `/sys` și `/sys/fs/cgroup` în tabela de montări `VFS2` din memorie a `gvx`, fără a apela niciodată apelul de sistem extern `mount(2)` al `runsc`.

---

## Verificarea `docker:dind` imbricat în interiorul `gVisor` (sau `gvx` în `gvx`)

Puteți verifica ambele scenarii (`rungv` în interiorul `runsc` sau `rungv` în interiorul `rungv`) cu următoarele comenzi:

```sh
# În interiorul unui container gVisor (runsc) extern sau al unui container rungv extern:
mkdir -p ~/.gvx
rungv run \
  -p unix:~/.gvx/docker.sock:2375 \
  -v gvx-docker:/var/lib/docker \
  -e DOCKER_TLS_CERTDIR= \
  docker:dind --tls=false &
DIND_PID=$!

export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
until docker info >/dev/null 2>&1; do sleep 0.5; done

# Verificați că daemonul Docker interior folosește overlayfs nativ (nu vfs)
docker info --format 'Storage Driver: {{.Driver}}'
# Ieșire: Storage Driver: overlayfs

# Rulați o compilare cu mai multe straturi și executați un container imbricat
mkdir -p /tmp/nested-test && cd /tmp/nested-test
cat <<'EOF' > Dockerfile
FROM alpine:3.20
RUN adduser -D -u 1001 appuser && mkdir -p /app && chown appuser:appuser /app
USER appuser
WORKDIR /app
RUN echo "nested-build-ok" > status.txt
CMD ["cat", "/app/status.txt"]
EOF

docker build -t nested-test:latest .
docker run --rm nested-test:latest
# Ieșire: nested-build-ok

kill -INT $DIND_PID
```

---

## Lectură suplimentară din această serie

1. **[gvx: Rularea containerelor Linux, Docker și Kubernetes într-un singur proces neprivilegiat](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: Prezentare generală a `gvx` pe Windows (`whpx`, `systrap`), macOS (`hvf`) și Linux (`systrap`).
2. **[Rularea Docker, docker build și docker compose fără root sau user namespaces](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)**: Modul în care `gvx` rulează `docker:dind` atunci când `root`, `sudo`, `unshare(CLONE_NEWUSER)`, `mount(2)` și `/dev/fuse` sunt blocate.
3. **[Rularea Docker-in-Docker în interiorul gVisor și al sandboxurilor imbricate cu gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)** (această postare)
4. **[Medii Docker izolate și rapide pentru rollouturile RL ale agenților AI cu gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)**: Modul de utilizare a `gvx` și a snapshot-urilor copy-on-write dintr-un singur fișier `ext4lite` pentru a rula sute de medii paralele `SWE-bench` și RL rollout, cu resetări de 100 de milisecunde.
