Перейти к содержимому

Rootless docker: как работает и зачем нужен jenkins в kubernetes

Как работает Docker в режиме Rootless

Перенос Jenkins-агентов в Kubernetes часто выглядит простой задачей только на бумаге. Сам Jenkins можно развернуть в кластере относительно быстро, однако динамические агенты нередко используют Docker-in-Docker (DinD) для сборки образов и публикации приложений. Именно на этом этапе появляются вопросы безопасности, сетевой изоляции и доступов к Docker-демону.

Один из вариантов решения - запуск Docker в Rootless-режиме. В этом случае демон, контейнерный runtime и управляющие утилиты работают не от имени root, а от имени обычного пользователя. Такой подход особенно полезен для Jenkins-агентов, CI/CD-систем и других временных окружений, где компрометация контейнера не должна приводить к захвату узла Kubernetes или хостовой операционной системы.

Зачем нужен Rootless Docker

В классической конфигурации Docker демон запускается с привилегиями суперпользователя. Пользователь взаимодействует с ним через Unix-сокет, обычно расположенный по адресу `/var/run/docker.sock`. При этом любой процесс, получивший доступ к сокету, фактически может управлять Docker на уровне всей системы.

Злоумышленник с таким доступом способен:

- запускать контейнеры с расширенными полномочиями;
- подключать к контейнеру файловую систему хоста;
- изменять сетевые настройки;
- читать или изменять данные операционной системы;
- создавать новые процессы с правами root.

Rootless Docker устраняет необходимость запускать сам демон с привилегиями суперпользователя. Важно отличать этот режим от параметра `--user`, который лишь меняет UID процесса внутри конкретного контейнера. В Rootless-архитектуре непривилегированным становится весь Docker-стек: демон, runtime, контейнеры и вспомогательные компоненты.

Основные механизмы Rootless-режима

Работа Rootless Docker основана на нескольких возможностях ядра Linux.

User namespaces

Пользовательское пространство имён позволяет разделить идентификаторы внутри контейнера и на хосте. Например, обычный пользователь с UID 1000 на машине может выглядеть как root с UID 0 внутри контейнера. Однако это "виртуальный root": за пределами пользовательского namespace процесс сохраняет права исходного непривилегированного аккаунта.

Диапазоны идентификаторов задаются в файлах:

```text
/etc/subuid
/etc/subgid
```

Для пользователя выделяется дополнительный диапазон UID и GID. Эти значения используются при создании контейнеров и сопоставлении пользователей между внутренней и внешней средой.

Network namespaces

Сетевые пространства имён изолируют сетевые интерфейсы, маршруты и таблицы соединений. В обычном Docker для построения сети применяются bridge-интерфейсы и veth-пары, создание которых обычно требует расширенных полномочий.

Rootless Docker использует пользовательский сетевой стек. На практике часто применяется `slirp4netns`. Он обеспечивает сетевое взаимодействие контейнера без создания привилегированного bridge на хосте. Такой вариант проще и безопаснее, но может уступать классической Docker-сети по производительности и возможностям настройки.

Файловая система

Для работы overlay-файловой системы без root-привилегий применяется `fuse-overlayfs`. Этот компонент реализует необходимую логику через FUSE и позволяет Docker хранить слои образов в пользовательском каталоге.

В отдельных окружениях доступен нативный rootless overlayfs, но его поддержка зависит от версии ядра, конфигурации системы и используемого дистрибутива. Поэтому `fuse-overlayfs` остаётся наиболее совместимым вариантом.

Два способа запуска Rootless Docker

Docker в непривилегированном режиме можно запускать двумя способами:

1. как пользовательский systemd-сервис;
2. напрямую через `/usr/bin/dockerd-rootless.sh`.

Наиболее предпочтителен первый вариант. При установке systemd-сервиса корректно формируется окружение пользователя, создаются необходимые каталоги, запускается демон и настраиваются параметры взаимодействия с cgroups.

Прямой запуск скрипта кажется проще, но в контейнерах и Kubernetes-Pod может привести к неожиданным проблемам. Не всегда создаётся полноценное окружение systemd, а часть Docker-команд начинает работать некорректно. Например, Jenkins-плагин `withDockerContainer` может выполнять `docker top`, получать неполную информацию о контейнере и завершать pipeline с ошибкой.

Установка Rootless Docker

На системах семейства Debian и Ubuntu сначала устанавливают Docker и пакет с дополнительными компонентами Rootless:

```bash
sudo apt install docker-ce docker-ce-rootless-extras
```

В состав `docker-ce-rootless-extras` входят скрипты настройки и необходимые зависимости, включая инструменты работы с UID/GID и пользовательской сетью.

Перед установкой стоит проверить наличие подчинённых диапазонов идентификаторов:

```bash
grep "$(whoami)" /etc/subuid
grep "$(whoami)" /etc/subgid
```

Если записей нет, их добавляют с правами администратора. Пример:

```text
jenkins:100000:65536
```

Один и тот же пользователь должен иметь диапазоны и в `/etc/subuid`, и в `/etc/subgid`.

После этого запускают автоматическую настройку:

```bash
dockerd-rootless-setuptool.sh install
```

Скрипт обычно:

- проверяет доступность `newuidmap` и `newgidmap`;
- анализирует настройки `/etc/subuid` и `/etc/subgid`;
- создаёт каталоги конфигурации Docker;
- формирует пользовательский unit systemd;
- включает и запускает Rootless Docker;
- выводит рекомендации по переменным окружения.

