Docker - 一种访问主机USB或串行设备的方法?

时间:2014-06-15 00:56:48

标签: docker

上次我查看了Docker didn't have any means to give container access to host serial or USB port。是否有允许这样做的技巧?

10 个答案:

答案 0 :(得分:149)

有几种选择。首先,正如下面的@Mark所述,Docker版本1.2。0(2014/08年度发布)添加了--device标志,用于在没有--privileged模式的情况下访问USB设备:

docker run -t -i --device=/dev/ttyUSB0 ubuntu bash

或者,假设您的USB设备可用于/dev/bus/usb主机上的驱动程序等,您可以使用privileged modevolumes option将其安装在容器中。例如:

docker run -t -i --privileged -v /dev/bus/usb:/dev/bus/usb ubuntu bash

答案 1 :(得分:72)

使用当前版本的Docker,您可以使用--device标志来实现您的目标,而无需访问所有USB设备。

例如,如果您只想在Docker容器中访问/dev/ttyUSB0,则可以执行以下操作:

docker run -t -i --device=/dev/ttyUSB0 ubuntu bash

答案 2 :(得分:9)

我想扩展已经给出的答案,以包括对未用/dev/bus/usb捕获的动态连接设备的支持,以及如何在将Windows主机与boot2docker VM一起使用时如何使它正常工作。

如果使用Windows,则需要在VirtualBox管理器中为希望Docker访问的设备添加任何USB规则。为此,您可以通过运行以下命令停止VM:

host:~$ docker-machine stop default

打开VirtualBox Manager并根据需要添加带有过滤器的USB支持。

启动boot2docker VM:

host:~$ docker-machine start default

由于USB设备已连接到boot2docker VM,因此需要从该计算机上运行命令。使用VM打开终端并运行docker run命令:

host:~$ docker-machine ssh
docker@default:~$ docker run -it --privileged ubuntu bash

注意,当像这样运行命令时,将仅捕获以前连接的USB设备。仅当您希望它与容​​器启动后连接的设备一起使用时,才需要volumes标志。在这种情况下,您可以使用:

docker@default:~$ docker run -it --privileged -v /dev:/dev ubuntu bash

请注意,在某些情况下,我必须使用/dev而不是/dev/bus/usb来捕获/dev/sg2之类的设备。我只能假设对于/dev/ttyACM0/dev/ttyUSB0之类的设备也是如此。

docker run命令也将与Linux主机一起使用。

答案 3 :(得分:7)

如果您想动态访问可在Docker容器运行时插入的USB设备,例如访问/ dev / video0上刚刚连接的USB网络摄像头,则可以在启动容器时添加cgroup规则。此选项不需要--privileged容器,仅允许访问特定类型的硬件。

步骤1

检查要添加的设备类型的设备主号码。您可以在linux kernel documentation中查找它。或者,您可以为您的设备检查它。例如,要检查连接到/ 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

在启动Docker容器时添加规则:

  • 为要访问的每种类型的设备添加--device-cgroup-rule='c major_number:* rmw'规则
  • 添加对udev信息的访问权限,以便Docker容器可以通过-v /run/udev:/run/udev:ro在您的USB设备上获取更多信息
  • 使用-v /dev:/dev将/ dev卷映射到您的docker容器中

总结

因此,要将所有USB网络摄像头和serial2usb设备添加到Docker容器,请执行以下操作:

docker run -it -v /dev:/dev --device-cgroup-rule='c 188:* rmw' --device-cgroup-rule='c 81:* rmw' ubuntu bash

答案 4 :(得分:6)

在没有--privileged模式的情况下访问tty设备的安全和正确方式

按照说明一行行,所有步骤都有说明

想法是正确配置cgroup规则。首先,让我们找到您的 USB 设备的 cgroup 属性。运行以下命令:

$ ls -l /dev/ | grep ttyUSB
crw-rw-rw-  1 root  dialout 188,   0 Mar  1 18:23 ttyUSB0 #Example output

根据输出,您可以看到在我的情况下,tty 设备的主要组是 188,因此我将继续进行。

您可以运行 docker 镜像 allowing access to range of devices with specific major number,docker 会在您的主机中为您添加所需的规则(这将在分离模式下运行 docker,我们稍后会附加到它):

docker run --device-cgroup-rule='c 188:* rmw' -itd --name my_container ubuntu

现在的想法是添加一个脚本,每次插入或拔出 USB 设备时都会运行该脚本。关于自定义规则 herehere on passing arguments 的一些解释。在 ubuntu 上,您应该以超级用户 (sudo) 身份创建文件 /etc/udev/rules.d/99-docker-tty.rules

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> 查找其他参数,您可以根据这些参数过滤设备。您可以按照建议的 here 测试规则。要应用这些规则:

root@~$ udevadm control --reload 

