Rootless-режим Docker решает конкретную задачу: запустить демон и контейнеры без прав root на хосте. Для процессов изоляции это удобно — меньше поверхность атаки, меньше риск, что уязвимость в контейнере превратится в компрометацию всего сервера. Но у этой модели есть обратная сторона: почти вся сетевая подсистема Linux — создание bridge-интерфейсов, veth-пар, правил iptables — исторически требует привилегий суперпользователя. Rootless Docker обходит это ограничение, полностью выстраивая сеть в пользовательском пространстве, и именно поэтому сетевой стек здесь работает иначе, чем в обычном Docker.
В этом материале мы разберём, из каких компонентов состоит сетевая архитектура rootless-режима, как контейнер получает выход в интернет без единого привилегированного вызова и какие ограничения стоит учитывать при переносе продакшн-нагрузок на такую схему. Отдельное внимание уделим настройке — от установки до выбора сетевого драйвера и публикации портов.
Материал рассчитан на системных администраторов, DevOps-инженеров и разработчиков, которые уже знакомы с обычным Docker, но впервые настраивают rootless-режим — например, на выделенном сервере, где нет доступа к root, или в целях повышения изоляции на многопользовательском хосте.
Терминология: основные понятия
Прежде чем переходить к архитектуре, стоит зафиксировать термины — без них дальнейшее описание будет звучать как набор аббревиатур.
| Термин | Что это |
|---|---|
| User namespace | Механизм ядра Linux, позволяющий процессу считать себя root внутри изолированного пространства идентификаторов, оставаясь непривилегированным пользователем на хосте. На этом механизме держится вся модель rootless. |
| RootlessKit | Инструмент, который оборачивает демон Docker: создаёт user, mount и network namespace, поднимает пользовательский сетевой стек и передаёт управление dockerd. По сути — оркестратор всего rootless-окружения. |
| slirp4netns | Сетевой драйвер по умолчанию. Эмулирует TCP/IP-стек в пользовательском пространстве и подключает его к TAP-интерфейсу внутри network namespace — без прав root и без изменения сетевых настроек хоста. |
| VPNKit | Резервный драйвер, который подключается, если slirp4netns не установлен. Изначально создавался для Docker Desktop на macOS и Windows. |
| pasta | Более новый драйвер (доступен начиная с Docker Engine 25.0), который транслирует пакеты между TAP-устройством и хостом напрямую, без полной эмуляции стека. За счёт этого показывает заметно более высокую пропускную способность, чем slirp4netns. |
| Port driver | Компонент RootlessKit, отвечающий за проброс портов из network namespace на хост — то есть за работу флага -p в docker run. |
Как эти компоненты связаны между собой
Проще всего представить это слоями. RootlessKit создаёт изолированное пространство и внутри него разворачивает dockerd — обычный демон, который ничего не знает о том, что работает без root. Сетевой драйвер (slirp4netns, VPNKit или pasta) обеспечивает связь этого пространства с внешним миром. Port driver отдельно решает, как именно порт контейнера станет доступен снаружи. Каждый слой можно настраивать независимо, и в этом — гибкость архитектуры.
Как это устроено: архитектура сетевого стека
Роль RootlessKit как посредника
Обычный Docker создаёт docker0 — bridge-интерфейс на хосте, к которому подключаются veth-пары контейнеров. В rootless-режиме такой трюк недоступен: создание bridge и veth требует CAP_NET_ADMIN. Поэтому RootlessKit запускает dockerd внутри собственного network namespace, а docker0 и остальные Docker-сети существуют только внутри этого namespace. На хосте они не видны — команда ip link show, выполненная от имени обычного пользователя, их не покажет.
Связь между этим изолированным namespace и реальной сетью хоста обеспечивает пара TAP-устройство плюс сетевой драйвер. TAP-интерфейс создаётся внутри namespace, а драйвер читает из него пакеты и переправляет их наружу — уже от имени процесса RootlessKit, у которого достаточно прав для обычных сетевых операций через сокеты хоста.
Пользовательский стек и NAT без iptables
Здесь и проявляется главное отличие. slirp4netns не создаёт правила NAT на хосте — вместо этого драйвер сам эмулирует трансляцию адресов внутри пользовательского процесса. Контейнер получает частный IP (обычно из диапазона 10.0.2.0/24), пакеты уходящего трафика перехватываются TAP-интерфейсом, разворачиваются программным TCP/IP-стеком и отправляются во внешнюю сеть уже как обычные сокеты процесса slirp4netns. Входящий трафик проходит обратный путь. С точки зрения хоста никакой особой сетевой конфигурации не происходит — просто работает ещё один процесс пользователя.
У pasta логика похожая, но реализована легче: вместо полной эмуляции стека драйвер занимается преимущественно трансляцией заголовков пакетов, что снижает накладные расходы на копирование данных между пространствами. На практике это выражается в заметно более высокой пропускной способности — в некоторых бенчмарках pasta приближается к производительности обычного bridge-режима, тогда как slirp4netns заметно отстаёт.
Проброс портов: builtin, slirp4netns, socat, implicit
Публикация порта — отдельная задача, потому что namespace изолирован не только от исходящего, но и от входящего трафика. По умолчанию используется builtin port driver: он открывает слушающий сокет на хосте и перенаправляет соединения в namespace через внутренний RPC-механизм RootlessKit. Это самый быстрый и наименее ресурсоёмкий вариант, но у него есть заметный недостаток — исходный IP-адрес клиента теряется, все входящие соединения внутри контейнера выглядят как пришедшие с локального шлюза.
Если важно сохранить реальный IP клиента, применяют port driver slirp4netns — он пробрасывает порты силами самого сетевого драйвера и поддерживает передачу исходного адреса. Есть и driver socat, который для каждого проброшенного порта запускает отдельный процесс socat — простое, но менее масштабируемое решение, разумное только при небольшом числе портов. Для pasta используется собственный режим — implicit, где проброс портов встроен в саму логику драйвера.
DNS и разрешение имён
Контейнеры в rootless-режиме получают DNS-сервер от слоя эмуляции — как правило, это виртуальный адрес вроде 10.0.2.3, который транслирует запросы в резолвер хоста. С точки зрения приложения внутри контейнера ничего необычного не происходит: nslookup и curl работают привычно. На практике проблемы с DNS почти всегда сводятся к тому, что не поднялся сам сетевой драйвер — ошибки в настройках резолвера встречаются заметно реже.
Производительность и MTU
Плата за пользовательский стек — задержка и нагрузка на CPU: каждый пакет проходит через дополнительный слой копирования данных между namespace и процессом драйвера. Для slirp4netns значение MTU по умолчанию обычно выставляется в 65520, что частично компенсирует потери на мелких пакетах, но для тяжёлых сетевых нагрузок — стриминга, репликации баз данных, высоконагруженных API — разница с обычным Docker всё равно заметна. pasta в этом смысле выигрывает за счёт более лёгкой реализации, но остаётся не таким быстрым, как нативный bridge с правами root.
Практическое применение
Сценарии, где rootless-сеть оправдана
Rootless-режим редко выбирают ради скорости — его выбирают ради изоляции. Несколько типичных сценариев: локальная среда разработки на общем сервере, где у разработчика нет и не должно быть root; CI/CD-раннеры, которые выполняют произвольный код из pull request-ов и не должны иметь возможность выйти за пределы своей песочницы; многопользовательские хосты, где несколько команд запускают контейнеры на одной машине и нужно гарантировать, что compromise одного контейнера не затронет соседей.
Отдельный случай — размещение на VPS, где провайдер не выдаёт полный root-доступ или где сам администратор сознательно ограничивает привилегии сервисных аккаунтов. При развёртывании на VPS от Serverspace rootless Docker позволяет запускать контейнеризованные сервисы под отдельным непривилегированным пользователем, не выдавая ему доступ к остальной системе, — удобно, если на одном сервере соседствуют несколько независимых проектов.
Архитектурный пример: приложение за обратным прокси
Типичная схема выглядит так: rootless-контейнер с приложением слушает непривилегированный порт, например 8080, а перед ним стоит nginx или Caddy — либо тоже в контейнере, либо как системный сервис с правами на привязку к 80 и 443. Такая связка снимает главное ограничение rootless-режима — невозможность напрямую занять порты ниже 1024 — и при этом сохраняет изоляцию самого приложения.
Сравнение сетевых драйверов
| Драйвер | Производительность | Сохранение исходного IP | Когда применять |
|---|---|---|---|
| slirp4netns | Средняя | Да, с port driver slirp4netns | Значение по умолчанию, максимальная совместимость |
| VPNKit | Низкая | Ограниченно | Резервный вариант, когда slirp4netns недоступен |
| pasta | Выше среднего, ближе к нативной | Да, через режим implicit | Нагруженные сервисы, где важна пропускная способность |
Пошаговая настройка
Шаг 1. Подготовка: subuid и subgid
Rootless Docker опирается на диапазоны идентификаторов, выделенные пользователю в /etc/subuid и /etc/subgid. Без них user namespace просто не поднимется.
grep "^$(whoami):" /etc/subuid /etc/subgid
# если записей нет — добавляем диапазон вручную
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $(whoami)
Шаг 2. Установка rootless-режима
Официальный установочный скрипт разворачивает systemd-сервис пользователя и настраивает dockerd под текущим аккаунтом — root на этом шаге не нужен вовсе.
curl -fsSL https://get.docker.com/rootless | sh
dockerd-rootless-setuptool.sh install
docker context use rootless
systemctl --user enable docker
loginctl enable-linger $(whoami)
Последняя команда важна отдельно: она разрешает пользовательским сервисам продолжать работу после выхода из сессии — иначе демон остановится при закрытии SSH-подключения.
Шаг 3. Проверка текущего сетевого режима
docker info | grep -i rootless
ps aux | grep rootlesskit
Вторая команда полезнее, чем кажется: в строке запуска rootlesskit видны фактические флаги --net и --port-driver, а значит — какой драйвер реально используется прямо сейчас.
Шаг 4. Переключение на pasta
По умолчанию используется slirp4netns. Для нагруженных сервисов имеет смысл перейти на pasta — драйвер нужно сначала установить, затем прописать переменные окружения для systemd-юнита.
sudo apt install -y passt
mkdir -p ~/.config/systemd/user/docker.service.d/
cat < ~/.config/systemd/user/docker.service.d/override.conf
[Service]
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET=pasta"
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=implicit"
EOF
systemctl --user daemon-reload
systemctl --user restart docker
После перезапуска стоит повторить проверку из шага 3 — в строке процесса rootlesskit должно появиться --net=pasta.
Шаг 5. Публикация привилегированных портов
Если приложению действительно нужен порт ниже 1024 без обратного прокси перед ним, есть два пути. Первый — снизить системную границу непривилегированных портов, второй — выдать capability самому бинарнику rootlesskit.
# вариант 1: снизить порог для всех пользователей
echo "net.ipv4.ip_unprivileged_port_start=0" | sudo tee -a /etc/sysctl.d/99-rootless.conf
sudo sysctl --system
# вариант 2: точечная capability только для rootlesskit
sudo setcap cap_net_bind_service=ep $(which rootlesskit)
Первый вариант проще, но снижает изоляцию для всех непривилегированных процессов на хосте — стоит применять его осознанно, а не по умолчанию.
Шаг 6. Проверка связности
docker run -d --name netcheck -p 8080:80 nginx:alpine
curl -I http://127.0.0.1:8080
docker rm -f netcheck
Если curl вернул HTTP-заголовки — сеть настроена верно, контейнер получает трафик снаружи и, в свою очередь, может достучаться до внешних адресов через тот же слой драйвера.
Шаг 7. Диагностика типичных проблем
Когда что-то не работает, первым делом стоит смотреть логи пользовательского сервиса — там обычно видно, на каком именно шаге упал rootlesskit.
journalctl --user -u docker --no-pager -n 50
Чаще всего причина одна из трёх: не выделены диапазоны subuid/subgid, не установлен пакет slirp4netns или passt, либо на хосте отключена поддержка непривилегированных user namespace на уровне ядра — это стоит проверить отдельно, поскольку некоторые дистрибутивы ограничивают её из соображений безопасности по умолчанию.
Заключение: что важно запомнить
Сетевой стек rootless-режима устроен на других принципах, чем в обычном Docker: вся изоляция строится вокруг пользовательского пространства вместо ядерных примитивов. RootlessKit берёт на себя изоляцию, сетевой драйвер — связь с внешним миром, port driver — публикацию портов, и каждый из этих слоёв можно настраивать под конкретную задачу. slirp4netns остаётся безопасным выбором по умолчанию, pasta — разумным апгрейдом там, где важна пропускная способность, а builtin port driver стоит менять на slirp4netns или implicit, если приложению нужен реальный IP клиента.
Главное ограничение — привилегированные порты и общая просадка производительности относительно root-режима — на практике решается связкой с обратным прокси и выбором более быстрого драйвера. Для сценариев, где изоляция важнее последних процентов пропускной способности — общие серверы, CI-раннеры, мультитенантные окружения на VPS — rootless Docker закрывает задачу, не требуя компромиссов по безопасности.