理解Docker中的用户文件所有权:如何避免更改链接卷的权限

100

考虑以下简单的Dockerfile:

FROM debian:testing
RUN  adduser --disabled-password --gecos '' docker
RUN  adduser --disabled-password --gecos '' bob 

在仅有工作目录的情况下。构建docker镜像:
docker build -t test .

然后在容器中运行bash脚本,将工作目录链接到Bob的主目录下的一个新子目录中:
docker run --rm -it -v $(pwd):/home/bob/subdir test 

谁拥有容器中 subdir 的内容? 在容器中运行以下命令:

cd /home/bob/subdir
ls -l

我们看到:

-rw-rw-r-- 1 docker docker 120 Oct 22 03:47 Dockerfile

天哪!docker拥有这些内容!回到容器外的主机上,我们可以看到我们原来的用户仍然拥有Dockerfile。让我们尝试修复bob的主目录的所有权。在容器中运行:

chown -R bob:bob /home/bob
ls -l 

并且我们看到:

-rw-rw-r-- 1 bob bob 120 Oct 22 03:47 Dockerfile

等等!容器外,我们现在运行 ls -l

-rw-rw-r-- 1 1001 1001 120 Oct 21 20:47 Dockerfile

我们不再拥有我们自己的文件。可怕的消息!


如果我们在上面的例子中只添加了一个用户,一切都会更加顺利。由于某种原因,Docker似乎会让任何一个家目录都归属于第一个非root用户(即使该用户在较早的镜像中已声明)。同样地,这个第一个用户对应于与我的本地用户相同的所有权权限。

问题1:这是正确的吗? 能否有人指向相关文档,我只是根据上述实验推测出来的。

问题2:也许这只是因为它们在内核中具有相同的数字值,在我的主机用户不是id 1000的系统上测试,则权限将在每种情况下都被更改?

问题3:当然,真正的问题是“我该怎么做?”,如果bob在给定的主机上作为bob登录,则他应该能够以bob身份运行容器,而不会在其主机帐户下修改文件权限。按照现在的情况,他必须将容器作为用户docker运行,以避免更改他的帐户。

我听到你问“为什么我的Dockerfile如此奇怪?”。有时候我也在想。我正在编写一个Web应用程序(RStudio-server)的容器,允许不同的用户登录,其中只需要使用来自Linux机器的用户名和凭据作为有效用户名。这给了我创造多个用户的或许不寻常的动因。我可以通过在运行时仅创建用户来绕过这个问题,并且一切都很好。但是,我使用了一个已添加了单个docker用户的基本镜像,以便它可以交互式地使用而不必作为root运行(符合最佳实践)。这破坏了一切,因为该用户成为第一个用户并拥有所有内容,因此尝试以其他用户身份登录失败(应用程序无法启动因为缺少写权限)。将启动脚本首先运行chown可以解决此问题,但成本是链接卷更改权限(如果我们链接卷,则显然是一个问题)。


如果在/etc/passwd中将1001映射为bob,则表示容器未挂载/etc/password。在内核级别,进程上下文仅携带ID/编号而不是名称。 - resultsway
参见:http://unix.stackexchange.com/a/154092/43610 - Ryne Everett
3个回答

81

我找到了两种选项:

修改所有权(在完成工作后)

我已经执行了 docker run -v `pwd`/shared:/shared image,容器已经在pwd/shared中创建了文件,这些文件现在归Docker进程所有。然而,/shared仍然是属于我的。所以在Docker进程中,我执行以下命令:

chown -R `stat -c "%u:%g" /shared` /shared

stat -c "%u:%g" /shared 在我的情况下返回 1000:1000,也就是我的用户的uid:gid。虽然Docker容器中没有用户1000,但该ID确实存在(如果您要求用户名,则stat /shared会显示 "unknown")。

无论如何,chown 忠实地将/shared 的内容的所有权转移给1000:1000(在它看来,1000:1000 不是真正存在的,但在容器外部,它就是我)。因此,我现在拥有所有文件。容器仍然可以修改文件,因为从其角度来看,它是root

一切都好了。

docker run -u,这样所有创建的文件都会自动拥有正确的所有者

另一种方法是在docker run上使用-u标志。

docker run -v `pwd`/shared:/shared -u `stat -c "%u:%g" /shared` ubuntu bash

这样,容器内部的Docker用户就是 youruid:yourgid

但是:这意味着放弃您在容器中的root权限(例如,apt-get install 等)。除非您创建一个具有新uid的用户并将其添加到root组。


1
是的,我也倾向于大量使用 chown。 我也倾向于使用 -u 1000。 我认为使用 %u 没有太大帮助,因为如果您的用户在容器中具有不存在的 UID,则会出现错误。 - cboettig
它并不会真的出错,只是向 stderr 抱怨 :) - Jared Forsyth
请注意,由于Docker的工作方式,通过-u选项传递到Docker中的UIDGID必须在Docker容器本身内存在(即与其关联的名称不是必须的)。 - code_dredd
这个在 podman 中怎么实现? - user190245

35

这是正确的吗?有人能提供相关文档给我参考吗?我只是根据上面的实验推测。

