管理Docker共享卷权限的(最佳)方法是什么?

What is the (best) way to manage permissions for Docker shared volumes?

我和Docker一起玩了一段时间,在处理持久性数据时一直在寻找相同的问题。

我创建了我的Dockerfile并暴露了一个卷,或者使用--volumes-from在容器中装入一个主机文件夹。

我应该对主机上的共享卷应用哪些权限?

我可以想到两种选择:

  • 到目前为止,我已经为每个人提供了读/写权限,所以我可以从Docker容器中写入文件夹。

  • 将用户从主机映射到容器中,这样我可以分配更精细的权限。但不确定这是可能的,而且还没有找到太多关于它的信息。到目前为止,我所能做的就是以某个用户的身份运行容器:docker run -i -t -user="myuser" postgres,但是这个用户的uid与我的主机myuser不同,所以权限不起作用。另外,我不确定映射用户是否会带来一些安全风险。

还有其他选择吗?

你们如何处理这个问题?


更新:作为2016年- 03 - 0 docker 1.9.0 docker已命名的,其中只有容器取代-卷的数据。回答下面的链接,以及我的博客后,仍然具有价值意义的在想关于数据如何在docker则认为大但使用命名的模式描述implement卷下面而不是煤的数据容器。

我相信这是典型的由大的求解方法-只使用数据的容器。所有与本卷的方法,获得的数据通过使用的数据的容器,容器内-volumes-fromUID / GID寄主,所以没关系。

例如,在一个给定的用例文档是一个backing卷上的数据。本文的另一大原因是以前做的backup集装箱通过tar,使用顺序和它太大-volumes-from在芒特的卷。所以我认为关键点是:grok煤而不是想着如何获得访问的数据在适当的宿主与permissions,认为关于如何做你需要——backups,browsing,等——通过另一个集装箱。自己需要使用的容器gids UID /一致的,但他们并不需要的东西,在大地图portable寄主,thereby剩余。

这是比较新的,但如果你为我以及用例有一个特别的感觉自由的评论,我会尝试对expand回答。

更新:为给定的用例在评论,你可能有个大graphite some/graphite颤抖的图像,图像数据的安全和some/graphitedata作为容器。所以,ignoring波尔特和这样的图像,Dockerfilesome/graphitedata是什么,如:

1
2
3
4
5
6
7
8
9
FROM debian:jessie
# add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later
RUN groupadd -r graphite \
  && useradd -r -g graphite graphite
RUN mkdir -p /data/graphite \
  && chown -R graphite:graphite /data/graphite
VOLUME /data/graphite
USER graphite
CMD ["echo","Data container for graphite"]

建立和创建的数据容器:

1
2
docker build -t some/graphitedata Dockerfile
docker run --name graphitedata some/graphitedata

some/graphitedockerfile应该也得到相同的UID / gids,因此它可能看起来像这样的一件事:

1
2
3
4
5
6
7
8
FROM debian:jessie
# add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later
RUN groupadd -r graphite \
  && useradd -r -g graphite graphite
# ... graphite installation ...
VOLUME /data/graphite
USER graphite
CMD ["/bin/graphite"]

和它会运行如下:

1
docker run --volumes-from=graphitedata some/graphite

好吧,现在,我们给出了相关的数据和我们的graphite集装箱货柜的correct -只有与用户/组(注意,你可以重新使用的数据为some/graphite集装箱的集装箱以及,overriding的entrypoing / cmd当运行它,但让他们作为单独的图像imo是clearer)。

现在,你说你想让folder在编辑数据。而煤的体积比较大bind mounting寄主和编辑它那里,创造了一个新的容器做工作。some/graphitetools让调用它。也创造了让用户/组的合适的图像,就像some/graphite

1
2
3
4
5
6
7
FROM debian:jessie
# add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later
RUN groupadd -r graphite \
  && useradd -r -g graphite graphite
VOLUME /data/graphite
USER graphite
CMD ["/bin/bash"]

你能使这干燥由inheriting从some/graphitesome/graphitedata在dockerfile,而不是创造一个新的图像或只是重新使用现有的(其中overriding entrypoint / cmd作为必要的)。

现在,你当然发抖:

