通常,主机卷挂载的权限问题是因为容器内的UID/GID不能根据主机上文件的UID/GID权限访问该文件。然而,这一具体情况有所不同。
权限字符串末尾的点,drwxr-xr-x。,表示配置了SELinux。在SELinux中使用主机挂载时,您需要将一个额外的选项传递到卷定义的末尾:
z选项表示绑定挂载内容在多个容器之间共享。
Z选项表示绑定挂载内容是私有的、非共享的。
你的卷挂载命令看起来像这样:
sudo docker run -i -v /data1/Downloads:/Downloads:z ubuntu bash
有关使用SELinux的主机挂载的更多信息,请参见配置SELinux标签。
对于以不同用户运行容器时遇到此问题的其他人,您需要确保容器内用户的UID/GID对主机上的文件具有权限。在生产服务器上,这通常是通过在映像构建过程中控制UID/GID来实现的,以匹配能够访问文件的主机上的UID/GID(或者更好的是,在生产中不使用主机挂载)。
命名卷通常优于主机挂载,因为它将从映像目录初始化卷目录,包括任何文件所有权和权限。这种情况发生在卷为空且容器是用命名卷创建的情况下。
macOS用户现在有OSXFS自动处理Mac主机和容器之间的UID/ gid。它不能帮助的一个地方是来自嵌入式虚拟机内部被挂载到容器中的文件,比如/var/lib/docker.sock。
对于每个开发人员都可能更改主机UID/GID的开发环境,我的首选解决方案是启动容器时使用一个以root身份运行的入口点,在容器中固定用户的UID/GID以匹配主机卷UID/GID,然后使用gosu从根删除到容器用户,以便在容器中运行应用程序。这个重要的脚本是我的基本映像脚本中的fix-perms,可以在:Docker base Images 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发生了移位。在这种情况下,可能最容易避免使用主机卷,而只使用命名卷。