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


当前回答

如果你想在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

其他回答

另一种选择是调整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文件夹中的设备的符号链接。此文件夹中的文件将始终命名相同。

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

在没有-特权模式的情况下安全正确地访问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

有几个选择。你可以使用——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

我们很难将特定的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

也许会有帮助。