docker挂载volume的用户权限问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了docker挂载volume的用户权限问题相关的知识,希望对你有一定的参考价值。

参考技术A

在刚开始使用docker volume挂载数据卷的时候,经常出现没有权限的问题。
这里通过遇到的问题来理解docker容器用户uid的使用,以及了解容器内外uid的映射关系。

本地有一个node的项目需要编译,采用docker来run npm install.

可以看到,install之后,node_modules文件的权限变成root了。那么,作为使用者的我们就没有权限去删除这个文件了。

为什么docker输出的文件权限会是root?

Docker容器运行的时候,如果没有专门指定user, 默认以root用户运行。我们的node镜像的 Dockerfile 里没有指定user.

容器里的执行用户的id是0,输出文件的权限也是0.

以下参考 Understanding how uid and gid work in Docker containers

首先了解uid,gid的实现 。Linux内核负责管理uid和gid,并通过内核级别的系统调用来决定是否通过请求的权限。
比如,当一个进程尝试去写文件,内核会检查创建这个进程的的user的uid和gid,来决定这个进程是否有权限修改这个文件。
这里没有使用username,而是uid。

当docker容器运行在宿主机上的时候,仍然只有一个内核。容器共享宿主机的内核,所以所有的uid和gid都受同一个内核来控制。

那为什么我容器里的用户名不一定和宿主内核一样呢? 比如,superset容器的用户叫做superset, 而本机没有superset这个用户。这是因为username不是Linux kernel的一部分。简单的来说,username是对uid的一个映射。
然而,权限控制的依据是uid,而不是username。

我们继续使用node镜像, 你可以在 github 查看Dockerfile. 里面创建了一个
uid为1000的用户node,但没指定运行user。

我执行的用户为ryan(uid=1000), 让容器后台执行sleep程序。

可以看到,容器外执行sleep的进程的用户是root。容器内部的用户也是0(root). 虽然执行docker run的用户是ryan .

也就是说,我一个普通用户居然可以以root的身份去执行一个命令。看起来挺恐怖的样子。

权限是通过uid来判断的。接下来测试,相同uid的用户可以修改归属于这个uid的文件。

宿主机有一个用户ryan:

刚才使用的node镜像的Dockerfile也定义了1000的用户node:

我们在本地写一个文件a, 归属用户ryan

然后,通过volume挂载的方式,指定运行user为1000, 启动容器node:

可以看到, 容器外执行sleep的进程,user是ryan(另一个sleep进行是前面的root用户执行的实例,没删除)。
即, docker run -u 可以指定宿主机运行docker命令的用户, -u指定的uid就是docker实际运行的进程拥有者

接下来去容器内部,看看能不能修改挂载的文件。

可以看到,我们挂载的文件a在容器内部显示owner是node,即uid=1000的用户。并且有权限查看和修改。
然后,我们写一个文件b,在容器内部,这个b自然属于uid=1000的node。来看看容器外:

同样的,容器外显示b从属于uid=1000的用户ryan,并且有权限查看和修改。

如此,可以证明容器内外共享uid和对应的权限。

本文最初的问题就是因为容器执行者和挂载数据卷的权限不同。容器内部运行是uid=0的用户,数据卷从属与uid=1000的ryan。最终导致容器写入数据卷的文件权限升级为root, 从而普通用户无法访问。

如果挂载了root的文件到容器内部,而容器内部执行uid不是0,则报错没有权限。我在挂载npm cache的时候遇到了这个问题,于是有了本文。

上面的demo恰好宿主机器和容器都存在一个uid=1000的用户,于是很和谐的实现了文件权限共享。接下来测试一个更加明显的demo。

宿主机器和容器都没有uid=1111, 我们以1111来执行容器:

接下来看看容器外的表现:

即-u指定容器内部执行的用户,以及容器外在宿主机进程的用户,同样容器写到数据卷的权限也由此指定。

如此,这个demo更容易理解容器内外的uid的对应关系。理解了以后我们挂载数据卷的时候就不会出现权限问题了。

由于安全问题,通常也是建议不用使用root来运行容器的。

docker查看挂载目录Volume

使用docker inspect命令查看container的volume信息,按照书本上面敲,发现一直报错:

使用命令如下:

sudo docker inspect --format "{{.Volumes}}" 676b04bec7c5

 错误信息为

Template parsing error: template: :1:2: executing "" at <.Volumes>: map has no entry for key "Volumes"

然后网上找到一条类似的错误信息,原来是现在命令方式改了.

新的查看方式如下,Volumes前面加上了.Config,注意有点啊,刚开始作者粗心大意漏了. 

sudo docker inspect --format "{{.Config.Volumes}}" 

 

显示结果正常: 

map[/data:{}]

  

 技术分享图片

 

此处显示的信息只有docker 容器本地数据卷的信息,与其关联的物理主机的挂载目录位置信息,要使用如下信息查看:

docker inspect -f "{{.Mounts}}" 676b04bec7c5

#显示结果
[{volume f06e4ee059b5e78feb957b5d2fcefe7287309fa43cef4b431c22fbac5c178861 /var/lib/docker/volumes/f06e4ee059b5e78feb957b5d2fcefe7287309fa43cef4b431c22fbac5c178861/_data /data local  true }]
技术分享图片

 其实此处可以通过上面提供报错链接的文章的后面解决,但是当初作者没注意,忽略了.可以直接查看对应的挂载地址,也能达到我们的目的

命令如下

docker inspect redis-master | grep Mounts -A 10

  

#对应的 docker inspect 信息数组格式如下:
"Mounts": [
            {
                "Type": "volume",
                "Name": "f06e4ee059b5e78feb957b5d2fcefe7287309fa43cef4b431c22fbac5c178861",
                "Source": "/var/lib/docker/volumes/f06e4ee059b5e78feb957b5d2fcefe7287309fa43cef4b431c22fbac5c178861/_data",
                "Destination": "/data",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }

  技术分享图片

 

 

以上是关于docker挂载volume的用户权限问题的主要内容,如果未能解决你的问题,请参考以下文章

Docker:使用 --volume 绑定挂载的文件权限

解决 Docker 数据卷挂载的文件权限问题

k8s文件挂载权限分析

在docker挂载卷中管理nginx vhost文件权限

docker 启动 jenkins 挂载目录权限问题 Permission denied

Docker Volume数据卷