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

时间:2014-05-08 14:11:10

标签: docker

我一直在玩Docker一段时间,并在处理持久数据时继续发现同样的问题。

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

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

我可以想到两个选择:

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

  • 将用户从主机映射到容器,这样我就可以分配更细化的权限。不确定这是否可行,但并未发现它有多少。到目前为止,我所能做的就是以某个用户身份运行容器:docker run -i -t -user="myuser" postgres,但此用户的UID与我的主机myuser不同,因此权限不起作用。此外,我不确定映射用户是否会带来一些安全风险。

还有其他选择吗?

你们/ gals如何处理这个问题?

13 个答案:

答案 0 :(得分:160)

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


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

例如,文档中给出的一个用例是备份数据卷。为此,使用另一个容器通过tar进行备份,并且它也使用-volumes-from来安装卷。因此,我认为grok的关键点在于:而不是考虑如何使用适当的权限访问主机上的数据,考虑如何通过另一个容器执行任何操作(备份,浏览等) 。容器本身需要使用一致的uid / gids,但它们不需要映射到主机上的任何东西,从而保持可移植性。

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

更新:对于评论中的给定用例,您可能拥有运行石墨的图像some/graphite和图像some/graphitedata作为数据容器。因此,忽略端口等,图像Dockerfile的{​​{1}}类似于:

some/graphitedata

构建并创建数据容器:

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 Dockerfile也应该获得相同的uid / gids,因此它可能看起来像这样:

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
# ... graphite installation ...
VOLUME /data/graphite
USER graphite
CMD ["/bin/graphite"]

好的,现在它为我们提供了石墨容器和相关的仅数据容器以及正确的用户/组(请注意,您也可以重新使用数据容器的docker run --volumes-from=graphitedata some/graphite 容器,覆盖入口/ cmd在运行时,将它们作为单独的图像IMO更清晰。)

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

some/graphite

您可以通过继承Dockerfile中的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"] some/graphite来创建此DRY,或者不是创建新映像而只是重新使用其中一个(根据需要覆盖入口点/ cmd)

现在,您只需运行:

some/graphitedata

然后docker run -ti --rm --volumes-from=graphitedata some/graphitetools 。这非常有效,因为所有容器都具有相同的石墨用户和匹配的uid / gid。

由于您从未在主机上挂载vi /data/graphite/whatever.txt,因此您并不关心主机uid / gid如何映射到/data/graphitegraphite容器内定义的uid / gid 。这些容器现在可以部署到任何主机,它们将继续完美地工作。

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

更新2 :写完这个答案之后,我决定写一篇关于这种方法的more complete blog post。我希望它有所帮助。

更新3 :我更正了此答案并添加了更多细节。它之前包含一些关于所有权和权限的错误假设 - 所有权通常在卷创建时分配,即在数据容器中分配,因为这是在创建卷时。见this blog。这不是一个要求 - 你可以只使用数据容器作为"参考/句柄"并通过入口点中的chown在另一个容器中设置所有权/权限,该入口点以gosu结束,以便以正确的用户身份运行该命令。如果有人对这种方法感兴趣,请发表评论,我可以使用这种方法提供样本的链接。

答案 1 :(得分:46)

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

逐步说明:

  • 在其他任何事情之前创建redis用户/组

如Dockerfile评论所示:

  

首先添加我们的用户和组,以确保一致地分配他们的ID,无论添加什么依赖项

  • 使用Dockerfile安装gosu

gosu是su / sudo的替代方案,可以让root用户轻松降级。 (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-entrypoint将在redis用户下运行redis-server之前准备用户权限。

这使您可以轻松地设置零设置,以便在任何卷配置下运行容器。

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

答案 2 :(得分:28)

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

  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的组

  4.   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权限)。此外,它尖叫黑客工作; - )

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

答案 3 :(得分:15)

好的,现在是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)

CLI

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

更新我目前更倾向于Hamy answer

答案 4 :(得分:11)

尝试向Dockerfile添加命令

RUN usermod -u 1000 www-data

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

答案 5 :(得分:7)

和你一样,我一直在寻找一种方法将用户/组从主机映射到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->用户名(以及用于组)匹配主人。现在,您可以在容器内配置服务的用户/组设置,就像它在主机系统上工作一样。

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

答案 6 :(得分:5)

这是一种仍然使用仅数据容器的方法,但不要求它与应用程序容器同步(就具有相同的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; $@"

答案 7 :(得分:4)

对于docker容器的安全和更改root,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

更新支持应该在docker > 1.7.0

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

答案 8 :(得分:3)

基本图像

使用此图片: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)run.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

答案 9 :(得分:2)

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

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

答案 10 :(得分:0)

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

  

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

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

但是,我们遇到了两个问题:

  1. 如果您是非root用户,则无法写入已装入的卷,因为共享文件将归主机中的其他用户所有,
  2. 您不应该以root用户身份在容器内运行该进程,但即使您以某个硬编码用户身份运行,它仍然与您的笔记本电脑/ Jenkins上的用户不匹配,
  3. <强>解决方案:

    容器: 创建用户说'testuser',默认情况下用户ID将从1000开始,

    主机: 创建一个组名为'testgroup',组ID为1000,并将目录chown到新组(testgroup

答案 11 :(得分:0)

在我的具体情况下,我试图使用节点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 :(得分:-3)

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

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