【问题标题】:What is the (best) way to manage permissions for Docker shared volumes?管理 Docker 共享卷权限的(最佳)方法是什么?
【发布时间】:2014-06-25 23:50:46
【问题描述】:

我一直在使用 Docker,并且在处理持久数据时不断发现同样的问题。

我创建我的Dockerfileexpose a volume 或使用--volumes-frommount a host folder inside my container

我应该对主机上的共享卷应用什么权限?

我能想到两个选择:

  • 到目前为止,我已授予每个人读/写权限,因此我可以从 Docker 容器写入文件夹。

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

还有其他选择吗?

你们是如何处理这个问题的?

【问题讨论】:

标签: docker


【解决方案1】:

2016-03-02 更新:从 Docker 1.9.0 开始,Docker 有 named volumesreplace data-only containers。下面的答案以及我链接的博客文章在如何考虑 docker 中的数据方面仍然具有价值,但请考虑使用命名卷来实现下面描述的模式,而不是数据容器。


我相信解决这个问题的规范方法是使用data-only containers。使用这种方法,对卷数据的所有访问都是通过使用 -volumes-from 数据容器的容器进行的,因此主机 uid/gid 无关紧要。

例如,文档中给出的一个用例是备份数据卷。为此,另一个容器用于通过tar 进行备份,它也使用-volumes-from 来挂载卷。所以我认为 grok 的关键点是:与其考虑如何以适当的权限访问主机上的数据,不如考虑如何通过另一个容器做任何你需要的事情——备份、浏览等。 .容器本身需要使用一致的 uid/gids,但它们不需要映射到主机上的任何东西,从而保持可移植性。

这对我来说也相对较新,但如果您有特定的用例,请随时发表评论,我会尝试扩展答案。

更新:对于 cmets 中的给定用例,您可能有一个映像 some/graphite 来运行石墨,以及一个映像 some/graphitedata 作为数据容器。因此,忽略端口等,图像some/graphitedataDockerfile 类似于:

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"]

构建和创建数据容器:

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

some/graphite Dockerfile 也应该获得相同的 uid/gids,因此它可能看起来像这样:

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"]

它将按如下方式运行:

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

好的,现在这为我们提供了带有正确用户/组的石墨容器和关联的纯数据容器(请注意,您也可以将 some/graphite 容器重新用于数据容器,在运行时覆盖 entrypoing/cmd它,但将它们作为单独的图像 IMO 更清晰)。

现在,假设您要编辑数据文件夹中的某些内容。因此,与其将卷绑定到主机并在那里进行编辑,不如创建一个新容器来完成这项工作。让我们称之为some/graphitetools。我们还可以创建适当的用户/组,就像 some/graphite 图像一样。

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"]

您可以通过从 Dockerfile 中的 some/graphitesome/graphitedata 继承来使这个 DRY,或者不创建新映像,只需重新使用现有映像之一(根据需要覆盖入口点/cmd)。

现在,您只需运行:

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

然后是vi /data/graphite/whatever.txt。这非常有效,因为所有容器都有相同的石墨用户和匹配的 uid/gid。

由于您从不从主机挂载/data/graphite,因此您不必关心主机uid/gid 如何映射到graphitegraphitetools 容器中定义的uid/gid。这些容器现在可以部署到任何主机上,并且它们将继续完美运行。

这方面的巧妙之处在于graphitetools 可以拥有各种有用的实用程序和脚本,您现在还可以以可移植的方式进行部署。

更新 2:写完这个答案后,我决定写一个more complete blog post 来介绍这种方法。希望对你有帮助。

更新 3:我更正了这个答案并添加了更多细节。它以前包含一些关于所有权和权限的错误假设——所有权通常是在创建卷时分配的,即在数据容器中,因为那是创建卷的时候。见this blog。但这不是必需的——您可以将数据容器用作“参考/句柄”,并通过入口点中的 chown 在另一个容器中设置所有权/权限,最后以 gosu 结尾,以正确用户身份运行命令。如果有人对此方法感兴趣,请发表评论,我可以提供指向使用此方法的示例的链接。