Переменные окружения

Rootless-демон использует пользовательский сокет, а не системный `/var/run/docker.sock`. Поэтому клиент Docker должен знать его расположение.

Чаще всего добавляют в профиль пользователя:

```bash
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
```

Для постоянной настройки строку можно сохранить в `~/.bashrc`, `~/.profile` или другом файле, который загружается оболочкой CI-агента.

Проверить текущий адрес Docker можно командой:

```bash
echo "$DOCKER_HOST"
```

Также полезно убедиться, что клиент действительно обращается к непривилегированному демону:

```bash
docker info
```

В выводе должны присутствовать признаки Rootless-режима, а каталог данных обычно располагается внутри домашней директории пользователя, например:

```text
~/.local/share/docker
```

Управление сервисом через systemd

Поскольку Docker работает как пользовательский сервис, управление им выполняют без `sudo`:

```bash
systemctl --user status docker
systemctl --user restart docker
systemctl --user stop docker
systemctl --user start docker
```

Чтобы сервис запускался после перезагрузки даже без активной пользовательской сессии, может потребоваться включить linger:

```bash
loginctl enable-linger jenkins
```

Это особенно важно для Jenkins-агентов и серверных окружений. Без linger пользовательский systemd может завершить работу после выхода из сессии, из-за чего Docker окажется недоступен для новых заданий.

Особенности cgroups

Rootless Docker не всегда может управлять всеми параметрами cgroups так же, как привилегированный демон. Это зависит от версии systemd, ядра Linux и конфигурации cgroup v2.

Некоторые ограничения могут затрагивать:

- установку лимитов памяти;
- управление CPU;
- настройку swap;
- сбор статистики контейнеров;
- применение отдельных параметров runtime.

В Kubernetes дополнительно нужно учитывать ограничения самого Pod. Даже если Rootless Docker корректно настроен внутри контейнера, он не сможет получить больше ресурсов, чем выделено этому Pod через `resources.requests` и `resources.limits`.

Запуск Rootless Docker внутри Kubernetes

Для DinD-подхода требуется отдельный контейнер с Docker-демоном и клиентом, однако запускать его с `privileged: true` обычно не хочется. Rootless-вариант позволяет снизить уровень риска, но требует тщательной подготовки образа.

В образ должны входить:

- Docker Engine и `docker-ce-rootless-extras`;
- `slirp4netns`;
- `fuse-overlayfs`;
- `uidmap`;
- пользователь с настроенными диапазонами UID/GID;
- корректный `HOME`;
- каталог для Docker data root;
- пользовательский systemd либо иной механизм управления процессом.

Важно заранее решить, где будет храниться каталог `/home//.local/share/docker`. Для эфемерного Jenkins-агента чаще всего достаточно временного тома. Если слои образов должны сохраняться между заданиями, используется PersistentVolume, но это увеличивает требования к очистке, правам доступа и контролю дискового пространства.

Производительность и ограничения

Rootless Docker повышает безопасность, но не является полностью бесплатной абстракцией. Пользовательская сеть может давать большую задержку и меньшую пропускную способность по сравнению с обычным bridge-режимом. `fuse-overlayfs` также способен уступать нативной overlayfs на операциях с большим количеством мелких файлов.

Дополнительные ограничения могут проявляться при использовании:

- Docker Compose со сложными сетевыми настройками;
- контейнеров, которым нужны capabilities;
- публикации портов ниже 1024;
- прямого управления iptables;
- устройств хоста;
- host network;
- аппаратных ускорителей.

Если приложению действительно требуется доступ к устройствам или ядру хоста, Rootless Docker не заменит корректную модель привилегий. В таких случаях необходимо отдельно оценивать, можно ли отказаться от требования или изолировать подобную задачу на выделенном узле.

Как диагностировать проблемы

При неполадках сначала проверяют состояние пользовательского сервиса:

```bash
systemctl --user status docker
journalctl --user -u docker
```

Затем проверяют:

```bash
echo "$DOCKER_HOST"
docker info
docker version
```

Если Docker-клиент пытается подключиться к `/var/run/docker.sock`, значит переменная `DOCKER_HOST` не задана или не передана в окружение Jenkins.

Ошибки, связанные с UID/GID, обычно указывают на проблемы в `/etc/subuid` и `/etc/subgid`. Сообщения о сетевых возможностях требуют проверки `slirp4netns`, а ошибки хранилища - наличия `fuse-overlayfs`, прав на каталог Docker и свободного места.

Итог

Rootless Docker переносит Docker-демон из привилегированного пространства в пользовательское. User namespaces обеспечивают изоляцию идентификаторов, `slirp4netns` заменяет привилегированный сетевой стек, а `fuse-overlayfs` позволяет работать со слоями образов без root-доступа.

Для стабильной эксплуатации важно не ограничиваться запуском `dockerd-rootless.sh`. Лучше установить пользовательский systemd-сервис, настроить диапазоны UID/GID, переменную `DOCKER_HOST`, linger и проверить доступность cgroups. В Kubernetes необходимо дополнительно учитывать ресурсы Pod, жизненный цикл Jenkins-агента и место хранения слоёв.

Rootless-режим не устраняет все ограничения Docker, но существенно уменьшает последствия компрометации контейнера и делает DinD более приемлемым для CI/CD-инфраструктуры.

Прокрутить вверх