GoSuda

Rularea Docker, docker build și docker compose fără root sau user namespaces

By Lemon Mint views ...

Soluție rapidă: Rulați Docker fără privilegii în trei pași

Dacă lucrați într-un container Linux restricționat, un runner CI, un Kubernetes Pod sau un sandbox pentru agenți AI în care root, sudo, user namespaces (unshare(CLONE_NEWUSER)), mount(2) și /dev/fuse sunt blocate, gvx (rungv) este soluția sigură, bazată pe apărare în profunzime, pentru a rula un motor docker:dind complet în spațiul utilizatorului.

De ce gvx este sigur prin design (nu este o soluție de ocolire a escaladării privilegiilor): Spre deosebire de montarea socket-ului host /var/run/docker.sock sau de rularea docker run --privileged (care acordă un control echivalent cu cel de root asupra kernelului host), gvx nu ocolește și nu slăbește nicio politică de securitate a host-ului (PR_SET_NO_NEW_PRIVS, seccomp, AppArmor sau limitările UID-urilor non-root). În schimb, rungv își blochează propriul proces host (PR_SET_NO_NEW_PRIVS + allowlist seccomp BPF strict) și rulează dockerd în interiorul kernelului user-space gVisor (Sentry) al Google, un kernel cu siguranță a memoriei. Toate capabilitățile UID 0 ale oaspetelui, montările overlayfs (ext4lite) și regulile iptables rămân strict izolate în interiorul procesului sandbox.

 1# 1. Instalați rungv în spațiul utilizatorului (versiune prebuilt cu atestare SLSA sau go install)
 2# Opțiunea A: Verificați și extrageți binarul prebuilt cu atestare SLSA din GitHub Releases
 3gh attestation verify rungv-v0.2.0-linux-amd64.tar.gz --repo gfire-sigs/gvx \
 4  --signer-workflow gfire-sigs/gvx/.github/workflows/release.yml \
 5  --source-ref refs/tags/v0.2.0 --deny-self-hosted-runners
 6tar -xzf rungv-v0.2.0-linux-amd64.tar.gz && install -m 0755 rungv-v0.2.0-linux-amd64/rungv ~/.local/bin/rungv
 7
 8# Opțiunea B: Sau instalați direct din sursă cu Go
 9CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest
10
11# 2. Porniți docker:dind în gvx cu un volum ext4lite dintr-un singur fișier
12#    Redirecționează ~/.gvx/docker.sock (modul 0600) către portul 2375 al oaspetelui
13mkdir -p ~/.gvx
14~/.local/bin/rungv run \
15  -p unix:~/.gvx/docker.sock:2375 \
16  -p 127.0.0.1:9000-9019:9000-9019 \
17  -v gvx-docker:/var/lib/docker \
18  -e DOCKER_TLS_CERTDIR= \
19  docker:dind --tls=false &
20
21# 3. Indicați CLI-ul Docker standard și Docker Compose către socket-ul din spațiul utilizatorului
22export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
23until docker info >/dev/null 2>&1; do sleep 0.5; done
24docker info --format '{{.Driver}} {{.OperatingSystem}}'
25# Ieșire: overlayfs Alpine Linux ... (containerized)
26
27docker build -t app:test .
28docker run --rm app:test
29docker compose up -d

De ce instrumentele standard rootless eșuează în sandbox-uri restricționate

Atunci când un inginer sau un agent de codare autonom are nevoie să ruleze docker build, docker run sau docker compose într-un container întărit sau într-un runner multi-tenant, soluțiile standard eșuează în cinci bariere distincte ale kernelului.

1. Lipsa socket-ului daemonului Docker de pe host

Rularea docker ps sau docker build eșuează imediat atunci când /var/run/docker.sock de pe host nu este montat în mediu:

1Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

2. PR_SET_NO_NEW_PRIVS blochează sudo și helperii setuid

Containerele întărite (securityContext.allowPrivilegeEscalation: false în Kubernetes, --security-opt=no-new-privileges în Docker sau sandbox-urile exebox / bwrap) setează bitul Linux PR_SET_NO_NEW_PRIVS pe arborele de procese. Încercarea de escaladare prin sudo sau binare setuid (newuidmap, newgidmap, fusermount3) eșuează:

1sudo: The "no new privileges" flag is set, which prevents sudo from running as root.
2sudo: effective uid is not 0, is /usr/bin/sudo on a file system with the 'nosuid' option set or an NFS file system without root privileges?

3. Politicile seccomp și sysctl blochează unshare(CLONE_NEWUSER)

