Docker和--userns-remap,如何管理卷权限以在主机和容器之间共享数据?


96

在docker中,从主机检查容器时,在容器内部创建的文件往往具有不可预测的所有权。默认情况下,卷上文件的所有者是root(uid 0),但是一旦非root用户帐户包含在容器中并写入文件系统,从主机的角度来看,所有者就会或多或少地变得随机。

当您需要使用调用docker命令的同一用户帐户从主机访问卷数据时,这是一个问题。

典型的解决方法是

  • 在创建时在Dockerfile中强制用户uID(非便携式)
  • 将主机用户的UIDdocker run作为环境变量传递给命令,然后chown在入口点脚本中在卷上运行某些命令。

这两种解决方案都可以对容器外部的实际权限进行一些控制。

我希望用户名称空间是此问题的最终解决方案。我已经对最近发布的1.10版和--userns-remap设置为我的桌面帐户进行了一些测试。但是,我不确定它是否会使挂载卷上的文件所有权更容易处理,恐怕实际上相反。

假设我启动这个基本容器

docker run -ti -v /data debian:jessie /bin/bash
echo 'hello' > /data/test.txt
exit

然后检查主机中的内容:

ls -lh /var/lib/docker/100000.100000/volumes/<some-id>/_data/

-rw-r--r-- 1 100000 100000 6 Feb  8 19:43 test.txt

这个数字“ 100000”是我的主机用户的一个子UID,但是由于它不对应于我的用户的UID,因此我仍然无法在没有特权的情况下编辑test.txt。该子用户似乎与docker之外的我的实际普通用户没有任何亲和力。它没有被映射回来。

这篇文章前面提到的变通办法是在主机和容器之间对齐UID,由于 UID->sub-UID名称空间中发生映射,。

然后,是否有一种方法可以在启用用户命名空间的情况下运行docker(以提高安全性),同时仍使运行docker的主机用户拥有在卷上生成的文件的可能性?


我认为,如果要在主机和容器之间共享卷,则用户名称空间将不会成为解决方案的一部分。您的第二种选择(“将主机用户的UID作为环境变量传递给docker run命令,然后在入口点脚本中的卷上运行一些chown命令”)可能是最好的解决方案。
larsk's

4
Docker本身似乎并不鼓励使用主机安装的可写卷。由于我没有运行云服务,而仅使用自己的受信任映像,所以我现在想知道用户NS的安全性是否值得牺牲这么多便利。
斯特凡C.

@StéphaneC。您是否找到了更好的方法?
'88

4
不幸的是,不,不使用用户名称空间和从主机传递UID仍然是我的选择。我希望将来会有适当的方式来映射用户。我对此表示怀疑,但我仍然睁大眼睛。
斯特凡C.

Answers:


46

如果可以预先安排用户和组,则可以以特定的方式分配UID和GID,以便主机用户与容器内的命名空间用户相对应。