【讨论】:

  • 恐怕这不是一个解决方案,因为您会遇到与纯数据容器相同的问题。归根结底,这些容器将使用从主机共享的卷,因此您仍需要管理这些共享文件夹的权限。
  • 请记住,我可能需要从主机编辑数据文件夹(即:删除测试石墨密钥、删除我的 JIRA 测试主文件夹或使用最新的生产备份更新它...) .据我从你的评论中了解到,我应该做一些事情,比如通过第三个容器更新 JIRA 数据。在任何情况下,您会对新的数据文件夹/data/newcontainer 应用什么权限?我假设您以 root 身份运行 docker(可以不这样做吗?)此外,如果数据直接挂载在主容器中或通过 data-only 容器我>?
  • 感谢您的精心回复。我一有机会就会对此进行测试。此外,很好地参考了your blog 的帖子和关于using minimal images for data containers 的帖子。
  • 这种方法的唯一问题是很容易误删容器。想象一下,如果它恰好是您的数据容器。我认为(CMIIW)数据仍将在 /var/lib/docker 某处,但仍然是一个巨大的痛苦
  • “您可以将数据容器用作“参考/句柄”并通过入口点中的 chown 在另一个容器中设置所有权/权限”... @Raman:这是最后的部分在解决了许多权限问题后救了我。在此使用入口点脚本并设置权限对我有用。感谢您的详尽解释。这是迄今为止我在网上找到的最好的。
【解决方案2】:

在官方redis image 和所有官方图片中都可以看到一个非常优雅的解决方案。

分步说明:

  • 先创建 redis 用户/组

如在 Dockerfile cmets 上所见:

首先添加我们的用户和组,以确保一致地分配他们的 ID,而不管添加了任何依赖项

  • 使用 Dockerfile 安装 gosu

gosu 是su / sudo 的替代品,可轻松从 root 用户降级。 (Redis 总是使用redis 用户运行)

  • 配置/data 卷并将其设置为工作目录

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

将其配置为工作目录 (WORKDIR /data) 使其成为执行命令的默认目录。

  • 添加 docker-entrypoint 文件并使用默认 CMD redis-server 将其设置为 ENTRYPOINT

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

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

这有以下效果

如果/data目录绑定挂载到主机,docker-entrypoint会在redis用户下运行redis-server之前准备好用户权限。

这让您可以放心地进行零设置,以便在任何卷配置下运行容器。

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

【讨论】:

  • 接受的答案提供了丰富的信息,但它只会让我陷入长达一周的挫败感,因为我有权找到这个实际上提供了解决问题的规范方法的答案。
  • 所以?如何从 docker 内部使卷可写? chown 里面有 ENTRYPOINT 脚本?
【解决方案3】:

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

  1. 绑定挂载主机卷

    Host folder FOOBAR is mounted in container /volume/FOOBAR

  2. 修改容器的启动脚本以找到您感兴趣的卷的 GID

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

  3. 确保您的用户属于具有此 GID 的组(您可能必须创建一个新组)。在这个例子中,我假设我的软件在容器内以nobody 用户身份运行,所以我想确保nobody 属于一个组ID 等于TARGET_GID的组

  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 子句一起使用(除非我认为该用户具有 root 权限)。此外,它尖叫黑客工作;-)

如果你想成为铁杆,你显然可以通过多种方式扩展它 - 例如。搜索任何子文件、多个卷等上的所有组。