现在我们还需要以超级用户 (sudo) 身份在 /usr/local/bin/docker_tty.sh 中创建以下脚本。您可以看到它被设置为在我们之前创建的 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 

此脚本将在您正在运行的 docker 容器中创建 tty 设备,或者根据设备是否已插入或已拔出将其删除(类似于 Ubuntu 机器发生的情况 - 每次插入设备时,您都可以看到它在 /dev/ 目录下)。提示:检查文件 /tmp/docker_tty.log 以获得主机上的一些调试输出,此外,按照建议的 here 调试 bash 脚本。

不要忘记使脚本可执行:

root@~$ chmod +x /usr/local/bin/docker_tty.sh

现在连接到 docker 并查看插入和拔出设备时设备是否出现在 /dev/ 目录中:

docker exec -it my_container bash

答案 5 :(得分:5)

--device一直工作到USB设备拔出/重新插入,然后停止工作为止。您必须使用 cgroup设备。允许绕开它。
您可以只使用-v /dev:/dev,但这是不安全的,因为它会将主机中的所有设备都映射到容器中,包括原始磁盘设备等。基本上,这可以使容器在主机上获得根,而这通常不是您想要的。
在这方面,使用cgroups方法更好,并且可以在容器启动后添加的设备上使用。

在此处查看详细信息:Accessing USB Devices In Docker without using --privileged

粘贴起来有点困难,但是简而言之,您需要获取字符设备的主要编号并将其发送给cgroup:

189是/ dev / ttyUSB *的主要号码,可以通过'ls -l'获得。您的系统上可能与我的系统上不同:

root@server:~# echo 'c 189:* rwm' > /sys/fs/cgroup/devices/docker/$A*/devices.allow  
(A contains the docker containerID)

然后像这样启动容器:

docker run -v /dev/bus:/dev/bus:ro -v /dev/serial:/dev/serial:ro -i -t --entrypoint /bin/bash debian:amd64

如果不执行此操作,则在容器启动后任何新插入或重新启动的设备都将获得新的总线ID,并且将不允许在该容器中访问。

答案 6 :(得分:0)

另一个选项是调整udev,它控制设备的安装方式和特权。允许非root用户访问串行设备很有用。如果您已永久连接设备,则--device选项是最好的选择。如果您拥有临时设备,这就是我一直在使用的设备:

1。设置udev规则

默认情况下,将挂载串行设备,以便只有root用户才能访问该设备。我们需要添加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 run命令中添加以下volume命令来实现此目的:

-v /dev:/dev

如果您的设备已永久连接,则从安全角度来看,使用--device选项或更特定的卷挂载可能是更好的选择。

3。在特权模式下运行容器

如果您未使用--device选项并安装在整个/ dev文件夹中,则将需要以特权模式运行容器(我将检查上述cgroup的内容,看看是否可以删除)。您可以通过在Docker运行命令中添加以下内容来实现此目的:

--privileged

4。从/ dev / serial / by-id文件夹访问设备

如果可以插入和拔出设备,Linux不能保证将其始终安装在同一ttyUSBxxx位置(特别是如果您有多个设备)。幸运的是,Linux会自动在/ dev / serial / by-id文件夹中建立到设备的符号链接。此文件夹中的文件将始终命名为相同的文件。

这是一个简短的摘要,我有一个blog article,其中有更多详细信息。

答案 7 :(得分:0)

我们很难将特定的USB设备绑定到同样特定的Docker容器。如您所见,推荐的实现方法是:

docker run -t -i --privileged -v /dev/bus/usb:/dev/bus/usb ubuntu bash

它将所有设备绑定到此容器。不安全每个容器都被允许操作所有容器。

另一种方法是通过devpath绑定设备。可能看起来像:

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

也许会有所帮助。

答案 8 :(得分:0)

对于想要快速使用docker内部工作的外部USB设备(HDD,闪存驱动器),并且使用特权模式的人,

在主机上找到设备的devpath:

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

安装此devpath(最好是/media):

sudo mount <drive path> /media/<mount folder name>

然后您可以将其用作docker run的参数,例如:

docker run -it -v /media/<mount folder name>:/media/<mount folder name>

或在docker下的卷下组成:

services:
  whatevermyserviceis:
    volumes:
      - /media/<mount folder name>:/media/<mount folder name>

现在,当您运行并输入容器时,您应该可以在/media/<mount folder name>处访问容器内的驱动器

免责声明:

  1. 这可能不适用于网络摄像头等串行设备。 我只对USB存储驱动器进行过测试。
  2. 如果您需要定期重新连接和断开设备,则此 该方法将很烦人,并且除非重新设置安装路径并重新启动容器,否则该方法将不起作用。
  3. 我按照docs的规定使用了docker 17.06 +

答案 9 :(得分:-1)

使用最新版本的docker,这就足够了:

docker run -ti --privileged ubuntu bash

它将允许访问所有系统资源(例如,在/ dev中)