这是一个示例(Ubuntu 14.04,Docker 1.10):

  1. 创建一些具有固定数字ID的用户:

    useradd -u 5000 ns1
    
    groupadd -g 500000 ns1-root
    groupadd -g 501000 ns1-user1
    
    useradd -u 500000 -g ns1-root ns1-root
    useradd -u 501000 -g ns1-user1 ns1-user1 -m
    
  2. /etc/subuid/etc/subgid文件中手动编辑自动生成的下级ID范围:

    ns1:500000:65536
    

    (注意没有用于记录ns1-rootns1-user1MAX_UIDMAX_GID限制中/etc/login.defs

  3. 在中启用用户名称空间/etc/default/docker

    DOCKER_OPTS="--userns-remap=ns1"
    

    重新启动守护程序service docker restart,确保/var/lib/docker/500000.500000目录已创建。

    现在,在容器内您拥有rootand user1,并且在主机上-ns1-rootns1-user1,具有匹配的ID

    更新:为了确保非root用户在容器中具有固定的ID(例如,user1 1000:1000),请在映像构建期间显式创建它们。

试驾:

  1. 准备卷目录

    mkdir /vol1
    chown ns1-root:ns1-root /vol1
    
  2. 从容器中尝试

    docker run --rm -ti -v /vol1:/vol1 busybox sh
    echo "Hello from container" > /vol1/file
    exit
    
  3. 从主机尝试

    passwd ns1-root
    login ns1-root
    cat /vol1/file
    echo "can write" >> /vol1/file
    

不便携,看起来像黑客,但是可以工作。


3
非常有趣,值得+1。但是,您仍然需要确保为映像中的user1分配了UID1000。否则,不能确定它将在主机上收到UID 501000。顺便说一句,subUID lower bound + UID in image如果我们运行许多不同的图像且用户的ID设置为1000,我们是否绝对确定公式始终有效?
斯特凡C.

@StéphaneC。好点子!添加了有关固定图像内部ID的注释。至于公式,我将用自己的图像做进一步的实验,如果发现任何问题,它将更新答案
amartynov

1
如果您在主机和容器中手动安排用户和组,您是否真的需要“用户名称空间”功能?
特里斯坦(Tristan)

1
您创建的名称空间将主机用户与容器用户分开,但是您可能需要容器的多个名称空间,尤其是当官方映像(例如mysql)创建没有显式uid的用户时。当--userns-remap选项仅需要一个名称空间时,如何处理多个名称空间?
特里斯坦,

2
@amartynov我想问一下为什么您要为您的“ ns1”用户指定UID(5000)吗?由于它是您在subuid和subgid文件中引用的名称(而不是UID),因此该用户获得的UID似乎无关紧要。我是否缺少某种关系,因为可能暗示5000和500000之间的相似性?
Jollymorphic '17

2

一种解决方法是在构建时动态分配用户的uid以匹配主机。

范例Dockerfile

FROM ubuntu
# Defines argument which can be passed during build time.
ARG UID=1000
# Create a user with given UID.
RUN useradd -d /home/ubuntu -ms /bin/bash -g root -G sudo -u $UID ubuntu
# Switch to ubuntu user by default.
USER ubuntu
# Check the current uid of the user.
RUN id
# ...

然后构建为:

docker build --build-arg UID=$UID -t mycontainer .

并运行为:

docker run mycontainer

如果您已有容器,请使用以下内容创建包装容器Dockerfile

FROM someexistingcontainer
ARG UID=1000
USER root
# This assumes you've the existing user ubuntu.
RUN usermod -u $UID ubuntu
USER ubuntu

可以docker-compose.yml像这样包装:

version: '3.4'
services:
  myservice:
    command: id
    image: myservice
    build:
      context: .
    volumes:
    - /data:/data:rw

然后构建并运行为:

docker-compose build --build-arg UID=$UID myservice; docker-compose run myservice

1
我不想显得不友好,但这实际上是原始问题中列出的解决方法之一,但不是解决方案,并且与用户名称空间无关。
斯特凡C.

@StéphaneC。您可以对这个相关问题发表评论吗?stackoverflow.com/questions/60274418/...
overexchange

-1

您可以通过使用docker cp命令来避免权限问题。

所有权设置为目的地的用户和主要组。例如,复制到容器的文件是使用UID:GIDroot用户创建的。复制到本地计算机上的文件是使用UID:GID调用docker cp命令的用户的创建的。

这是切换为使用的示例docker cp

$ docker run -ti -v /data debian:jessie /bin/bash
root@e33bb735a70f:/# echo 'hello' > /data/test.txt
root@e33bb735a70f:/# exit
exit
$ docker volume ls
DRIVER              VOLUME NAME
local               f073d0e001fb8a95ad8d919a5680e72b21a457f62a40d671b63c62ae0827bf93
$ sudo ls -l /var/lib/docker/100000.100000/volumes/f073d0e001fb8a95ad8d919a5680e72b21a457f62a40d671b63c62ae0827bf93/_data
total 4
-rw-r--r-- 1 100000 100000 6 Oct  6 10:34 test.txt
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
e33bb735a70f        debian:jessie       "/bin/bash"         About a minute ago   Exited (0) About a minute ago                       determined_hypatia
$ docker cp determined_hypatia:/data/test.txt .
$ ls -l test.txt 
-rw-r--r-- 1 don don 6 Oct  6 10:34 test.txt
$ cat test.txt
hello
$ 

但是,如果您只想从容器中读取文件,则不需要命名的卷。本示例使用命名容器而不是命名卷:

$ docker run -ti --name sandbox1 debian:jessie /bin/bash
root@93d098233cf3:/# echo 'howdy' > /tmp/test.txt
root@93d098233cf3:/# exit
exit
$ docker cp sandbox1:/tmp/test.txt .
$ ls -l test.txt
-rw-r--r-- 1 don don 6 Oct  6 10:52 test.txt
$ cat test.txt
howdy
$ 

本问题所述,当我想将文件复制到容器中时,我发现命名卷很有用。


但是docker cp涉及到复制数据。此外,根据该文档,将数据复制到容器时,它会根据根用户设置所有权ID,该用户通常不是运行容器化应用程序的帐户。我看不到它如何解决我们的问题。
斯特凡C.

没错,@Stéphane,它涉及到复制数据。但是,通过复制文件,您可以在主机和容器中分配不同的所有权和权限。docker cp当您将tar归档流传输到容器中或从容器中流出时,可以完全控制文件所有权。您可以在传输流文件时调整tar文件中每个条目的所有权和权限,因此您不仅限于root用户。
唐·柯比
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.