也许这只是因为它们在内核上具有相同的数值,如果我在一个我的家目录用户不是id 1000的系统上进行测试,则权限将在每种情况下都会更改?

阅读一下 info coreutils 'chown invocation' ,那可能会更好地了解文件权限/所有权的工作原理。

基本上,你的机器上的每个文件都附加有一组位来定义它的权限和所有权。当你使用chown命令更改文件所有者时,实际上是设置这些位。

当你使用用户名或组名将一个文件的所有权更改为特定的用户/组时,chown命令将在/etc/passwd中查找用户名,在/etc/group中查找组名,试图将名称映射到ID。如果用户名/组名不存在于这些文件中,chown命令将失败。

root@dc3070f25a13:/test# touch test
root@dc3070f25a13:/test# ll
total 8
drwxr-xr-x  2 root root 4096 Oct 22 18:15 ./
drwxr-xr-x 22 root root 4096 Oct 22 18:15 ../
-rw-r--r--  1 root root    0 Oct 22 18:15 test
root@dc3070f25a13:/test# chown test:test test
chown: invalid user: 'test:test'

然而,您可以使用ID将文件的所有权更改为任何您想要的内容(当然,这在某些正整数上限范围内),无论您的计算机上是否存在具有这些ID的用户/组。

root@dc3070f25a13:/test# chown 5000:5000 test
root@dc3070f25a13:/test# ll
total 8
drwxr-xr-x  2 root root 4096 Oct 22 18:15 ./
drwxr-xr-x 22 root root 4096 Oct 22 18:15 ../
-rw-r--r--  1 5000 5000    0 Oct 22 18:15 test

UID和GID位设置在文件本身上,因此当您将这些文件挂载到docker容器内时,文件的所有者/组UID与主机上的相同,但现在映射到容器中的/etc/passwd,除非它由root(UID 0)拥有,否则可能会映射到不同的用户。

当然,真正的问题是:“我该怎么办?”如果bob作为给定主机上的bob登录,则他应该能够将容器作为bob运行,并且不会在主机帐户下更改文件权限。目前,他实际上需要以用户docker运行容器,以避免更改他的帐户。

看起来,根据您当前的设置,如果您想要以与主机上已登录的用户相同的用户交互来处理安装的用户目录,您需要确保主机上的/etc/passwd中的UID>用户名与容器中的UID>用户名相匹配。

您可以使用useradd -u xxxx创建具有特定用户ID的用户。但是,那似乎是一个混乱的解决方案...

您可能需要考虑一种不挂载主机用户主目录的解决方案。


感谢您提供详细的答案,这确实听起来是正确的解释。但正如您所说,这并不是特别令人满意的解决方案—— Docker 没有更明确地解决这个问题,这有点令人惊讶。在主机用户的主目录中不挂载任何内容似乎非常受限制。 - cboettig
我正在阅读这篇文章,意识到这个人解决了你上面提出的问题。也许这并不是那么糟糕? - Chris McKinnel
谢谢链接。看起来该方法将UID硬编码(在他的情况下,无论如何都是默认值),因此问题仍会出现(例如,容器在没有用户预配置和构建Dockerfile的情况下不可移植)。如果Docker支持名称而不是UID值,则此问题确实会消失。有关将其发布到docker google组的更多信息,请参考Docker开发人员的信息:https://groups.google.com/forum/#!topic/docker-user/VFdFuZ4Ze_A - cboettig
1
是的,我想这实际上并不是一个坏策略。不过需要先用 userdel 释放 UID。顺便提一下,这是我在实际的 Dockerfile 中采取的解决方案:https://github.com/rocker-org/rocker/issues/50#issuecomment-60306104 - cboettig

11

因此,我最终在这篇文章中寻找如何将所有由 root 拥有的文件(来自以 root 身份运行的 docker 容器)的所有权恢复到主机上的非特权用户。

我需要容器内的进程以 root 身份运行,因此无法在 docker run 上使用 -u。

我对自己的所作所为并不感到骄傲,但在我的 bash 脚本末尾,我添加了以下内容:

docker run --rm -it \
    --entrypoint /bin/sh \
    -e HOST_UID=`id -u` \
    -v ${HOST_FOLDER_OWNED_BY_ROOT}:/tmp \
    alpine:latest \
    -c 'chown -R ${HOST_UID}:${HOST_UID} /tmp/'

我们来分解一些行:

  • 在容器内运行 /bin/sh:

--entrypoint /bin/sh

  • 将当前用户的uid作为环境变量传递给容器:

-e HOST_UID=`id -u`

  • 将您想要重新拥有的任何文件夹(填充了以root用户拥有的文件,由上一个作为root运行的容器输出),挂载到这个新容器的 /tmp 下:

-v ${HOST_FOLDER_OWNED_BY_ROOT}:/tmp

  • 使用主机用户的uid在目标目录(在容器中挂载在/tmp下)上运行递归的 chown 命令:

-c 'chown -R ${HOST_UID}:${HOST_UID} /tmp/'

这样,我就可以让文件重新属于我的当前用户,而不必提升root或sudo权限。

虽然有点危险,但它起作用了。希望能对你有所帮助。


1
谢谢你救了我的一天 :) - Gigs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接