【讨论】:

  • 这是针对读取已安装卷中的文件吗?我正在寻找一种解决方案来编写文件,而不是由创建 docker 容器的其他用户拥有。
  • 我从 2015 年 8 月开始使用这种方法。一切都很好。只是在容器内创建的文件的权限是不同的。用户(容器内部和外部)都拥有其文件的所有权,但都具有读取权限,因为它们确实属于此解决方案创建的同一组。当用例确实对公共文件施加写访问权时,问题就开始了。更大的问题是共享卷有 git 文件(它是在同一生产环境中测试开发源文件的卷)。 Git 开始警告共享代码的访问问题。
  • 我认为$TARGET_GID 更好的grep 是使用grep ':$TARGET_GID:',否则如果容器有,例如gid 10001 并且您的主机是 1000,此检查将通过但不应该。
  • 这个解决方案也适用于写作。 – 您最终需要在 git 存储库中运行 chmod g+w -R OR 在已安装的卷上,以便 tempgroup 仍然可以从容器内部写入。
  • 我的评论方法的唯一缺点是,根据您的操作系统(如在 macOS 上),您可能设置了umask 022,而不是umask 002,因此您在主机上创建的每个文件对于需要在容器中写入的任何文件,侧(例如来自 IDE)将需要运行相同的chmod g+w 命令。但大多数情况下,对于通过 IDE 创建的源代码文件,情况并非如此。 ?
【解决方案4】:

尝试向 Dockerfile 添加命令

RUN usermod -u 1000 www-data

致谢https://github.com/denderello/symfony-docker-example/issues/2#issuecomment-94387272

