Привет!
Давайте рассмотрим способы запуска Docker в Docker-контейнере (вложенное использование Docker). Такой подход не является повседневным использованием, но иногда помогает решить задачи:
При построении пайплайна CI/CD в GitLab или Jenkins для создания образов Docker и их публикации в реджестри (реестре образов)
При использовании Jenkins Dynamic Agents (динамических Docker-контейнеров)
При необходимости построения изолированной среды от хоста, где будет выполнятся стендовая работа, а после проведения можно будет легко удалить её.
Существует три способа запуска docker в docker:
Запуск docker путём монтирования docker.sock (метод DooD)
Использовать дочерний контейнер внутри контейнера (метод dind)
Использование среды выполнения Nestybox sysbox Docker
Для эксперимента создадим виртуальную машину в облаке Cloud4Y из готового шаблона ubuntu-server-2204 по мануалу. Далее установим docker (https://docs.docker.com/engine/install/ubuntu/). Я воспользуюсь скриптом:
#curl -sSL https://get.docker.com/ | sh
Проверим версию
# docker –v
Docker version 20.10.22, build 3a2c30b
Метод 1: Docker в Docker с использованием [/var/run/docker.sock]
Что такое /var/run/docker.sock
/var/run/docker.sock
является сокетом Unix по умолчанию (IPC-сокет). Сокеты предназначены для обмена данными между процессами на одном хосте. Демон Docker по умолчанию прослушивает docker.sock. Если вы находитесь на том же хосте, на котором запущен демон Docker, вы можете использовать /var/run/docker.sock
для управления контейнерами.
Например, если мы запустим следующую команду:
# curl --unix-socket /var/run/docker.sock http://localhost/version
она вернёт версию docker engine, такую же, как мы проверяли ранее:
{"Platform":{"Name":"Docker Engine -
Community"},"Components":[{"Name":"Engine","Version":"20.10.22","Details":{"ApiVersion":"1.41","A
rch":"amd64","BuildTime":"2022-12-
15T22:25:49.000000000+00:00","Experimental":"false","GitCommit":"42c8b31","GoVersion":"go1.18.9
","KernelVersion":"5.15.0-27-
generic","MinAPIVersion":"1.12","Os":"linux"}},{"Name":"containerd","Version":"1.6.13","Details":{"Git
Commit":"78f51771157abb6c9ed224c22013cdf09962315d"}},{"Name":"runc","Version":"1.1.4","Detail
s":{"GitCommit":"v1.1.4-0-g5fd4c4d"}},{"Name":"dockerinit","
Version":"0.19.0","Details":{"GitCommit":"de40ad0"}}],"Version":"20.10.22","ApiVersion":"1.41",
"MinAPIVersion":"1.12","GitCommit":"42c8b31","GoVersion":"go1.18.9","Os":"linux","Arch":"amd64","
KernelVersion":"5.15.0-27-generic","BuildTime":"2022-12-15T22:25:49.000000000+00:00"}
Теперь давайте запустим Docker in Docker с помощью /var/run/docker.sock
. Для этого нам нужно запустить docker с сокетом Unix по умолчанию и передать docker.sock в качестве тома:
# docker run -v /var/run/docker.sock:/var/run/docker.sock -ti docker
Этот метод иногда называют DooD (Docker outside of Docker), потому что он использует Docker вне Docker. Поскольку он монтирует только сокет хост-среды, используемый образ Docker не используется dind, и не требует привилегированный -privileged режим, как это описано далее в методе 2.
Тут нужно понимать риски безопасности, что если ваш контейнер получает доступ к docker.sock, это означает, что у него будут повышенные привилегии по сравнению с docker.
Теперь из контейнера мы имеем возможность выполнять команды docker для создания и отправки образов в реджестри. Фактически, в данном случае операции docker выполняются на хосте виртуальной машины, на которой запущен ваш базовый контейнер docker, а не внутри контейнера. То есть, если вы выполняете команды docker из контейнера, вы даёте указание docker подключиться к docker-engine хоста виртуальной машины через docker.sock.
Воспользуемся примером выше, где мы используем официальный образ docker из docker hub и запускаем контейнер Docker в интерактивном режиме.
# docker run --name dood -v /var/run/docker.sock:/var/run/docker.sock -ti docker
Внутри запущенного контейнера скачаем образ docker — alpine, командой # docker pull alpine
И посмотрим доступные локальные образы в контейнере
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker latest 81a4721a045e 8 days ago 152MB
alpine latest 49176f190c7e 3 weeks ago 7.05MB
Теперь, если мы выйдем из контейнера и посмотрим доступные образы на хостовой машине, то увидим те же, что и в контейнере. Данный метод позволяет экономить место на диске, поскольку не дублирует образы Docker, но позволяет из контейнера увидеть другие контейнеры, запущенные на хостовой машине.
Метод 2: Docker в Docker с использованием dind
Этот метод фактически создаёт дочерний контейнер внутри контейнера. Используйте этот метод, только если вы действительно хотите, чтобы контейнеры и образы находились внутри контейнера. Для этого нужно использовать официальный образы docker с тэгом dind. Образ dind загружается с необходимыми утилитами для запуска Docker внутри контейнера Docker и требуют для запуска привилегированный режим. Запустим контейнер:
# docker run --privileged -d --name dind docker:dind
И войдем интерактивно:
# docker exec -it dind /bin/sh
повторим, что делали ранее, но скачаем теперь другой образа docker — busybox
# docker pull busybox
И посмотрим доступные локальные образы в контейнере
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 334e4a014c81 7 days ago 4.86MB
Теперь выйдем из контейнера и посмотрим на хостовой машине
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker dind d74418bd2227 8 days ago 322MB
docker latest 81a4721a045e 8 days ago 152MB
alpine latest 49176f190c7e 3 weeks ago 7.05MB
Поскольку Docker на хост-машине и Docker в контейнере docker: dind являются отдельными, образы в контейнере (busybox) не видны с хост-машины, и наоборот.
С этим методом связано много проблем:
Он плохо работает с модулями безопасности Linux (LSM)
Есть проблемы с драйвером хранилища и возможным несоответствии между файловой системой, используемой для контейнеров созданных внутри родительского контейнера, с файловой системой хоста. Могут возникать комбинации, когда это не будет работать.
С безопасностью — из-за необходимости использования привилегированного режима.
Отсутствием поддержки docker-compose в установке по умолчанию и необходимости установки https://docs.docker.com/compose/install/
Метод 3: Docker в Docker с использованием среды выполнения Sysbox
Методы 1 и 2 имеют недостатки с точки зрения безопасности из-за запуска базовых контейнеров в привилегированном режиме. Nestybox пытается решить эту проблему, используя среду выполнения Sysbox Docker. Sysbox — это бесплатная среда выполнения контейнеров с открытым исходным кодом, которая расширяет возможности контейнеров двумя ключевыми способами:
Улучшает изоляцию контейнера:
Пространство имён пользователей Linux во всех контейнерах (т. е. пользователь root в контейнере не имеет привилегий на хосте).
Виртуализирует части procfs и sysfs внутри контейнера.
Скрывает информацию о хосте внутри контейнера и другие.
Позволяет контейнерам работать подобно виртуальным машинам:
С помощью Sysbox контейнеры могут запускать программное обеспечение системного уровня, такое как systemd, Docker, Kubernetes, K3s, buildx, устаревшие приложения и т. д. беспрепятственно и безопасно.
Это программное обеспечение может работать внутри контейнеров Sysbox без модификации и без использования специальных версий программного обеспечения.
Никаких привилегированных контейнеров, специальных монтирований томов и т. д.
Sysbox достигает этого, используя контейнер максимально похожий на виртуальную среду, с помощью передовых методов виртуализации ОС. Для этого метода установим Sysbox по документации.
# wget https://downloads.nestybox.com/sysbox/releases/v0.5.0/sysbox-ce_0.5.0-0.linux_amd64.deb
# sha256sum sysbox-ce_0.5.0-0.linux_amd64.deb
(Должен быть eeacd9ae0e08ee5e5637e3b93e4f0cf78f20f9590ef2e7ab08347700682422f0 sysbox-ce_0.5.0-0.linux_amd64.deb)
# docker rm $(docker ps -a -q) -f
# sudo apt-get install -y jq
# sudo apt-get install -y ./sysbox-ce_0.5.0-0.linux_amd64.deb
# sudo systemctl status sysbox -n20
● sysbox.service - Sysbox container runtime
Loaded: loaded (/lib/systemd/system/sysbox.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2022-12-14 13:39:54 UTC; 14s ago
Docs: https://github.com/nestybox/sysbox
Main PID: 14708 (sh)
Tasks: 2 (limit: 4579)
Memory: 408.0K
CPU: 61ms
CGroup: /system.slice/sysbox.service
├─14708 /bin/sh -c "/usr/bin/sysbox-runc --version && /usr/bin/sysbox-mgr --version && /usr/bin/sysbox-fs --version && /bin/sleep infinity"
└─14726 /bin/sleep infinity
Теперь мы можем запустить наш контейнер с использованием среда выполнения sysbox при помощи # docker run --runtime=sysbox-runc --name sysbox -d docker:dind
И войти интерактивно:
# docker exec -it sysbox /bin/sh
Рекомендации по использованию
Используйте Docker в Docker только в том случае, если это действительно необходимо вам. Проводите достаточное тестирование, прежде чем переносить любые продуктовые решения на метод Docker-in-Docker.
При использовании контейнеров в привилегированном режиме убедитесь, что вы получили необходимые разрешения от групп безопасности на то, что вы планируете делать.
Использовании Docker-in-Docker не влияет на производительность контейнера, но определяется производительностью хоста.
Пример использования Docker-in-Docker для gitlab-runner
В качестве примера рассмотрим работу gitlab-runner с использованием всех методов. Для этого на нашей виртуальной машине установим gitlab-runner с executor-м docker:
# docker run -d --name gitlab-runner --restart always \
-v /srv/gitlab-runner/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest
зарегистрируем три раннера с разными тэгами: dood-tag, dind-tag, sysbox-tag для каждого метода и образом по умолчанию docker:stable.
# docker run --rm -it -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register
В конфигурацию соответствующего раннера в /srv/gitlab-runner/config/config.toml внесём правки:
В раннер dood-tag с методом 1, ставим настройки
volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]
В раннер dind-tag с методом 2, ставим настройки
privileged = true
В раннер sysbox-tag с методом 3, ставим настройки
runtime = "sysbox-runc"
В итоге CI/CD Settings нашего проекта мы увидим
Напишем конфигурацию для GitLab .gitlab-ci.yml
stages:
- DooD
- dind
- Sysbox
DooD_Job:
stage: DooD
script:
- docker images
tags:
- dood-tag
dind_Job:
stage: dind
variables:
DOCKER_TLS_CERTDIR: ""
services:
- docker:dind
script:
- docker images
tags:
- dind-tag
Sysbox_Job:
stage: Sysbox
variables:
DOCKER_TLS_CERTDIR: ""
services:
- docker:dind
script:
- docker images
tags:
- sysbox-tag
с тремя стейджами и заданием для каждого метода, запускаемого на своем раннере, и посмотрим результат docker images
Результаты по каждому раннеру:
DooD_Runner
Dind_runner
Sysbox_runner
Мы видим сходство результата по второму и третьему методу, при этом мы не использовали привилегированный режим в третьем.
Подробности по раннерам смотрите в документации GitLab:
Написано на базе этой статьи. Спасибо за внимание!
Что ещё интересного есть в блоге Cloud4Y
→ Информационная безопасность и глупость: необычные примеры
→ Как распечатать цветной механический телевизор на 3D-принтере
→ Создание e-ink дисплея с прогнозом погоды
→ Аналоговый компьютер Telefunken RA 770
Подписывайтесь на наш Telegram-канал, чтобы не пропустить очередную статью. Пишем только по делу. А ещё напоминаем про второй сезон нашего сериала ITить-колотить. Его можно посмотреть на YouTube и ВКонтакте.