GoSuda

Изпълнение на Docker, docker build и docker compose без root права или user namespaces

By Lemon Mint views ...

Бързо решение: Сигурен непривилегирован Docker в три стъпки

Ако работите в заключен Linux контейнер, CI изпълнител, Kubernetes Pod или сандбокс на AI агент за програмиране (например облачна изчислителна среда за агенти), където root, sudo, потребителските именни пространства (unshare(CLONE_NEWUSER)), mount(2) и /dev/fuse са блокирани, gvx (rungv) е сигурното, нискоусилното и ефективно по отношение на токените решение за стартиране на пълен двигател docker:dind в потребителското пространство.

Нискоусилно и ефективно по отношение на токените решение за AI агенти за програмиране: Когато автономен агент за програмиране получи Cannot connect to the Docker daemon, опитът да се инсталират и поправят docker.io, podman или rootlesskit в ограничен контейнер обикновено отнема между 15 и 30 извиквания на инструменти и десетки хиляди контекстни токена, докато се преминава през грешки в systemctl, ulimit, iptables, unshare и runc, които в крайна сметка водят до неуспех или до ръчно пренаписване на Dockerfile. С gvx (rungv) агентът изпълнява един детерминистичен тристъпков начален процес в едно извикване и незабавно изпълнява стандартните команди docker build, docker run и docker compose без промени.

Защо gvx е безопасен по замисъл (не е заобиколно решение за ескалация на привилегии): За разлика от монтирането на хостовия /var/run/docker.sock или изпълнението на docker run --privileged (които дават контрол, еквивалентен на root, над хостовото ядро), gvx не заобикаля и не отслабва никаква политика за сигурност на хоста (PR_SET_NO_NEW_PRIVS, seccomp, AppArmor или ограниченията на непривилегирования UID). Вместо това rungv заключва собствения си хостов процес (PR_SET_NO_NEW_PRIVS и строг BPF seccomp списък с разрешени системни извиквания) и изпълнява dockerd в потребителското ядро gVisor (Sentry) на Google, написано на безопасен по отношение на паметта език. Всички UID 0 възможности на гост системата, монтиранията на overlayfs (ext4lite) и правилата на iptables остават строго изолирани в рамките на сандбокс процеса.

 1# 1. Инсталирайте rungv в потребителското пространство (предварително компилирано издание с SLSA атестация или go install)
 2# Опция A: Проверете и разархивирайте предварително компилирания двоичен файл с SLSA атестация от 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# Опция B: Или инсталирайте директно от изходния код с Go
 9CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest
10
11# 2. Стартирайте docker:dind в gvx с единичен ext4lite том във файл
12#    Пренасочва ~/.gvx/docker.sock (режим 0600) към порт 2375 на гост системата
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. Насочете стандартния Docker CLI и Docker Compose към сокета в потребителското пространство
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# Изход: overlayfs Alpine Linux ... (containerized)
26
27docker build -t app:test .
28docker run --rm app:test
29docker compose up -d

Защо стандартните инструменти без root се провалят в ограничени сандбокс среди

Когато инженер или автономен агент за програмиране трябва да изпълни docker build, docker run или docker compose в закалени контейнер или многопотребителски изпълнител, стандартните заобиколни решения се провалят при пет различни бариери на ядрото.

1. Няма хостов сокет на Docker демона (systemctl и service docker start също се провалят)

Изпълнението на docker ps или docker build се проваля незабавно, когато хостовият /var/run/docker.sock не е монтиран в средата, а опитът за стартиране на docker.io през systemctl или service docker start се проваля, защото PID 1 не е systemd, а /etc/init.d/docker достига до твърдия лимит ulimit -n на контейнера:

1Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
2ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
3System has not been booted with systemd as init system (PID 1). Can't operate.
4Failed to connect to bus: Host is down
5/etc/init.d/docker: 62: ulimit: error setting limit (Operation not permitted)

2. PR_SET_NO_NEW_PRIVS блокира sudo и помощните програми с setuid

