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

我使用Docker已经有一段时间了,在处理持久数据时不断发现同样的问题。

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

主机上的共享卷需要申请哪些权限?

我可以想到两个选择:

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

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

还有其他选择吗?

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

369506 次浏览

好的,这个现在是在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 回答

更新2016-03-02:从Docker 1.9.0开始,Docker有名叫卷替换仅数据容器. c。下面的答案,以及我链接的博客文章,在如何看待docker内的数据的意义上仍然有价值,但考虑使用命名卷来实现下面描述的模式,而不是数据容器。


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

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

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

更新:对于注释中的给定用例,你可能有一个图像some/graphite来运行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/gid,因此它可能看起来像这样:

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,或者只是重用现有的一个映像(必要时重写entrypoint/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:写完这个答案后,我决定写一个关于这个方法的更完整的博客文章。我希望这能有所帮助。

更新3:我修正了这个答案,并添加了更多的细节。它之前包含了一些关于所有权和perms的错误假设——所有权通常是在卷创建时分配的,即在数据容器中,因为那是创建卷的时候。看到这个博客。但这并不是必须的——您可以只使用数据容器作为“引用/句柄”,并通过入口点中的chown在另一个容器中设置所有权/perms,该入口点以gosu结尾,以正确的用户运行命令。如果有人对这种方法感兴趣,请发表评论,我可以提供使用这种方法的示例的链接。

为了保护和更改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

更新支持应该在docker > 1.7.0

更新 Version 1.10.0 (2016-02-04) add --userns-remap标志 https://github.com/docker/docker/blob/master/CHANGELOG.md#security-2 < / p >

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

  1. 绑定挂载主机卷

    Host folder FOOBAR is mounted in container /volume/FOOBAR < / p >

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

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

  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子句一起使用(除非该用户具有根权限)。此外,它是一种黑客工作;-)

如果你想要硬核,你显然可以在许多方面扩展它——例如,搜索任何子文件上的所有组,多个卷,等等。

一个非常优雅的解决方案可以在官方复述,形象和一般的所有官方图像中看到。

详细描述:

  • 首先创建redis用户/组

如Dockerfile评论所示:

首先添加我们的用户和组,以确保他们的id被一致分配,不管添加什么依赖项

  • 使用Dockerfile安装gosu

gosu是su / sudo的替代品,便于从根用户降级。(Redis总是使用redis用户运行)

  • 配置/data卷并将其设置为workdir

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

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

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

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

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

这有以下影响

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

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

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

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

假设,你想在容器中运行一些应用程序作为非根$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; $@"

基地图片

使用这个映像: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

尝试在Dockerfile中添加命令

RUN usermod -u 1000 www-data

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

如果你使用Docker Compose,以特权模式启动容器:

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

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

$(pwd):$(pwd)-i -t ubuntu

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

然而,这里有两个问题:

  1. 如果你是非根用户,你不能写挂载的卷,因为共享文件将由主机中的其他用户拥有,
  2. 你不应该以root用户在容器中运行进程,但即使你以硬编码用户运行,它仍然不会与你笔记本电脑上的用户匹配。

解决方案:

< p >容器: 创建用户testuser,默认用户id从1000开始,

< p >主持人: 创建一个名为'testgroup'的组,组id为1000,并将目录更改为新组(testgroup

. txt)

和你一样,我正在寻找一种将用户/组从主机映射到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镜像构建我的节点包,这样我就不必在部署服务器上安装npm。它一直工作得很好,直到在容器外和主机上,我试图将一个文件移动到节点docker映像创建的node_modules目录中,但由于它属于root用户,所以我被拒绝访问该目录。我意识到可以通过将目录从容器复制到主机来解决这个问题。通过码头工人文档……

拷贝到本地的文件创建时使用的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

我的方法是检测当前的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"

如果你对发展这样做,一个好的解决方案是使用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是我遇到的最简单的选择。除了克隆,没有运行成本。