1
docker run -ti --rm --volumes-from=graphitedata some/graphitetools

然后vi /data/graphite/whatever.txt。这是因为所有的作品perfectly容器有相同的UID / GID与matching graphite用户。

因为你永远不/data/graphite芒特从寄主,你不在乎如何寄主UID / GID的UID / GID的地图定义的graphitegraphitetools容器里面。这些容器可以是任何deployed现在大的主机,和他们将perfectly继续工作。

关于这件事的neat是graphitetools可以有所有的和有用的sorts utilities scripts,那你也可以在一个portable现在部署远程的方式。

更新2:写作这个回答后,我决定写一个更大的完整的博客后,关于这种方法。我希望它helps。

更新3:我和添加更多的specifics纠正这个答案。这其中的一些不正确的assumptions以前关于ownership和perms——通常是在卷的ownership assigned创造时间即在集装箱的数据,因为这是当是创建的卷。看到这个博客。这是不是一个requirement虽然只是——你可以使用集装箱作为一个"参考的数据集的ownership /处理/ perms"和在另一个集装箱安全通过chown在entrypoint两端与gosu颤抖,其中大correct作为用户的命令。如果任何人有兴趣在这个方法是评论,请和我可以提供一个链接到样本的使用这种方法。


一个非常优雅的解决方案可以在官方的redis图像上看到,通常在所有的官方图像中都可以看到。

在逐步过程中描述:

  • 创建redis用户/组之前

如Dockerfile评论所示:

add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added

  • 用dockerfile安装gosu

gosu是susudo的替代方案,可以方便地从根用户进行逐步下降。(redis总是与redis用户一起运行)

  • 配置/data卷,设置为workdir

通过使用VOLUME /data命令配置/data卷,我们现在有了一个单独的卷,可以是docker卷,也可以绑定到主机目录。

