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

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
gvxeste sigur prin design (nu este o soluție de ocolire a escaladării privilegiilor): Spre deosebire de montarea socket-ului host/var/run/docker.socksau de rulareadocker run --privileged(care acordă un control echivalent cu cel de root asupra kernelului host),gvxnu 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+ allowlistseccompBPF strict) și ruleazădockerdîn interiorul kernelului user-space gVisor (Sentry) al Google, un kernel cu siguranță a memoriei. Toate capabilitățileUID 0ale oaspetelui, montărileoverlayfs(ext4lite) și regulileiptablesră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)sauclone(CLONE_NEWUSER). - Ubuntu 24.04+ activează
kernel.apparmor_restrict_unprivileged_userns = 1. - Nodurile Debian sau RHEL setează
kernel.unprivileged_userns_clone = 0sauuser.max_user_namespaces = 0. newuidmapnu poate scrie în/proc/self/uid_mapdeoarecePR_SET_NO_NEW_PRIVSeste 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.

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)șimount(2)operează asupra ierarhiei de task-uri în memorie aSentryși asupra tabelei de montareVFS2(procfs,sysfs,tmpfs,devpts,cgroupfsv2), în timp ce procesul hostrungvrulează 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:
dockerddin interiorul oaspetelui creează bridge-uldocker0(172.17.0.1/16), atașează perechi virtualevethpentru containerele imbricate și programează lanțurileNAT(DOCKER,POSTROUTING) șiFILTERcu potriviriaddrtypeșiconntrackînnetstack-ulgvxdin spațiul utilizatorului.- Transmiterea
-p unix:~/.gvx/docker.sock:2375leagă un socket Unix de domeniu de pe host (modul0600) și redirecționează conexiunile direct către portul2375aldockerddin sandbox.
Comparație cu instrumentele de containerizare existente
| Capabilitate / Constrângere | Docker standard (dockerd) | Rootless Docker / Podman | Kaniko / Buildah (fără privilegii) | gvx (rungv + docker:dind) |
|---|---|---|---|---|
Necesită root sau sudo pe host | Da | Nu | Nu | Nu |
Funcționează cu PR_SET_NO_NEW_PRIVS=1 | Nu | Nu (newuidmap eșuează) | Parțial | Da |
Funcționează când unshare(CLONE_NEWUSER) este blocat (EPERM) | N/A | Nu | Buildah: Nu / Kaniko: Da | Da |
Funcționează fără /dev/fuse | Da (folosește overlay2 al kernelului) | Doar prin copierea lentă vfs | Da | Da (overlayfs pe ext4lite) |
Suportă docker build (BuildKit) | Da | Da (dacă userns este permis) | Doar construire de imagini (fără runtime) | Da |
Suportă docker run și docker compose up | Da | Da (dacă userns este permis) | Nu | Da |
| Rulează pe Windows și macOS fără VM Linux | Nu (necesită WSL2 / VM) | Nu (necesită VM Linux) | Nu | Da (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:
ptrace(PTRACE_ATTACH)asupra propriului său thread copil, o singură dată, la inițializare:systrapporneș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ăptracela nivel de sistem) sau blocheazăptraceîntr-un filtru seccomp exterior, inițializareasystrapnu poate fi finalizată. (Cuyama.ptrace_scope = 1, valoarea implicită pe Ubuntu și Debian,gvxreușește, deoarece trasarea de la părinte la copil este permisă.)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,gvxfuncționează atât atunci cândSECCOMP_FILTER_FLAG_NEW_LISTENEReste disponibil, cât și atunci când returneazăEBUSYîntr-un sandbox imbricat.mmap(..., PROT_EXEC, ...): Folosit pentru a mapa segmentele ELF ale oaspetelui și trampolina de memorie partajată asystrap.
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
- 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), aVFS2(erofs,ext4lite,hostfsv2) și anetstack. - Running Docker, docker build, and docker compose Without Root or User Namespaces (această postare)
- Running Docker-in-Docker Inside gVisor and Nested Sandboxes with gvx: Cum rezolvă
gvxmodprobe,iptables-nft,overlay2pegoferșiSECCOMP_FILTER_FLAG_NEW_LISTENERimbricat (EBUSY) în interiorulgVisor(runsc). - Fast Isolated Docker Environments for AI Agent RL Rollouts with gvx: Scalarea mediilor paralele
SWE-benchși RL rollout cu resetări de volume.ext4Copy-on-Write în 100 de milisecunde.