GoSuda

Запуск Docker, docker build и docker compose без прав root и пространств имён пользователей

By Lemon Mint views ...

Быстрое решение: безопасный непривилегированный Docker в три шага

Если вы работаете внутри ограниченного Linux-контейнера, CI-раннера, Kubernetes Pod или песочницы AI-агента, где заблокированы root, sudo, пользовательские пространства имён (unshare(CLONE_NEWUSER)), mount(2) и /dev/fuse, gvx (rungv) является безопасным решением с эшелонированной защитой для запуска полноценного движка docker:dind в пользовательском пространстве.

Почему 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 на хосте

Команды docker ps или docker build немедленно завершаются ошибкой, если хостовый /var/run/docker.sock не подключён к окружению:

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

2. PR_SET_NO_NEW_PRIVS блокирует sudo и вспомогательные программы с setuid

Усиленные контейнеры (securityContext.allowPrivilegeEscalation: false в Kubernetes, --security-opt=no-new-privileges в Docker или песочницы exebox / bwrap) устанавливают бит Linux PR_SET_NO_NEW_PRIVS для дерева процессов. Попытка повысить привилегии через 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 (user namespaces, 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) заблокирован без CAP_SYS_ADMIN на хосте

Даже если среда разрешает CLONE_NEWUSER, монтирование нового /proc внутри вложенного пространства монтирования завершается ошибкой, если внешний контейнер скрыл поддеревья /proc (/proc/acpi, /proc/kcore, /proc/keys) или исключил mount(2) из 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. Отсутствие /dev/fuse ломает fuse-overlayfs

Rootless-движки контейнеров, которые не могут смонтировать ядерный 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 (gVisor Sentry) и пользовательский сетевой стек (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 и без чрезмерного расхода дискового пространства, характерного для резервного драйвера Docker vfs.

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 внутри песочницы.

Сравнение с существующими инструментами для контейнеров

Возможность / ОграничениеStandard Docker (dockerd)Rootless Docker / PodmanKaniko / Buildah (Unprivileged)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)Только сборка образов (без runtime)Да
Поддерживает docker run и docker compose upДаДа (если разрешён userns)НетДа
Работает на Windows и macOS без виртуальной машины LinuxНет (требуется WSL2 / ВМ)Нет (требуется ВМ Linux)НетДа (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-агентов ИИ с gvx: масштабирование параллельных сред SWE-bench и RL-развёртываний со сбросом томов .ext4 с copy-on-write за 100 миллисекунд.