上次我检查时,Docker没有任何方法让容器访问主机串行或USB端口。有什么技巧可以做到吗?


有几个选择。你可以使用——device标志,用户可以在没有——特权模式的情况下访问USB设备:

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

或者,假设您的USB设备在主机/dev/bus/usb上可用,并且驱动程序正在工作等,您可以使用特权模式和volumes选项将其挂载到容器中。例如:

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

注意,顾名思义,——privileged是不安全的,应该小心处理。


在当前版本的Docker中,你可以使用——device标志来实现你想要的,而不需要访问所有的USB设备。

例如,如果你想在Docker容器中只使/dev/ttyUSB0可访问,你可以这样做:

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

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

如果你正在使用Windows,你需要在VirtualBox管理器中为你想要Docker访问的设备添加任何USB规则。为此,您可以通过以下命令停止虚拟机:

host:~$ docker-machine stop default

打开VirtualBox管理器,根据需要添加带有过滤器的USB支持。

启动boot2docker虚拟机:

host:~$ docker-machine start default

由于USB设备连接到boot2docker虚拟机,因此需要从该机器运行命令。打开虚拟机的终端,运行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运行命令也可以在Linux主机上运行。


--device works until your USB device gets unplugged/replugged and then it stops working. You have to use cgroup devices.allow get around it. You could just use -v /dev:/dev but that's unsafe as it maps all the devices from your host into the container, including raw disk devices and so forth. Basically this allows the container to gain root on the host, which is usually not what you want. Using the cgroups approach is better in that respect and works on devices that get added after the container as started.

在Docker中访问USB设备而不使用——特权

这有点难粘贴,但简而言之,你需要获得你的角色设备的主编号,并将其发送到cgroup:

188是/dev/ttyUSB*的主编号,可以通过'ls -l'获得。你的系统和我的系统可能不同:

root@server:~# echo 'c 188:* 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,并且将不允许访问容器。


对于最新版本的docker,这就足够了:

docker run -ti --privileged ubuntu bash

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


另一种选择是调整udev,它控制设备的挂载方式和权限。用于允许非根用户访问串行设备。如果你有永久连接的设备,——device选项是最好的选择。如果你有短暂的设备,这是我一直在使用的:

1. 设置udev规则

默认情况下,串口设备被挂载,只有root用户可以访问该设备。我们需要添加一条udev规则,让非root用户也能读懂它们。

创建一个名为/etc/udev/rules.d/99-serial.rules的文件。在该文件中添加以下一行:

内核==“ttyUSB[0-9]*”,MODE=“0666”

MODE="0666"将给予所有用户对ttyUSB设备的读/写(但不执行)权限。这是最允许的选项,您可能希望根据您的安全需求进一步限制它。您可以阅读udev以了解更多关于控制设备插入Linux网关时发生的事情的信息。

2. 从主机到容器挂载在/dev文件夹中

Serial devices are often ephemeral (can be plugged and unplugged at any time). Because of this, we can’t mount in the direct device or even the /dev/serial folder, because those can disappear when things are unplugged. Even if you plug them back in and the device shows up again, it’s technically a different file than what was mounted in, so Docker won’t see it. For this reason, we mount the entire /dev folder from the host to the container. You can do this by adding the following volume command to your Docker run command:

- v / dev: / dev

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

3.以特权模式运行container

如果你没有使用——device选项,并且挂载在整个/dev文件夹中,你将被要求运行容器的特权模式(我将检查上面提到的cgroup的东西,看看是否可以删除)。你可以通过在Docker run命令中添加以下命令来做到这一点:

——特权

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

如果您的设备可以插拔,Linux并不保证它总是挂载在同一个ttyUSBxxx位置(特别是如果您有多个设备)。幸运的是,Linux会自动创建到/dev/serial/by-id文件夹中的设备的符号链接。此文件夹中的文件将始终命名相同。

这是一个快速的纲要,我有一篇博客文章详细介绍。


我们很难将特定的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(最好没有特权):

docker run -t -i --device /dev/bus/usb/001/002 ubuntu bash

更安全。但实际上很难知道特定设备的devpath是什么。

我写了这个repo来解决这个问题。

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设备(HDD,闪存驱动器)在docker内部工作,而不使用特权模式的人:

在主机上找到你的设备的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运行的参数:

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

或者在docker中合成volumes:

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

现在,当您运行并进入容器时,您应该能够访问容器中的驱动器/media/<挂载文件夹名>

免责声明:

这可能不适用于串行设备,如网络摄像头等。 我只对USB存储驱动器进行了测试。 如果您需要定期重新连接和断开设备,这 方法将会很烦人,并且除非重置挂载路径并重新启动容器,否则将无法工作。 我使用docker 17.06 +在文档中规定


如果你想在docker容器已经运行时动态地访问可以插入的USB设备,例如在/dev/video0上访问一个附加的USB摄像头,你可以在启动容器时添加一个cgroup规则。该选项不需要——特权容器,只允许访问特定类型的硬件。

步骤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

在启动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

在没有-特权模式的情况下安全正确地访问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镜像,允许访问特定主号的设备范围,docker将在你的主机上为你添加所需的规则(这将以分离模式运行docker,我们稍后将附加到它):

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

现在的想法是添加一个脚本,它将在每次USB设备插入或拔出时运行。这里和这里对传递参数的自定义规则做了一些解释。在ubuntu上,你应该以超级用户(sudo)的身份创建文件/etc/udev/rules.d/99-docker- ty.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设备被插入-添加或插入-删除时,运行提供的脚本并传递一些参数。如果你想要更具体,你可以使用udevadm info——name=<设备名>来查找其他可以过滤设备的参数。您可以测试这里建议的规则。要应用这些规则:

root@~$ udevadm control --reload 

现在我们需要以超级用户(sudo)的身份在/usr/local/bin/ docker_ty .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_ty .log文件,以获得主机上的一些调试输出,以及这里建议的调试bash脚本。

别忘了让脚本可执行:

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

现在连接到docker,看看当你插入和拔出它时,设备是否出现在/dev/目录中:

docker exec -it my_container bash

还有一种更简单的共享usb设备的方法,没有——privileged标志。这样做:

Docker运行…——device=/dev/bus/usb——device=/dev/usb <container> .使用实例