Закалените контейнери (securityContext.allowPrivilegeEscalation: false в Kubernetes, --security-opt=no-new-privileges в Docker или сандбокси exebox / bwrap) задават бита PR_SET_NO_NEW_PRIVS на Linux за дървото от процеси. Опитът за ескалация през sudo или двоични файлове с setuid (newuidmap, newgidmap, fusermount3) се проваля:

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. Политиките на seccomp и sysctl блокират unshare(CLONE_NEWUSER)

Инструментите, рекламирани като „rootless“ (Rootless Docker, Podman, Buildah, nerdctl, bubblewrap), все още разчитат на потребителски именни пространства на Linux (CLONE_NEWUSER). Те се провалят, когато:

  • Типичният seccomp профил на външния контейнер блокира unshare(2) или clone(CLONE_NEWUSER).
  • Ubuntu 24.04+ включва kernel.apparmor_restrict_unprivileged_userns = 1.
  • Възлите на Debian или RHEL задават kernel.unprivileged_userns_clone = 0 или user.max_user_namespaces = 0.
  • newuidmap не може да записва в /proc/self/uid_map, защото PR_SET_NO_NEW_PRIVS е активен.

Получените грешки изглеждат така:

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) и pivot_root(2) са блокирани по време на docker build без хостов CAP_SYS_ADMIN

Дори когато средата позволява CLONE_NEWUSER или dockerd се стартира принудително с --iptables=false --bridge=none --storage-driver=vfs, изпълнението на docker build се проваля при първата инструкция RUN, защото монтирането на нов /proc или извикването на pivot_root(2) във вложено пространство за монтиране е блокирано:

1ERROR: failed to solve: process "/bin/sh -c ..." did not complete successfully:
2mount("/proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL): Operation not permitted
3OCI 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
4OCI runtime create failed: runc create failed: unable to start container process: error during container init: error jailing process inside rootfs: pivot_root .: invalid argument
5bwrap: Creating new namespace failed: Operation not permitted

5. Липсата на /dev/fuse чупи fuse-overlayfs

Engine-ите за контейнери без root, които не могат да монтират ядрен overlayfs, преминават към fuse-overlayfs. В минимални контейнери или Kubernetes pod-ове без препредаване на устройството /dev/fuse, fuse-overlayfs се проваля незабавно:

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

Как gvx изпълнява docker:dind изцяло в потребителското пространство

gvx преодолява и петте бариери на ядрото, защото не изисква от ядрото на хост Linux да създава именни пространства, да монтира файлови системи или да конфигурира мрежови интерфейси. Вместо това rungv изпълнява вътрешно потребителско Linux ядро (Sentry на gVisor) и потребителски мрежов стек (netstack) в един обикновен непривилегирован процес.

Изпълнение на непривилегирован Docker-in-Docker с gvx

1. Именни пространства и таблица за монтиране в паметта (VFS2)

Когато dockerd, containerd и runc се изпълняват в rungv, systrap прехваща техните системни извиквания и ги насочва към вътрешното ядро Sentry в процеса, а не към ядрото на хоста:

  • unshare(2), clone(2), pivot_root(2), chroot(2) и mount(2) работят върху йерархията от задачи в паметта на Sentry и таблицата за монтиране VFS2 (procfs, sysfs, tmpfs, devpts, cgroupfs v2), докато хостовият процес rungv работи под вашия непривилегирован хостов UID.

2. Роден overlayfs върху еднофайлови томове ext4lite

Когато подадете -v gvx-docker:/var/lib/docker (или --mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker), gvx монтира вградената си файлова система ext4lite в /var/lib/docker.

Тъй като ext4lite реализира файлова система ext4 с журналиране JBD2 във вътрешността на един хостов файл (gvx-docker.ext4), с поддръжка на разширените атрибути trusted.overlay.*, символните устройства за заличаване (0:0), d_type и RENAME_EXCHANGE, dockerd използва своя роден драйвер за съхранение overlayfs в гост системата. Многоетапните операции docker build споделят слоеве Copy-on-Write без /dev/fuse и без експлозията на дисковото пространство, характерна за резервния драйвер vfs на Docker.

3. Мост docker0 в потребителското пространство и iptables-legacy

Когато docker:dind се стартира в gvx:

  • dockerd в гост системата създава моста docker0 (172.17.0.1/16), прикачва виртуални двойки veth за вложените контейнери и програмира веригите NAT (DOCKER, POSTROUTING) и FILTER с помощта на съвпадения addrtype и conntrack вътре в потребителския netstack на gvx.
  • Подаването на -p unix:~/.gvx/docker.sock:2375 свързва хостов Unix домейн сокет (режим 0600) и пренасочва връзките директно към порт 2375 на dockerd в сандбокса.

Сравнение с други инструменти за контейнери

Възможност / ОграничениеСтандартен Docker (dockerd)Rootless Docker / PodmanKaniko / Buildah (непривилегировани)gvx (rungv + docker:dind)
Изисква хостов root или sudoДаНеНеНе
Работи с PR_SET_NO_NEW_PRIVS=1НеНе (newuidmap се проваля)ЧастичноДа
Работи, когато unshare(CLONE_NEWUSER) е блокиран (EPERM)Не се прилагаНеBuildah: Не / Kaniko: ДаДа
Работи без /dev/fuseДа (използва ядрен overlay2)Само през бавно копиране vfsДаДа (overlayfs върху ext4lite)
Поддържа docker build (BuildKit)ДаДа (ако userns е разрешено)Само изграждане на образи (без изпълнение)Да
Поддържа docker run и docker compose upДаДа (ако userns е разрешено)НеДа
Работи на Windows и macOS без Linux VMНе (изисква WSL2 / VM)Не (изисква Linux VM)НеДа (whpx, hvf, systrap)

Точни изисквания към хостовото ядро на Linux

gvx изисква нулеви повишени възможности (CapEff: 0000000000000000) и нула потребителски именни пространства. На хост Linux rungv разчита само на три стандартни непривилегировани примитива на ядрото:

  1. ptrace(PTRACE_ATTACH) върху собствената си дъщерна нишка веднъж по време на инициализация: systrap стартира локална помощна нишка-заглушка, прикачва се към нея, за да инициализира контекста със споделена памет, и се отделя. Ако хостът зададе kernel.yama.ptrace_scope = 3 (което деактивира всички ptrace в системата) или блокира ptrace във външен филтър seccomp, инициализацията на systrap не може да завърши. (При yama.ptrace_scope = 1, което е по подразбиране в Ubuntu и Debian, gvx работи успешно, защото проследяването от родител към дете е разрешено.)
  2. seccomp(SECCOMP_SET_MODE_FILTER): използва се за прихващане на системните извиквания на гост системата от нишките-заглушки на контейнера. Както е описано в Част 3 от тази серия, gvx работи както когато SECCOMP_FILTER_FLAG_NEW_LISTENER е наличен, така и когато връща EBUSY във вложен сандбокс.
  3. mmap(..., PROT_EXEC, ...): използва се за картиране на ELF сегментите на гост системата и трамплина със споделена памет на systrap.

Край-до-край пример: Изграждане и тестване на многоконтейнерно приложение без root

Следният скрипт изпълнява пълен docker build и интеграционен тест с два контейнера (redis + контейнер с приложението) в непривилегирована потребителска сесия:

 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# Изградете образа с overlayfs върху ext4lite
20docker build -t demo-ping:latest .
21
22# Създайте мрежа за мост, стартирайте Redis и проверете връзката между контейнерите
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# Изход: PONG
27
28# Почистете работните контейнери и спрете сандбокса
29docker rm -f cache
30docker network rm demo-net
31kill -INT $DIND_PID

Допълнителна литература в тази серия

  1. gvx: Изпълнение на Linux контейнери, Docker и Kubernetes в един непривилегирован процес: Архитектурен преглед на gvx, неговите междуплатформени изпълнителни енджини (whpx, hvf, systrap), VFS2 (erofs, ext4lite, hostfs v2) и netstack.
  2. Изпълнение на Docker, docker build и docker compose без root или потребителски именни пространства (тази публикация)
  3. Изпълнение на Docker-in-Docker в gVisor и вложени сандбокси с gvx: Как gvx решава modprobe, iptables-nft, overlay2 върху gofer и вложения SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY) в gVisor (runsc).
  4. Бързи изолирани Docker среди за RL rollouts на AI агенти с gvx: Мащабиране на паралелни SWE-bench и RL rollout среди с 100-милисекундни връщания към състояние на томове .ext4 с Copy-on-Write.