在Docker中访问主机目录的权限被拒绝

我试图在Docker中挂载一个主机目录,但然后我不能从容器中访问它,即使访问权限看起来不错。

我正在做

sudo docker run -i -v /data1/Downloads:/Downloads ubuntu bash

然后

ls -al

它给我:

total 8892
drwxr-xr-x.  23 root root    4096 Jun 18 14:34 .
drwxr-xr-x.  23 root root    4096 Jun 18 14:34 ..
-rwxr-xr-x.   1 root root       0 Jun 18 14:34 .dockerenv
-rwx------.   1 root root 9014486 Jun 17 22:09 .dockerinit
drwxrwxr-x.  18 1000 1000   12288 Jun 16 11:40 Downloads
drwxr-xr-x.   2 root root    4096 Jan 29 18:10 bin
drwxr-xr-x.   2 root root    4096 Apr 19  2012 boot
drwxr-xr-x.   4 root root     340 Jun 18 14:34 dev
drwxr-xr-x.  56 root root    4096 Jun 18 14:34 etc
drwxr-xr-x.   2 root root    4096 Apr 19  2012 home

还有很多这样的台词(我认为这是相关的部分)。

如果我这样做

cd /Downloads
ls

结果是

ls: cannot open directory .: Permission denied

主机版本为Fedora 20, Docker 1.0.0, go1.2.2。

哪里出了问题?

419619 次浏览

这是一个SELinux问题。

你可以临时发行

su -c "setenforce 0"

在主机上访问或通过运行添加SELinux规则

chcon -Rt svirt_sandbox_file_t /path/to/volume

警告:此解决方案存在安全风险。

尝试以特权模式运行容器:

sudo docker run --privileged=true -i -v /data1/Downloads:/Downloads ubuntu bash

另一种选择(我没有尝试过)是创建一个特权容器,然后在其中创建非特权容器。

我验证了chcon -Rt svirt_sandbox_file_t /path/to/volume工作,你不必作为一个特权容器运行。

这是:

  • Docker版本0.11.1-dev, build 02d20af/0.11.1
  • CentOS 7作为主机和容器,启用SELinux。

access.redhat.com Sharing_Data_Across_Containers:

主机卷设置是不可移植的,因为它们依赖于主机,可能无法在任何其他机器上工作。 由于这个原因,Dockerfile中不存在将主机目录挂载到容器的等效文件。另外,要注意主机系统不了解容器SELinux策略。因此,如果强制执行SELinux策略,则无论rw设置如何,挂载的主机目录都不能写入容器。目前,您可以通过将适当的SELinux策略类型分配给主机目录quot;:

来解决这个问题

chcon -Rt svirt_sandbox_file_t host_dir

其中host_dir是主机系统上挂载到容器的目录的路径。

这似乎只是一种变通办法,但我试过了,而且奏效了。

完整的故事见这篇关于Volumes和SELinux的Project Atomic博客文章

具体地说:

自从Docker最终合并了一个补丁后,这变得更容易了 将在docker-1.7中出现(我们已经把补丁带进来了 docker-1.6在RHEL、CentOS和Fedora上).

这个补丁增加了对&;z&;和“;Z"作为卷上的选项 坐骑(- v) . < / p >

例如:

docker run -v /var/db:/var/db:z rhel7 /bin/sh

将自动执行chcon -Rt svirt_sandbox_file_t /var/db

.详细描述

更好的是,你可以用Z。

docker run -v /var/db:/var/db:Z rhel7 /bin/sh
这将用确切的MCS标记容器内的内容 容器将运行的标签,基本上它运行chcon -Rt svirt_sandbox_file_t -l s0:c1,c2 /var/db,其中s0:c1,c2不同 每个容器。< / p >

我通过使用数据容器解决了这个问题。这还具有将数据与应用层隔离的优点。你可以这样运行:

docker run --volumes-from=<container-data-name> ubuntu

本教程对数据容器的使用提供了很好的解释。

docker volume create试试。

mkdir -p /data1/Downloads
docker volume create --driver local --name hello --opt type=none --opt device=/data1/Downloads --opt o=uid=root,gid=root --opt o=bind
docker run -i -v hello:/Downloads ubuntu bash

看一下文档docker volume create

我也遇到过类似的问题。我的错误是由于主机的UID和容器用户的UID不匹配造成的。解决方法是将用户的UID作为参数传递给docker build命令,并使用相同的UID创建容器的用户。

在DockerFile中:

ARG UID=1000
ENV USER="ubuntu"
RUN useradd -u $UID -ms /bin/bash $USER

在构建步骤中:

docker build <path/to/Dockerfile> -t <tag/name> --build-arg UID=$UID

