Docker - способ дать доступ к хосту USB или последовательному устройству?
В прошлый раз, когда я проверял, у Docker не было никаких средств, чтобы предоставить контейнеру доступ к последовательному порту хоста или порту USB. Есть ли хитрость, которая позволяет это сделать?
11 ответов
Есть несколько вариантов. Во-первых, как отметил @Mark ниже, версия 1.2.0 Docker (выпущена в 2014/08) добавила --device
флаг, который можно использовать для доступа к USB-устройствам без --privileged
Режим:
docker run -t -i --device=/dev/ttyUSB0 ubuntu bash
В качестве альтернативы, если ваше USB-устройство доступно с работающими драйверами и т. Д. На хосте в /dev/bus/usb
Вы можете смонтировать это в контейнере, используя привилегированный режим и параметр томов. Например:
docker run -t -i --privileged -v /dev/bus/usb:/dev/bus/usb ubuntu bash
В текущих версиях Docker вы можете использовать --device
флаг, чтобы достичь того, что вы хотите, без необходимости предоставлять доступ ко всем USB-устройствам.
Например, если вы хотите сделать только /dev/ttyUSB0
доступный в вашем контейнере Docker, вы можете сделать что-то вроде:
docker run -t -i --device=/dev/ttyUSB0 ubuntu bash
--device работает до тех пор, пока ваше USB-устройство не будет отключено / повторно подключено, а затем оно перестанет работать. Вы должны использовать cgroup devices.allow обойти это.
Вы можете просто использовать -v / dev: / dev, но это небезопасно, так как оно отображает все устройства с вашего хоста в контейнер, включая необработанные дисковые устройства и так далее. По сути, это позволяет контейнеру получить права root на хосте, что обычно не то, что вам нужно.
Использование подхода cgroups лучше в этом отношении и работает на устройствах, которые добавляются после контейнера при запуске.
Подробности см. Здесь: http://marc.merlins.org/perso/linux/post_2018-12-20_Accessing-USB-Devices-In-Docker-_ttyUSB0_-dev-bus-usb-_-for-fastboot_-adb_-without-using-privileged.html
Это немного сложно вставить, но в двух словах, вам нужно получить основной номер для вашего устройства персонажа и отправить его в cgroup:
root @ server: ~ # echo 'c 189: * rwm'> /sys/fs/cgroup/devices/docker/$A*/devices.allow
(A содержит идентификатор контейнера Docker)
Затем запустите свой контейнер следующим образом:
запуск докера -v / dev / bus: / dev / bus: ro -v / dev / serial: / dev / serial: ro -i -t - точка входа /bin/bash debian:amd64
без этого любое новое подключенное или перезагружаемое устройство после запуска контейнера получит новый идентификатор шины и не получит доступ к контейнеру.
Безопасный и правильный способ доступа к tty-устройствам без привилегированного режима
Просто следуйте инструкциям строка за строкой, все шаги объяснены
Идея состоит в том, чтобы правильно настроить правила контрольной группы. Прежде всего, давайте найдем свойства cgroup вашего USB-устройства. Выполните следующую команду:
$ ls -l /dev/ | grep ttyUSB
crw-rw-rw- 1 root dialout 188, 0 Mar 1 18:23 ttyUSB0 #Example output
Основываясь на выводе, вы можете видеть, что основная группа tty-устройств - это
188
в моем случае, так что я продолжу это.
Вы можете запустить образ докера, позволяющий получить доступ к ряду устройств с определенным основным номером , докер добавит необходимые правила для вас на вашем хост-компьютере (это запустит докер в отдельном режиме, мы подключимся к нему позже):
docker run --device-cgroup-rule='c 188:* rmw' -itd --name my_container ubuntu
Теперь идея состоит в том, чтобы добавить сценарий, который будет запускаться каждый раз, когда ваше USB-устройство подключается или отключается. Некоторые пояснения по поводу пользовательских правил здесь и здесь по передаче аргументов . В ubuntu вы должны создать файл
/etc/udev/rules.d/99-docker-tty.rules
как суперпользователь (sudo):
ACTION=="add", SUBSYSTEM=="tty", RUN+="/usr/local/bin/docker_tty.sh 'added' '%E{DEVNAME}' '%M' '%m'"
ACTION=="remove", SUBSYSTEM=="tty", RUN+="/usr/local/bin/docker_tty.sh 'removed' '%E{DEVNAME}' '%M' '%m'"
Этот файл добавляет новую запись в ваши правила, в основном говоря: Каждый раз, когда устройство tty подключается -
add
или отключен -
remove
запустите предоставленный сценарий и передайте некоторые параметры. Если вы хотите быть более конкретным, вы можете использовать
udevadm info --name=<device name>
найти другие параметры, по которым можно фильтровать устройства. Вы можете проверить правила, как предложено здесь . Чтобы применить эти правила:
root@~$ udevadm control --reload
Теперь нам нужно создать следующий скрипт в
/usr/local/bin/docker_tty.sh
также как суперпользователь (sudo). Вы можете видеть, что он был настроен на запуск в правилах udev, которые мы создали ранее.
#!/usr/bin/env bash
echo "Usb event: $1 $2 $3 $4" >> /tmp/docker_tty.log
if [ ! -z "$(docker ps -qf name=env_dev)" ]
then
if [ "$1" == "added" ]
then
docker exec -u 0 env_dev mknod $2 c $3 $4
docker exec -u 0 env_dev chmod -R 777 $2
echo "Adding $2 to docker" >> /tmp/docker_tty.log
else
docker exec -u 0 env_dev rm $2
echo "Removing $2 from docker" >> /tmp/docker_tty.log
fi
fi
Этот скрипт создаст tty-устройство в вашем работающем док-контейнере или удалит его в зависимости от того, было ли устройство подключено или отключено (аналогично тому, что происходит с машиной Ubuntu - каждый раз, когда устройство подключено, вы можете видеть его в каталоге ). Совет: проверьте файл
/tmp/docker_tty.log
для некоторых отладочных данных на вашем хост-компьютере также можно отладить сценарий bash, как предлагается здесь.
Не забудьте сделать скрипт исполняемым:
root@~$ chmod +x /usr/local/bin/docker_tty.sh
Теперь подключитесь к докеру и посмотрите, отображается ли устройство в
/dev/
каталог при подключении и отключении:
docker exec -it my_container bash
Я хотел бы расширить уже предоставленные ответы, чтобы включить поддержку динамически подключенных устройств, которые не перехвачены с /dev/bus/usb
и как заставить это работать при использовании хоста Windows вместе с виртуальной машиной boot2docker.
Если вы работаете с Windows, вам нужно будет добавить любые правила USB для устройств, к которым вы хотите, чтобы Docker имел доступ в диспетчере VirtualBox. Для этого вы можете остановить виртуальную машину, запустив:
host:~$ docker-machine stop default
Откройте VirtualBox Manager и добавьте поддержку USB с фильтрами по мере необходимости.
Запустите boot2docker VM:
host:~$ docker-machine start default
Поскольку устройства USB подключены к виртуальной машине boot2docker, команды необходимо запускать с этого компьютера. Откройте терминал с виртуальной машиной и выполните команду docker run:
host:~$ docker-machine ssh
docker@default:~$ docker run -it --privileged ubuntu bash
Обратите внимание, что когда команда запускается следующим образом, захватываются только ранее подключенные USB-устройства. Флаг томов требуется только в том случае, если вы хотите, чтобы он работал с устройствами, подключенными после запуска контейнера. В этом случае вы можете использовать:
docker@default:~$ docker run -it --privileged -v /dev:/dev ubuntu bash
Обратите внимание, я должен был использовать /dev
вместо /dev/bus/usb
в некоторых случаях для захвата устройства, как /dev/sg2
, Я могу только предположить, что то же самое будет верно для таких устройств, как /dev/ttyACM0
или же /dev/ttyUSB0
,
Команды docker run также будут работать с хостом Linux.
Если вы хотите получить динамический доступ к USB-устройствам, которые можно подключить, пока контейнер докера уже запущен, например, доступ к только что подключенной веб-камере usb в /dev/video0, вы можете добавить правило cgroup при запуске контейнера. Эта опция не требует --privileged контейнера и разрешает доступ только к определенным типам оборудования.
Шаг 1
Проверьте старший номер устройства того типа, который вы хотите добавить. Вы можете найти его в документации ядра Linux. Или вы можете проверить это для своего устройства. Например, чтобы проверить основной номер устройства для веб-камеры, подключенной к /dev/video0, вы можете выполнитьls -la /dev/video0
. В результате получается что-то вроде:
crw-rw----+ 1 root video 81, 0 Jul 6 10:22 /dev/video0
Где первое число (81) - это старший номер устройства. Некоторые общие основные номера устройств:
- 81: USB-камеры
- 188: преобразователи USB в последовательный порт
Шаг 2
Добавьте правила при запуске контейнера докеров:
- Добавить
--device-cgroup-rule='c major_number:* rmw'
правило для каждого типа устройства, к которому вы хотите получить доступ - Добавьте доступ к информации udev, чтобы контейнеры докеров могли получать больше информации о ваших USB-устройствах с помощью
-v /run/udev:/run/udev:ro
- Сопоставьте том / dev с контейнером докеров с помощью
-v /dev:/dev
Заворачивать
Итак, чтобы добавить все usb-веб-камеры и устройства serial2usb в ваш контейнер докеров, выполните:
docker run -it -v /dev:/dev --device-cgroup-rule='c 188:* rmw' --device-cgroup-rule='c 81:* rmw' ubuntu bash
Другой вариант - настроить udev, который управляет монтированием устройств и какими привилегиями. Полезно, чтобы разрешить доступ без полномочий root к последовательным устройствам. Если у вас есть постоянно подключенные устройства, --device
вариант - лучший путь. Если у вас есть эфемерные устройства, вот что я использовал:
1. Установите правило Udev
По умолчанию последовательные устройства монтируются так, что только корневые пользователи могут получить доступ к устройству. Нам нужно добавить правило udev, чтобы сделать его читаемым пользователями без полномочий root.
Создайте файл с именем /etc/udev/rules.d/99-serial.rules. Добавьте следующую строку в этот файл:
KERNEL=="ttyUSB[0-9]*",MODE="0666"
MODE = "0666" предоставит всем пользователям права на чтение / запись (но не выполнение) для ваших устройств ttyUSB. Это наиболее допустимый вариант, и вы можете ограничить его в зависимости от ваших требований безопасности. Вы можете прочитать об udev, чтобы узнать больше об управлении тем, что происходит, когда устройство подключено к шлюзу Linux.
2. Смонтировать в /dev папку с хоста на контейнер
Последовательные устройства часто бывают эфемерными (их можно подключать и отключать в любое время). Из-за этого мы не можем подключиться к прямому устройству или даже к папке /dev / serial, потому что они могут исчезнуть при отключении устройства. Даже если вы подключите их снова, и устройство снова появится, это технически файл, отличный от того, что был смонтирован, поэтому Docker его не увидит. По этой причине мы монтируем всю папку /dev с хоста в контейнер. Вы можете сделать это, добавив следующую команду громкости в команду запуска Docker:
-v /dev:/dev
Если ваше устройство постоянно подключено, то использование параметра --device или более точное монтирование тома, вероятно, является лучшим вариантом с точки зрения безопасности.
3. Запустите контейнер в привилегированном режиме.
Если вы не использовали опцию --device и монтировали ее во всей папке /dev, вам потребуется запустить контейнер в привилегированном режиме (я собираюсь проверить материал cgroup, упомянутый выше, чтобы увидеть, можно ли его удалить). Вы можете сделать это, добавив следующее в команду запуска Docker:
--privileged
4. Доступ к устройству из папки /dev/serial/by-id
Если ваше устройство может быть подключено и отключено, Linux не гарантирует, что оно всегда будет установлено в одном и том же месте ttyUSBxxx (особенно если у вас несколько устройств). К счастью, Linux автоматически создаст символическую ссылку на устройство в папке /dev/serial/by-id. Файл в этой папке всегда будет называться одинаково.
Это краткое изложение, у меня есть статья в блоге, в которой более подробно.
Нам сложно привязать конкретное USB-устройство к контейнеру докера, который также специфичен. Как видите, рекомендуемый способ достижения:
docker run -t -i --privileged -v /dev/bus/usb:/dev/bus/usb ubuntu bash
Он привяжет все устройства к этому контейнеру. Это небезопасно. Каждому контейнеру было предоставлено право управлять всеми из них.
Другой способ - привязка устройств по пути разработчика. Это может выглядеть так:
docker run -t -i --privileged -v /dev/bus/usb/001/002:/dev/bus/usb/001/002 ubuntu bash
или --device
(лучше нет privileged
):
docker run -t -i --device /dev/bus/usb/001/002 ubuntu bash
Намного безопаснее. Но на самом деле трудно узнать, что такое devpath конкретного устройства.
Я написал это репо, чтобы решить эту проблему.
https://github.com/williamfzc/usb2container
После развертывания этого сервера вы можете легко получить информацию обо всех подключенных устройствах через HTTP-запрос:
curl 127.0.0.1:9410/api/device
и получить:
{
"/devices/pci0000:00/0000:00:14.0/usb1/1-13": {
"ACTION": "add",
"DEVPATH": "/devices/pci0000:00/0000:00:14.0/usb1/1-13",
"DEVTYPE": "usb_device",
"DRIVER": "usb",
"ID_BUS": "usb",
"ID_FOR_SEAT": "xxxxx",
"ID_MODEL": "xxxxx",
"ID_MODEL_ID": "xxxxx",
"ID_PATH": "xxxxx",
"ID_PATH_TAG": "xxxxx",
"ID_REVISION": "xxxxx",
"ID_SERIAL": "xxxxx",
"ID_SERIAL_SHORT": "xxxxx",
"ID_USB_INTERFACES": "xxxxx",
"ID_VENDOR": "xxxxx",
"ID_VENDOR_ENC": "xxxxx",
"ID_VENDOR_FROM_DATABASE": "",
"ID_VENDOR_ID": "xxxxx",
"INTERFACE": "",
"MAJOR": "189",
"MINOR": "119",
"MODALIAS": "",
"PRODUCT": "xxxxx",
"SEQNUM": "xxxxx",
"SUBSYSTEM": "usb",
"TAGS": "",
"TYPE": "0/0/0",
"USEC_INITIALIZED": "xxxxx",
"adb_user": "",
"_empty": false,
"DEVNAME": "/dev/bus/usb/001/120",
"BUSNUM": "001",
"DEVNUM": "120",
"ID_MODEL_ENC": "xxxxx"
},
...
}
и привяжите их к своим контейнерам. Например, вы можете увидеть DEVNAME этого устройства:/dev/bus/usb/001/120
:
docker run -t -i --device /dev/bus/usb/001/120 ubuntu bash
Может, это поможет.
Существует также более простой способ совместного использования USB-устройств без
--privileged
флаг. Для этого:
docker run ... --device=/dev/bus/usb --device=/dev/usb <container>
Для последних версий Docker этого достаточно:
docker run -ti --privileged ubuntu bash
Это даст доступ ко всем системным ресурсам (например, в /dev)
Добавляя ответы к приведенным выше ответам, для тех, кто хочет быстро использовать внешнее USB-устройство (жесткий диск, флеш-накопитель), работающее внутри докера, и не использующее привилегированный режим:
Найдите путь к устройству на хосте:
sudo fdisk -l
Вы можете легко узнать свой накопитель по его емкости из списка. Скопируйте этот путь (в следующем примере это/dev/sda2
).
Disque /dev/sda2 : 554,5 Go, 57151488 octets, 111624 secteurs
Unités : secteur de 1 × 512 = 512 octets
Taille de secteur (logique / physique) : 512 octets / 512 octets
taille d'E/S (minimale / optimale) : 512 octets / 512 octets
Смонтируйте этот путь разработчика (предпочтительно /media
):
sudo mount <drive path> /media/<mount folder name>
Затем вы можете использовать это как параметр для docker run
нравиться:
docker run -it -v /media/<mount folder name>:/media/<mount folder name>
или в докере составлять под томами:
services:
whatevermyserviceis:
volumes:
- /media/<mount folder name>:/media/<mount folder name>
И теперь, когда вы запускаете и входите в свой контейнер, вы должны иметь доступ к диску внутри контейнера по адресу /media/<mount folder name>
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ:
- Это, вероятно, не будет работать для последовательных устройств, таких как веб-камеры и т. Д. Я тестировал это только для USB-накопителей.
- Если вам нужно регулярно повторно подключать и отключать устройства, этот метод будет раздражать, а также не будет работать, если вы не сбросите путь монтирования и не перезапустите контейнер.
- Я использовал докер 17.06 + как прописано в документации