将其配置为workdir(WORKDIR /data使其成为从中执行命令的默认目录。

  • 添加docker entrypoint文件,并使用默认的cmd redis server将其设置为entrypoint

这意味着所有容器执行都将通过docker entrypoint脚本运行,默认情况下,要运行的命令是redis server。

docker-entrypoint是一个执行简单功能的脚本:将当前目录的所有权(/data)更改,并从root降级到redis用户运行redis-server。(如果执行的命令不是redis server,它将直接运行该命令。)

这有以下效果

如果/data目录绑定到主机,docker入口点将在redis用户下运行redis server之前准备用户权限。

这让您可以轻松地记住,为了在任何卷配置下运行容器,都有零设置。

当然,如果您需要在不同的映像之间共享卷,您需要确保它们使用相同的userid/groupid,否则最新的容器将劫持前一个映像的用户权限。


在大多数情况下,这可能不是最好的方法,但还没有被提及,所以它可能会帮助一些人。

  • 绑定装载主机卷

    Host folder FOOBAR is mounted in container /volume/FOOBAR

  • 修改容器的启动脚本以查找您感兴趣的卷的gid

    $ TARGET_GID=$(stat -c"%g" /volume/FOOBAR)

  • 确保您的用户属于具有此gid的组(您可能需要创建一个新组)。在这个例子中,我将假装我的软件在容器中以nobody用户的身份运行,所以我想确保nobody属于组ID等于TARGET_GID的组。

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      EXISTS=$(cat /etc/group | grep $TARGET_GID | wc -l)

      # Create new group using target GID and add nobody user
      if [ $EXISTS =="0" ]; then
        groupadd -g $TARGET_GID tempgroup
        usermod -a -G tempgroup nobody
      else
        # GID exists, find group name and add
        GROUP=$(getent group $TARGET_GID | cut -d: -f1)
        usermod -a -G $GROUP nobody
      fi

    我喜欢这样,因为我可以很容易地修改主机卷上的组权限,并知道这些更新的权限应用于Docker容器中。这是在没有对主机文件夹/文件进行任何权限或所有权修改的情况下发生的,这让我很高兴。

    我不喜欢这样,因为它假定在容器中添加自己到一个任意组中没有危险,而这个组恰好使用了您想要的gid。它不能与dockerfile中的USER子句一起使用(除非我认为该用户具有根权限)。同时,它还尖叫着"黑客作业"—)

    如果你想成为硬核,你可以通过很多方式来扩展它——例如搜索任何子文件、多个卷等上的所有组。


    好的,现在在Docker发行版上进行跟踪7198

    现在,我用你的第二个选择来处理这个问题:

    Map the users from host into the container

    文档文件

    1
    2
    3
    4
    5
    6
    #=======
    # Users
    #=======
    # TODO: Idk how to fix hardcoding uid & gid, specifics to docker host machine
    RUN (adduser --system --uid=1000 --gid=1000 \
            --home /home/myguestuser --shell /bin/bash myguestuser)

    CLI

    1
    2
    # DIR_HOST and DIR_GUEST belongs to uid:gid 1000:1000
    docker run -d -v ${DIR_HOST}:${DIR_GUEST} elgalu/myservice:latest

    更新我现在更倾向于哈米的回答


    尝试向dockerfile添加命令

    1
    RUN usermod -u 1000 www-data

    学分转到https://github.com/denderello/symfony docker example/issues/2 issuecomment-94387272


    和你一样,我也在寻找一种方法将用户/组从主机映射到Docker容器,这是迄今为止我发现的最短的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      version:"3"
        services:
          my-service:
            .....
            volumes:
              # take uid/gid lists from host
              - /etc/passwd:/etc/passwd:ro
              - /etc/group:/etc/group:ro
              # mount config folder
              - path-to-my-configs/my-service:/etc/my-service:ro
            .....

    这是我的docker-compose.yml的摘录。

    其思想是将用户/组列表从主机装载到容器中(以只读模式),因此在容器启动后,它将具有与主机相同的uid->username(以及对于组)匹配。现在,您可以在容器内为服务配置用户/组设置,就像它在主机系统上工作一样。

    当您决定将容器移动到另一个主机时,只需将服务配置文件中的用户名更改为该主机上的用户名。


    这里的方法仍然使用一个只包含数据的容器,但不要求它与应用程序容器同步(就拥有相同的uid/gid而言)。

    大概,您希望以非根$用户身份运行容器中的某些应用程序,而不使用登录shell。

    在Dockerfile中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    RUN useradd -s /bin/false myuser

    # Set environment variables
    ENV VOLUME_ROOT /data
    ENV USER myuser

    ...

    ENTRYPOINT ["./entrypoint.sh"]

    然后,在entrypoint.sh中:

    1
    2
    chown -R $USER:$USER $VOLUME_ROOT
    su -s /bin/bash - $USER -c"cd $repo/build; $@"

    要安全并更改docker容器的根目录,请尝试使用--uidmap--private-uids选项。

    https://github.com/docker/docker/pull/4572问题注释-38400893

    此外,为了安全起见,您还可以删除Docker容器中的一些功能(--cap-drop)

    http://opensource.com/business/14/9/security-for-docker

    更新支持应该在docker > 1.7.0中提供。

    更新版本1.10.0(2016-02-04)增加--userns-remap标志https://github.com/docker/docker/blob/master/changelog.md安全-2


    基础图像

    使用此图片:https://hub.docker.com/r/reduardo7/docker-host-user

    重要提示:这会破坏跨主机的容器可移植性。

    1)江户十一〔0]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #!/bin/bash

    if ! getent passwd $DOCKDEV_USER_NAME > /dev/null
      then
        echo"Creating user $DOCKDEV_USER_NAME:$DOCKDEV_GROUP_NAME"
        groupadd --gid $DOCKDEV_GROUP_ID -r $DOCKDEV_GROUP_NAME
        useradd --system --uid=$DOCKDEV_USER_ID --gid=$DOCKDEV_GROUP_ID \
            --home-dir /home --password $DOCKDEV_USER_NAME $DOCKDEV_USER_NAME
        usermod -a -G sudo $DOCKDEV_USER_NAME
        chown -R $DOCKDEV_USER_NAME:$DOCKDEV_GROUP_NAME /home
      fi

    sudo -u $DOCKDEV_USER_NAME bash

    2)江户十一〔1〕号

    1
    2
    3
    4
    5
    6
    7
    FROM ubuntu:latest
    # Volumes
        VOLUME ["/home/data"]
    # Copy Files
        COPY /home/data/init.sh /home
    # Init
        RUN chmod a+x /home/init.sh

    3)Run.SH

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #!/bin/bash

    DOCKDEV_VARIABLES=(\
      DOCKDEV_USER_NAME=$USERNAME\
      DOCKDEV_USER_ID=$UID\
      DOCKDEV_GROUP_NAME=$(id -g -n $USERNAME)\
      DOCKDEV_GROUP_ID=$(id -g $USERNAME)\
    )

    cmd="docker run"

    if [ ! -z"${DOCKDEV_VARIABLES}" ]; then
      for v in ${DOCKDEV_VARIABLES[@]}; do
        cmd="${cmd} -e ${v}"
      done
    fi

    # /home/usr/data contains init.sh
    $cmd -v /home/usr/data:/home/data -i -t my-image /home/init.sh

    4)用docker建造4)跑!

    1
    sh run.sh

    我的方法是检测当前的uid/gid,然后在容器中创建这样的用户/组,并在其下执行脚本,这样他将创建的所有文件都将与运行脚本的用户匹配:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # get location of this script no matter what your current folder is, this might break between shells so make sure you run bash
    LOCAL_DIR="$( cd"$( dirname"${BASH_SOURCE[0]}" )" && pwd )"

    # get current IDs
    USER_ID=$(id -u)
    GROUP_ID=$(id -g)

    echo"Mount $LOCAL_DIR into docker, and match the host IDs ($USER_ID:$GROUP_ID) inside the container."

    docker run -v $LOCAL_DIR:/host_mount -i debian:9.4-slim bash -c"set -euo pipefail && groupadd -r -g $GROUP_ID lowprivgroup && useradd -u $USER_ID lowprivuser -g $GROUP_ID && cd /host_mount && su -c ./runMyScriptAsRegularUser.sh lowprivuser"

    在我的特定案例中,我试图用node docker映像构建我的节点包,这样就不必在部署服务器上安装NPM。它工作得很好,直到在容器外部和主机上,我尝试将文件移动到节点Docker映像创建的node_modules目录中,但由于该文件归根目录所有,我被拒绝访问该目录。我意识到我可以通过将目录从容器复制到主机来解决这个问题。通过Docker文档…

    Files copied to the local machine are created with the UID:GID of the
    user which invoked the docker cp command.

    这是我用来更改docker容器创建的目录所有权的bash代码。

    1
    2
    3
    4
    5
    6
    NODE_IMAGE=node_builder
    docker run -v $(pwd)/build:/build -w="/build" --name $NODE_IMAGE node:6-slim npm i --production
    # node_modules is owned by root, so we need to copy it out
    docker cp $NODE_IMAGE:/build/node_modules build/lambda
    # you might have issues trying to remove the directory"node_modules" within the shared volume"build", because it is owned by root, so remove the image and its volumes
    docker rm -vf $NODE_IMAGE || true

    如果需要,可以使用第二个Docker容器删除目录。

    1
    docker run -v $(pwd)/build:/build -w="/build" --name $RMR_IMAGE node:6-slim rm -r node_modules


    要在Docker主机和Docker容器之间共享文件夹,请尝试以下命令

    $ docker run -v"$(pwd):$(pwd)" -i -t ubuntu

    -v标志将当前工作目录装载到容器中。当绑定挂载卷的主机目录不存在时,Docker会自动在主机上为您创建这个目录。

    但是,这里有两个问题:

  • 如果您不是根用户,则无法写入装载的卷,因为共享文件将由主机中的其他用户拥有,
  • 您不应该以根用户身份运行容器中的进程,但是即使您以硬编码用户身份运行,它仍然无法与您的笔记本电脑/jenkins上的用户匹配,
  • 解决方案:

    集装箱:创建一个用户,比如说"testuser",默认情况下,用户ID将从1000开始,

    主持人:创建一个组,比如组ID为1000的"testgroup",并将目录转换为新组(testgroup


    如果使用docker compose,则以previleged模式启动容器:

    1
    2
    3
    4
    5
    6
    wordpress:
        image: wordpress:4.5.3
        restart: always
        ports:
          - 8084:80
        privileged: true