【讨论】:

    【解决方案5】:

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

      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->用户名(以及组)匹配主持人。现在,您可以在容器内为您的服务配置用户/组设置,就好像它在您的主机系统上工作一样。

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

    【讨论】:

    • 这是一个很好的答案,如果您想在基本系统上运行处理文件的容器,而不暴露系统的其余部分,这非常简单。
    • 这是我最喜欢的答案。此外,我在 docker run 命令的其他地方看到了类似的建议,您可以通过 -u $( id -u $USER ):$( id -g $USER ) 传递当前的用户名/组,并且您不再需要担心用户名。这适用于您想要生成默认具有读/写访问权限的文件(例如二进制文件)的本地开发环境。
    • 这确实是预期的最终用户行为。发现我的 docker 容器的用户拥有主机的 uid/gid,但我在 docker run 命令之前明确设置的其他 gid 中没有相同的 uid,这让我感到困惑。当 docker 提出他们的默认行为时,没有考虑最终用户
    【解决方案6】:

    好的,现在是tracked at docker issue #7198

    目前,我正在使用您的第二个选项来处理这个问题:

    将用户从主机映射到容器中

    Dockerfile

    #=======
    # 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)
    

    命令行

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

    更新我目前更倾向于Hamyanswer

    【讨论】:

    【解决方案7】:

    我的做法是检测当前的UID/GID,然后在容器内创建这样的用户/组,并在他下面执行脚本。结果,他将创建的所有文件都将匹配主机上的用户:

    # get the 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"
    

    【讨论】:

      【解决方案8】:

      这是一种仍然使用纯数据容器但不要求它与应用程序容器同步的方法(就具有相同的 uid/gid 而言)。

      大概,您想在容器中以非 root $USER 身份运行某些应用程序,而无需登录 shell。

      在 Dockerfile 中:

      RUN useradd -s /bin/false myuser
      
      # Set environment variables
      ENV VOLUME_ROOT /data
      ENV USER myuser
      
      ...
      
      ENTRYPOINT ["./entrypoint.sh"]
      

      然后,在 entrypoint.sh 中:

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

      【讨论】:

        【解决方案9】:

        为了安全和更改 docker 容器的根目录,docker 主机尝试使用 --uidmap--private-uids 选项

        https://github.com/docker/docker/pull/4572#issuecomment-38400893

        您还可以删除 docker 容器中的多个功能 (--cap-drop) 以确保安全

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

        UPDATE支持应该来自docker > 1.7.0

        更新 版本1.10.0 (2016-02-04) 添加--userns-remap 标志 https://github.com/docker/docker/blob/master/CHANGELOG.md#security-2

        【讨论】:

        • 我正在运行 docker 1.3.2 build 39fa2fa(最新),没有看到 --uidmap--private-uids 选项的痕迹。看起来 PR 没有成功,也没有合并。
        • 它没有合并到核心中,如果你愿意,你可以使用它来打补丁。现在只能限制某些功能并从非 root 用户在容器中运行您的应用程序。
        • 2015 年 6 月,我没有看到它在 docker 1.6.2 中合并,你的答案仍然有效吗?
        • 问题仍未解决。开发者应该在 1.7 版本中添加支持。 (--root 选项)github.com/docker/docker/pull/12648
        • 开发人员似乎再次使用此功能移动了版本。 Docker 开发者 "icecrime" 说 "We apparently do have so some of conflicting designs between libnetwork and user namespaces ... and something we'd like to get in for 1.8.0. So don't think we're dropping this, we're definitely going to take a break after all these, and see how we need to reconsider the current design and integration of libnetwork to make this possible. Thanks!" github.com/docker/docker/pull/12648 所以我认为我们应该等待下一个稳定版本。
        【解决方案10】:

        基础图片

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

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

        1) init.sh

        #!/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) Dockerfile

        FROM ubuntu:latest
        # Volumes
            VOLUME ["/home/data"]
        # Copy Files
            COPY /home/data/init.sh /home
        # Init
            RUN chmod a+x /home/init.sh
        

        3) 运行.sh

        #!/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) 运行!

        sh run.sh
        

        【讨论】:

          【解决方案11】:

          在我的特定情况下,我尝试使用节点 docker 映像构建我的节点包,这样我就不必在部署服务器上安装 npm。它运行良好,直到在容器外部和主机上,我尝试将文件移动到节点 docker 映像创建的 node_modules 目录中,但我被拒绝访问该目录,因为它归 root 所有。我意识到我可以通过将目录从容器中复制到主机上来解决这个问题。通过docker docs...

          复制到本地机器的文件是使用 UID:GID 创建的 调用 docker cp 命令的用户。

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

          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 容器删除该目录。

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

          【讨论】:

            【解决方案12】:

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

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

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

            但是,这里有两个问题:

            1. 如果您是非 root 用户,则无法写入挂载的卷,因为共享文件将由主机中的其他用户拥有,
            2. 您不应该在容器中以 root 身份运行该进程,但即使您以某个硬编码用户身份运行,它仍然与您的笔记本电脑/Jenkins 上的用户不匹配,

            解决方案:

            容器: 创建一个用户说'testuser',默认用户ID将从1000开始,

            主持人: 创建一个组 id 为 1000 的组,例如“testgroup”,并将目录添加到新组(testgroup

            【讨论】:

              【解决方案13】:

              如果您这样做是为了开发,一个好的解决方案是使用bindfs

              1. 保留容器用户拥有的源代码。 (如果可能,让容器克隆源代码。)
              2. 使用bindfs映射主机用户的文件夹。

              这是我的 docker-compose 设置现在的样子:

              project:
                web/src # Container clones it using init scripts.
                web/log
                __web__/src # Host user uses this. It's just bindfs mirror.
                __web__/log
              

              我已经考虑这个问题一年多了,bindfs 是我遇到的最简单的选择。除了克隆之外,没有运行时成本。

              【讨论】:

                【解决方案14】:

                如果您使用 Docker Compose,请以 previleged 模式启动容器:

                wordpress:
                    image: wordpress:4.5.3
                    restart: always
                    ports:
                      - 8084:80
                    privileged: true
                

                【讨论】:

                • 这可能会使安装卷更容易,但是 .. Wordpress 是否以特权模式启动?这是一个可怕的想法——要求妥协。 wpvulndb.com/wordpresses/453
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2015-10-25
                • 1970-01-01
                • 1970-01-01
                • 2010-09-05
                • 2016-12-19
                • 2022-06-23
                • 1970-01-01
                相关资源
                最近更新 更多