Instrumentele promovate ca „rootless” (Rootless Docker, Podman, Buildah, nerdctl, bubblewrap) se bazează totuși pe user namespaces Linux (CLONE_NEWUSER). Ele eșuează ori de câte ori:

  • Profilul seccomp implicit al containerului exterior blochează unshare(2) sau clone(CLONE_NEWUSER).
  • Ubuntu 24.04+ activează kernel.apparmor_restrict_unprivileged_userns = 1.
  • Nodurile Debian sau RHEL setează kernel.unprivileged_userns_clone = 0 sau user.max_user_namespaces = 0.
  • newuidmap nu poate scrie în /proc/self/uid_map deoarece PR_SET_NO_NEW_PRIVS este activ.

Erorile rezultate arată astfel:

1unshare: unshare failed: Operation not permitted
2cannot clone: Operation not permitted
3Error: cannot re-exec process to join the existing user namespace
4newuidmap: write to uid_map failed: Operation not permitted
5RootlessKit: parent: failed to start the child: fork/exec /proc/self/exe: operation not permitted

4. mount(2) este blocat fără CAP_SYS_ADMIN pe host

Chiar dacă un mediu permite CLONE_NEWUSER, montarea unui /proc nou într-un mount namespace imbricat eșuează dacă containerul exterior a mascat subarborii /proc (/proc/acpi, /proc/kcore, /proc/keys) sau a eliminat mount(2) din seccomp:

