• docker挂载volume的用户权限问题,理解docker容器的uid


    docker挂载volume的用户权限问题,理解docker容器的uid
    目录
    遇到的问题
    原因
    容器共享宿主机的uid
    如果不指定user,容器内部默认使用root用户来运行
    容器内部用户的权限与外部用户相同
    一定要确保容器执行者的权限和挂载数据卷对应
    一个更加明显的demo
    参考
    docker挂载volume的用户权限问题,理解docker容器的uid

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

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

    sudo docker run -it --rm --name ryan
    -v pwd:pwd 
    -w pwd node
    npm install --registry=https://registry.npm.taobao.org

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

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

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

    That’s because the username (and group names) that show up in common linux tools aren’t part of the kernel, but are managed by external tools (/etc/passwd, LDAP, Kerberos, etc). So, you might see different usernames, but you can’t have different privileges for the same uid/gid, even inside different containers

    如果不指定user,容器内部默认使用root用户来运行
    我们继续使用node镜像, 你可以在github查看Dockerfile. 里面创建了一个
    uid为1000的用户node,但没指定运行user。

    docker run -d --rm --name ryan node sleep infinity
    我执行的用户为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:

    docker run -d --rm --name test -u 1000:1000 -v $(pwd):/tmp node sleep infinity

    可以看到, 容器外执行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
    上面的demo恰好宿主机器和容器都存在一个uid=1000的用户,于是很和谐的实现了文件权限共享。接下来测试一个更加明显的demo。

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

    docker run -d --rm --name demo -u 1111:1111 -v $(pwd):/tmp node sleep infinity

    当前数据卷有文件a和dir any_user. 文件a归属与uid=1000, dir any_user任何人可以写
    运行容器,并以uid=1111执行
    登录容器内部,查看数据卷,发现文件a和dir any_user都归属于uid=1000的node(uid映射)
    由于容器内部没有uid=1111的用户,所以显示I have no name!, 没有username,没有home。
    在容器内部执行数据卷的写操作,提示没权限。(因为数据卷的权限是uid=1000)
    在容器内部写入一个文件到公共数据区(777).
    接下来看看容器外的表现:

    数据文件确实有被写入,内容可读
    容器写入的文件的权限都是1111的uid。由于宿主机没有这个用户,直接显示uid
    查看进程,可以发现容器的进程也是1111
    即-u指定容器内部执行的用户,以及容器外在宿主机进程的用户,同样容器写到数据卷的权限也由此指定。

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

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

    参考
    Understanding how uid and gid work in Docker containers
    理解 docker 容器中的 uid 和 gid

    本文作者:@Ryan Miao
    本文链接:https://www.cnblogs.com/woshimrf/p/understand-docker-uid.html

  • 相关阅读:
    准备工作
    使用awstats分析nginx日志
    kvm虚拟化环境中的时区设置
    使用awk格式化输出文本
    gitlab(7.9)升级到8.0.1
    为openstack制作CoreOS虚拟机镜像(基于CoreOS官方提供镜像)
    KVM虚拟化之嵌套虚拟化nested
    编译制作Linux 3.18内核rpm包(升级centos6.x虚拟机内核)
    Linux主机之间传输文件的几种方法对比
    spice在桌面虚拟化中的应用系列之二(Linux平台spice客户端的编译安装,支持USB映射)
  • 原文地址:https://www.cnblogs.com/zhengchunyuan/p/11990840.html
Copyright © 2020-2023  润新知