在那之后,根据OP运行容器和命令给了我预期的结果。

在我的情况下,问题就不同了。我不知道为什么,但即使主机上的一个目录上运行了chmod 777,在Docker容器中,它也是可见的755

在容器sudo chmod 777 my_volume_dir内运行修复了它。

通常,主机卷挂载的权限问题是因为容器内的UID/GID根据该文件在主机上的UID/GID权限不能访问该文件。然而,这一具体情况有所不同。

权限字符串末尾的点drwxr-xr-x.表示配置了SELinux。在SELinux中使用主机挂载时,您需要将一个额外的选项传递到卷定义的末尾:

  • z选项表示绑定挂载内容在多个容器之间共享。
  • Z选项表示绑定挂载内容是私有且非共享的。

你的卷挂载命令看起来像这样:

sudo docker run -i -v /data1/Downloads:/Downloads:z ubuntu bash

有关SELinux主机挂载的更多信息,请参阅配置 selinux label


对于以不同用户运行容器时遇到此问题的其他人,您需要确保容器内用户的UID/GID对主机上的文件具有权限。在生产服务器上,这通常是通过在映像构建过程中控制UID/GID来实现的,以匹配能够访问文件的主机上的UID/GID(或者更好的是,在生产中不使用主机挂载)。

命名卷通常优于主机挂载,因为它将从映像目录初始化卷目录,包括任何文件所有权和权限。这种情况发生在卷为空且容器是用命名卷创建的情况下。

macOS用户现在有OSXFS自动处理Mac主机和容器之间的UID/ gid。它不能帮助的一个地方是来自嵌入式虚拟机内部被挂载到容器中的文件,比如/var/lib/docker.sock。

对于每个开发人员的主机UID/GID可能会改变的开发环境,我的首选解决方案是启动容器时使用一个作为根运行的入口点,在容器中固定用户的UID/GID以匹配主机卷UID/GID,然后使用gosu从根删除到容器用户,以便在容器中运行应用程序。这方面的重要脚本是我的基本图像脚本中的fix-perms,可以在:Docker Base image from Brandon Mitchell .中找到

fix-perms脚本的重要位是:

# Update the UID
if [ -n "$opt_u" ]; then
OLD_UID=$(getent passwd "${opt_u}" | cut -f3 -d:)
NEW_UID=$(stat -c "%u" "$1")
if [ "$OLD_UID" != "$NEW_UID" ]; then
echo "Changing UID of $opt_u from $OLD_UID to $NEW_UID"
usermod -u "$NEW_UID" -o "$opt_u"
if [ -n "$opt_r" ]; then
find / -xdev -user "$OLD_UID" -exec chown -h "$opt_u" {} \;
fi
fi
fi

它获取容器内用户的UID和文件的UID,如果不匹配,调用usermod来调整UID。最后,它进行递归查找以修复没有更改uid的任何文件。与运行带有-u $(id -u):$(id -g)标志的容器相比,我更喜欢这样做,因为上面的入口点代码不需要每个开发人员运行脚本来启动容器,并且卷之外的任何由用户拥有的文件都将更正其权限。


您还可以让Docker通过使用一个执行绑定挂载的命名卷来从映像初始化一个主机目录。这个目录必须已经存在,并且您需要提供到主机目录的绝对路径,而不像组合文件中的主机卷可以是相对路径。该目录也必须为空,以便Docker对其进行初始化。定义命名卷到绑定挂载的三个不同选项如下:

  # create the volume in advance
$ docker volume create --driver local \
--opt type=none \
--opt device=/home/user/test \
--opt o=bind \
test_vol


# create on the fly with --mount
$ docker run -it --rm \
--mount type=volume,dst=/container/path,volume-driver=local,volume-opt=type=none,volume-opt=o=bind,volume-opt=device=/home/user/test \
foo


# inside a docker-compose file
...
volumes:
bind-test:
driver: local
driver_opts:
type: none
o: bind
device: /home/user/test
...

最后,如果尝试使用用户名称空间,您将发现主机卷存在权限问题,因为容器的UID/ gid发生了移位。在这种情况下,可能最容易避免使用主机卷,而只使用命名卷。

这个问题是因为在您的机器上启用了SELinux。检查下面并禁用它。

[root@nfs-server ~]# getenforce
Enforcing
[root@nfs-server ~]# setenforce 0
[root@nfs-server ~]# getenforce
Permissive

如果你只是想禁用SELinux,你可以使用——security-opt标签:禁用标志来做到这一点。

docker run --security-opt label:disable -v /run/docker.sock:/run/docker.sock POWERFULLCONTAINER