1mount("/proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL): Operation not permitted
2OCI runtime create failed: runc create failed: unable to start container process: error during container init: error mounting "proc" to rootfs at "/proc": mount proc:/proc (via /proc/self/fd/6), flags: 0xe: operation not permitted
3bwrap: Creating new namespace failed: Operation not permitted

5. Lipsa /dev/fuse împiedică funcționarea fuse-overlayfs

Motoarele de containere rootless care nu pot monta overlayfs de kernel trec la fuse-overlayfs. În containerele minimale sau în pod-urile Kubernetes fără transmiterea dispozitivului /dev/fuse, fuse-overlayfs eșuează imediat:

1fuse: device not found, try 'modprobe fuse' first
2fusermount3: failed to open /dev/fuse: No such file or directory
3error initializing graphdriver: driver not supported: fuse-overlayfs

Cum rulează gvx docker:dind complet în spațiul utilizatorului

gvx ocolește toate cele cinci bariere ale kernelului deoarece nu cere kernelului Linux de pe host să creeze namespaces, să monteze sisteme de fișiere sau să configureze interfețe de rețea. În schimb, rungv rulează un kernel Linux intern, în spațiul utilizatorului (gVisor Sentry), și o stivă de rețea în spațiul utilizatorului (netstack), în interiorul unui singur proces obișnuit, fără privilegii.

Running Unprivileged Docker-in-Docker with gvx

1. Namespaces în memorie și tabela de montare (VFS2)

Atunci când dockerd, containerd și runc se execută în rungv, systrap interceptează apelurile lor de sistem și le direcționează către kernelul Sentry din proces, nu către kernelul host:

  • unshare(2), clone(2), pivot_root(2), chroot(2) și mount(2) operează asupra ierarhiei de task-uri în memorie a Sentry și asupra tabelei de montare VFS2 (procfs, sysfs, tmpfs, devpts, cgroupfs v2), în timp ce procesul host rungv rulează sub UID-ul dvs. host fără privilegii.

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

Atunci când transmiteți -v gvx-docker:/var/lib/docker (sau --mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker), gvx montează sistemul de fișiere integrat ext4lite la /var/lib/docker.

Deoarece ext4lite implementează un sistem de fișiere ext4 cu jurnal JBD2 într-un singur fișier de pe host (gvx-docker.ext4), cu suport pentru atributele extinse trusted.overlay.*, dispozitivele whiteout de tip caracter (0:0), d_type și RENAME_EXCHANGE, dockerd utilizează driverul de stocare nativ overlayfs în interiorul oaspetelui. Operațiile docker build în mai multe etape partajează straturile Copy-on-Write fără /dev/fuse și fără explozia spațiului pe disc specifică driverului de rezervă vfs al Docker.

3. Bridge-ul docker0 în spațiul utilizatorului și iptables-legacy

Atunci când docker:dind pornește în gvx:

  • dockerd din interiorul oaspetelui creează bridge-ul docker0 (172.17.0.1/16), atașează perechi virtuale veth pentru containerele imbricate și programează lanțurile NAT (DOCKER, POSTROUTING) și FILTER cu potriviri addrtype și conntrack în netstack-ul gvx din spațiul utilizatorului.
  • Transmiterea -p unix:~/.gvx/docker.sock:2375 leagă un socket Unix de domeniu de pe host (modul 0600) și redirecționează conexiunile direct către portul 2375 al dockerd din sandbox.

Comparație cu instrumentele de containerizare existente

Capabilitate / ConstrângereDocker standard (dockerd)Rootless Docker / PodmanKaniko / Buildah (fără privilegii)gvx (rungv + docker:dind)
Necesită root sau sudo pe hostDaNuNuNu
Funcționează cu PR_SET_NO_NEW_PRIVS=1NuNu (newuidmap eșuează)ParțialDa
Funcționează când unshare(CLONE_NEWUSER) este blocat (EPERM)N/ANuBuildah: Nu / Kaniko: DaDa
Funcționează fără /dev/fuseDa (folosește overlay2 al kernelului)Doar prin copierea lentă vfsDaDa (overlayfs pe ext4lite)
Suportă docker build (BuildKit)DaDa (dacă userns este permis)Doar construire de imagini (fără runtime)Da
Suportă docker run și docker compose upDaDa (dacă userns este permis)NuDa
Rulează pe Windows și macOS fără VM LinuxNu (necesită WSL2 / VM)Nu (necesită VM Linux)NuDa (whpx, hvf, systrap)

Cerințe exacte ale kernelului host pe Linux

gvx nu necesită capabilități ridicate (CapEff: 0000000000000000) și nici user namespaces. Pe un host Linux, rungv se bazează doar pe trei primitive standard ale kernelului, fără privilegii:

  1. ptrace(PTRACE_ATTACH) asupra propriului său thread copil, o singură dată, la inițializare: systrap pornește un thread helper local la pornire, se atașează la acesta pentru a inițializa contextul de memorie partajată și apoi se detașează. Dacă host-ul setează kernel.yama.ptrace_scope = 3 (ceea ce dezactivează ptrace la nivel de sistem) sau blochează ptrace într-un filtru seccomp exterior, inițializarea systrap nu poate fi finalizată. (Cu yama.ptrace_scope = 1, valoarea implicită pe Ubuntu și Debian, gvx reușește, deoarece trasarea de la părinte la copil este permisă.)
  2. seccomp(SECCOMP_SET_MODE_FILTER): Folosit pentru a intercepta apelurile de sistem ale oaspetelui din thread-urile stub ale containerului. După cum este prezentat în Partea 3 a acestei serii, gvx funcționează atât atunci când SECCOMP_FILTER_FLAG_NEW_LISTENER este disponibil, cât și atunci când returnează EBUSY într-un sandbox imbricat.
  3. mmap(..., PROT_EXEC, ...): Folosit pentru a mapa segmentele ELF ale oaspetelui și trampolina de memorie partajată a systrap.

Exemplu cap-coadă: Construirea și testarea unei aplicații multi-container fără root

Următorul script rulează un docker build complet și un test de integrare cu două containere (redis + container de aplicație) într-o sesiune de utilizator fără privilegii:

 1mkdir -p ~/.gvx
 2rungv run \
 3  -p unix:~/.gvx/docker.sock:2375 \
 4  -v gvx-docker:/var/lib/docker \
 5  -e DOCKER_TLS_CERTDIR= \
 6  docker:dind --tls=false &
 7DIND_PID=$!
 8
 9export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
10until docker info >/dev/null 2>&1; do sleep 0.5; done
11
12mkdir -p /tmp/demo-app && cd /tmp/demo-app
13cat <<'EOF' > Dockerfile
14FROM alpine:3.20
15RUN apk add --no-cache redis
16CMD ["redis-cli", "-h", "cache", "ping"]
17EOF
18
19# Construiți imaginea folosind overlayfs pe ext4lite
20docker build -t demo-ping:latest .
21
22# Creați o rețea bridge, porniți Redis și verificați conectivitatea între containere
23docker network create demo-net
24docker run -d --name cache --network demo-net redis:7-alpine
25docker run --rm --network demo-net demo-ping:latest
26# Ieșire: PONG
27
28# Curățați containerele de lucru și opriți sandbox-ul
29docker rm -f cache
30docker network rm demo-net
31kill -INT $DIND_PID

Alte lecturi din această serie

  1. gvx: Running Linux Containers, Docker, and Kubernetes in One Unprivileged Process: Prezentare arhitecturală a gvx, a motoarelor sale de execuție multi-platformă (whpx, hvf, systrap), a VFS2 (erofs, ext4lite, hostfs v2) și a netstack.
  2. Running Docker, docker build, and docker compose Without Root or User Namespaces (această postare)
  3. Running Docker-in-Docker Inside gVisor and Nested Sandboxes with gvx: Cum rezolvă gvx modprobe, iptables-nft, overlay2 pe gofer și SECCOMP_FILTER_FLAG_NEW_LISTENER imbricat (EBUSY) în interiorul gVisor (runsc).
  4. Fast Isolated Docker Environments for AI Agent RL Rollouts with gvx: Scalarea mediilor paralele SWE-bench și RL rollout cu resetări de volume .ext4 Copy-on-Write în 100 de milisecunde.