Как разрешить удаленное управление Docker
Как разрешить с одного хоста управлять Docker
-ом на другом хосте? Именно такой вопрос встал передо мной, когда я разворачивал сервисы для управления контейнерами: Portainer и Docker Compose UI. Теоретически, мне было известно, что у Docker
имеется API для удаленного управления, оставалось добыть сведения, как разрешить его использование. На самом деле, сделать это совсем не трудно. Довольно быстро нашлось несколько решений, которые, в принципе, сводятся к одному - задать параметры при старте сервиса Docker
.
Начну с самого очевидного: надо отредактировать файл, описывающий сервис Docker
-а. У меня Docker
установлен на виртуалках, на которых, в качестве "гостевых" ОС, используются Debian Jessie
и Debian Stretch
, в которых, в свою очередь, управление сервисами[1] осуществляется при помощи systemd. А раз так, то для задания параметров запуска сервиса нужно отредактировать файл описания этого самого сервиса.
Согласно документации, файлы описания служб (более правильный темин - unit
-файлы) могут находиться в каталогах:
/etc/systemd/system
-unit
-файлы локальной конфигурации/lib/systemd/system
-unit
-файлы, предоставляемыеDebian
причем те, которые находятся в каталоге/etc/systemd/system
имеют более высокий приоритет - они переопределяют такие же файлы из каталога/lib/systemd/system
, если, конечно, таковые найдутся.
Так вот, файл, описывающий службу Docker
(docker.service
), был обнаружен мной в каталоге /etc/systemd/system/multi-user.target.wants
(хотя, на самом деле, это была ссылка на /lib/systemd/system/docker.service
, но это не так уж и важно, просто... забавно, что ли). Вот эталонное содержимое этого файла, взятое с github-а:
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target docker.socket firewalld.service
Wants=network-online.target
Requires=docker.socket
[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd -H fd://
ExecReload=/bin/kill -s HUP $MAINPID
LimitNOFILE=1048576
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNPROC=infinity
LimitCORE=infinity
# Uncomment TasksMax if your systemd version supports it.
# Only systemd 226 and above support this version.
#TasksMax=infinity
TimeoutStartSec=0
# set delegate yes so that systemd does not reset the cgroups of docker containers
Delegate=yes
# kill only the docker process, not all processes in the cgroup
KillMode=process
# restart the docker process if it exits prematurely
Restart=on-failure
StartLimitBurst=3
StartLimitInterval=60s
[Install]
WantedBy=multi-user.target
Нас интересует следующая строка:
ExecStart=/usr/bin/dockerd -H fd://
Именно она задаёт параметры запуска службы Docker
и именно её необходимо изменить. Примерно так:
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375
В приведенном выше примере был добавлен параметр -H tcp://0.0.0.0:2375
, что означает разрешение взаимодействие со службой Docker
по порту 2375
через любой сетевой интерфейс. Если хочется указать конкретный интерфейс, то вместо 0.0.0.0
следует задать IP
адрес нужного интерфейса[2].
Итак, файл изменен и сохранен. Для того,чтобы изменения вступили в силу, необходимо "перечитать" unit
-файлы. Делается это при помощи команды:
systemctl daemon-reload
Эта команда предназначена для "мягкой" перезагрузки: производится считывание unit
-файлов и перестраиваются деревья зависимостей. Но этого недостаточно - для того, чтобы Docker
-ом можно было управлять, надо его (сервис) перегрузить:
service docker restart
Следует учитывать, что при перезапуске службы Docker
все активные контейнеры будут остановлены и запущены вновь. Поэтому, если есть такая неоходимость, следует озаботиться сохранностью данных в приложениях, связанных с работающими контейнерами. И да, я упоминал, что все вышеперечисленные действия надо выполнять от имени суперпользователя, то есть, root
? Если нет, то вот, упоминаю: все эти действия надо выполнять, работая как root
, или пользуясь командами sudo
или su
.
Теперь, когда, собственно, понятна суть требуемых изменений, хочу рассказать о различных вариантах этого решения. И начну с того, которое реализовал у себя.
Как я уже отмечал, все решения сводятся к одному и тому же - указанию (разрешению) использовать порт 2375
при доступе по протоколу TCP
. Только достигается это немного по-разному. Я, например, использовал файл /etc/default/docker
. На самом деле, считается, что для *nix
систем, работающих с systemd
этот каталог (/etc/default/
) утратил свое значение и использоваться не должен[3]. Тем не менее, он есть, и в нем даже есть множество файлов, в том числе, и для Docker
. Называется этот файл docker
(внезапно 😉) и первоначально имеет следующий вид:
# Docker Upstart and SysVinit configuration file
#
# THIS FILE DOES NOT APPLY TO SYSTEMD
#
# Please see the documentation for "systemd drop-ins":
# https://docs.docker.com/engine/admin/systemd/
#
# Customize location of Docker binary (especially for development testing).
#DOCKERD="/usr/local/bin/dockerd"
# Use DOCKER_OPTS to modify the daemon startup options.
#DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4"
# If you need Docker to use an HTTP proxy, it can also be specified here.
#export http_proxy="http://127.0.0.1:3128/"
# This is also a handy place to tweak where Docker's temporary files go.
#export DOCKER_TMPDIR="/mnt/bigdrive/docker-tmp"
В этом файле все прекрасно - он полностью состоит из комментариев, самыми интересными из которых являются:
...
# Use DOCKER_OPTS to modify the daemon startup options.
...
и
...
# THIS FILE DOES NOT APPLY TO SYSTEMD
...
Из этих двух строк следует, что для изменения параметров запуска dockerd
надо описать переменную окружения с именем DOCKER_OPTS
, а также то, что сделав это, мы ничего не добьёмся, потому что у нас система с systemd
. Но вот ведь какая штука - довольно часто на возникающие вопросы есть ответы. В нашем случае все решается так:
- в файле
/etc/default/docker
определяем переменную окруженияDOCKER_OPTS
[4]:
DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4 -H tcp://192.168.0.240:2375 -H unix:///var/run/docker.sock"
- в файле
/etc/systemd/system/multi-user.target.wants/docker.service
делаем следующие изменения:
...
[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
EnvironmentFile=/etc/default/docker
ExecStart=/usr/bin/dockerd -H fd:// $DOCKER_OPTS
...
Немного поясню суть этих правок. В файле /etc/default/docker
все-таки объявляем переменную окружения DOCKER_OPTS
, а в unit
-файле /etc/systemd/system/multi-user.target.wants/docker.service
определяем, что переменные окружения описаны в файле /etc/default/docker
и добавляем нужную нам переменную (DOCKER_OPTS
) в строку, описывающую параметры запуска сервиса Docker
.
Описанный выше метод прекрасно работает, в частности, у меня (после обновления конфигурации и перезапуска сервиса), но это, наверное, не самый правильный подход - решать задачу, используя механизмы, которые официально считаются неприменимыми в существующих реалиях. Естественно, возникает вопрос, а как идеологически правильно должна решаться эта задача.
Один путь решения проблемы основан на использовании механизмов, встроенных в systemd
. Идея, лежащая в основе такого подхода, заключается в том, что не следует редактировать файл /etc/systemd/system/multi-user.target.wants/docker.service
(который и не файл вовсе, а ссылка на /lib/systemd/system/docker.service
), так как его оригинал, так сказать, поставляется разработчиками Docker
и, при определенных условиях (обновлении, например), может быть перезаписан, в результате чего внесенные изменения будут потеряны. Вместо этого следует использовать механизм drop-in службы systemd
.
Что ж, похоже на разумный подход, тем более, должен признать - однажды у меня такое произошло и после обновления Docker
мне пришлось восстанавливать собственные правки в соответствующем unit
-файле. Далее я приведу последовательность действий, которые надо выполнить, чтобы разрешить с помощью механизма drop-in
, встроенного в systemd
, использовать Docker Remote API
.
Для начала, необходимо создать в каталоге /etc/systemd/system
подкаталог docker.service.d
, а в нем - файл с расширением conf
, например, docker_override.conf
. Таким образом, полный путь к файлу будет выглядеть так:
/etc/systemd/system/docker.service.d/docker_override.conf
Далее, в этом файле надо указать секции и имена свойств, которые надо переопределить (относительно базового unit
-фйла). В нашем случае это будет выглядеть, например, так:
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375
После сохранения файла выполняем стандартный набор команд:
systemctl daemon-reload
systemctl restart docker.service
Последняя команда (systemctl restart docker.service
) аналогична команде service docker restart
. По крайней мере, у меня.
Надо сказать, что этот вариант решения я не проверял на Debian Jessie
, но смог проверить на Debian Buster
, и, должен сказать, что он замечательно работает. Правда, для текущей стабильной версии Debian
[5] в unit
-файле службы Docker
строка ExecStart
имеет вид:
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
Поэтому, переопределяя конфигурацию для Buster
, я всегда дописывал --containerd=/run/containerd/containerd.sock
[6]. И вообще, внимательно изучайте оригинальный файл в своей системе, используйте именно его содержимое в качестве основы для модификаций[7].
Приведенный выше метод с использованием механизма drop-in
службы systemd
не является единственным. В документации Docker
приводится описание не только опций командной строки службы dockerd
, но и описание специального конфигурационного файла /etc/docker/daemon.json
. Исходя из этого описания можно предложить следующий вариант решения проблемы: создать в каталоге /etc/docker
файл daemon.json
и заполнить его таким содержимым:
{"hosts": ["tcp://0.0.0.0:2375"]}
После перечитывания конфигурации systemd
и перезапуска dockerd
должно все заработать. Обратите внимание, что я использовал слово "должно" - дело в том, что этот метод я не проверял 😉[8]
Теперь, когда мы предоставили возможность удаленно управлять нашими Docker
-машинами, самое время подумать про безопасность этого действа. Хотя, вроде бы, следовало поступить наоборот (то есть, сначала подумать, а уж потом...), но, лучше поздно, чем никогда. Тем более, что само по себе предоставление такого доступа повышает уязвимость системы в общем. Но, если честно, я не очень беспокоюсь. И вот почему.
Помимо всяких примочек, типа NAT на роутере, на входе у меня стоит reverse proxy
, который, помимо всего прочего, терминирует SSL
трафик. То есть, даже если какой-то сервис и не поддерживает SSL
самостоятельно, за него это делает прокси. Таким образом, трафик за пределами локальной сети всегда оказывается зашифрованным.
К тому же, если сервис не содержит встроенных возможностей создания и управления пользователями, эту функцию на себя берет все тот же reverse proxy
. То есть, при любом внешнем подключении к сервису (извне локальной сети) будут запрошены имя и пароль пользователя.
Ну и наконец, если уж очень страшно, то можно воспользоваться внутренними возможностями Docker
и вместо "открытого" порта 2375
задействовать "защищенный" порт 2376
. Правда, придется произвести дополнительные настройки, но ведь спокойствие того стоит? Я же не стал этого делать, так как сервисы, которые должны подключаться к моим Docker
-машинам, хостятся у меня же, и трафик этот не покидает пределов локальной сети.
Вот, собственно, пока и всё, что я имею сказать по этому поводу.
Так в системах с
systemd
принято называть "демонов" (daemons
) ↩︎Параметр
-H fd://
означает, что со службойDocker
разрешено взаимодействие с использованием механизма Systemd socket activation, и для большинства вариантов локального использования этого вполне достаточно. ↩︎О том, для чего предназначался этот каталог и почему он стал ненужен очень неплохо написано тут. ↩︎
Я привожу значение в точности в том виде, которое использую сам, но есть предположение, что параметр
-H unix:///var/run/docker.sock
- лишний ↩︎Buster
является в настоящей момент стабильной версиейDebian
↩︎На самом деле, это связано не с версией
Debian
, а с версиейDocker
. ↩︎Небольшое уточнение - проверил я этот подход и столкнулся с проблемами. При перезапуске службы
Docker
в таком варианте конфигурации выдается ошибка, что параметр задан и в командной строке и в конфигурационном файле, поэтому - все плохо и служба не запускается. Пока дальше не разбирался, может, просто где-то что-то недокрутил. ↩︎