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>

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ:

  1. Это, вероятно, не будет работать для последовательных устройств, таких как веб-камеры и т. Д. Я тестировал это только для USB-накопителей.
  2. Если вам нужно регулярно повторно подключать и отключать устройства, этот метод будет раздражать, а также не будет работать, если вы не сбросите путь монтирования и не перезапустите контейнер.
  3. Я использовал докер 17.06 + как прописано в документации
Другие вопросы по тегам