我一直在使用 Docker,并且在处理持久数据时不断发现同样的问题。
我创建我的 Dockerfile
和 expose a volume 或使用 --volumes-from
到 mount a host folder inside my container。
我应该对主机上的共享卷应用什么权限?
我可以想到两个选择:
到目前为止,我已经为每个人提供了读/写访问权限,因此我可以从 Docker 容器写入文件夹。
将主机中的用户映射到容器中,这样我就可以分配更精细的权限。虽然不确定这是可能的,但还没有找到太多关于它的信息。到目前为止,我所能做的就是以某个用户身份运行容器:docker run -i -t -user="myuser" postgres,但是这个用户的 UID 与我的主机 myuser 不同,因此权限不起作用。另外,我不确定映射用户是否会带来一些安全风险。
还有其他选择吗?
你们是如何处理这个问题的?
2016-03-02 更新:从 Docker 1.9.0 开始,Docker 具有 named volumes 和 replace data-only containers。下面的答案以及我链接的博客文章在如何考虑 docker 内部的数据 的意义上仍然具有价值,但考虑使用命名卷来实现下面描述的模式而不是数据容器。
我相信解决这个问题的规范方法是使用 data-only containers。使用这种方法,对卷数据的所有访问都是通过使用 -volumes-from
数据容器的容器进行的,因此主机 uid/gid 无关紧要。
例如,文档中给出的一个用例是备份数据卷。为此,另一个容器用于通过 tar
进行备份,它也使用 -volumes-from
来安装卷。所以我认为 grok 的关键点是:与其考虑如何以适当的权限访问主机上的数据,不如考虑如何通过另一个容器做任何你需要的事情——备份、浏览等。 .容器本身需要使用一致的 uid/gids,但它们不需要映射到主机上的任何东西,从而保持可移植性。
这对我来说也相对较新,但是如果您有特定的用例,请随时发表评论,我会尝试扩展答案。
更新:对于评论中的给定用例,您可能有一个图像 some/graphite
来运行石墨,以及一个图像 some/graphitedata
作为数据容器。因此,忽略端口等,图像 some/graphitedata
的 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
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/graphite
或 some/graphitedata
继承来使此 DRY,或者不创建新映像,只需重新使用现有映像之一(根据需要覆盖 entrypoint/cmd)。
现在,您只需运行:
docker run -ti --rm --volumes-from=graphitedata some/graphitetools
然后是 vi /data/graphite/whatever.txt
。这非常有效,因为所有容器都有相同的石墨用户和匹配的 uid/gid。
由于您从不从主机装载 /data/graphite
,因此您不必关心主机 uid/gid 如何映射到 graphite
和 graphitetools
容器内定义的 uid/gid。这些容器现在可以部署到任何主机上,并且它们将继续完美运行。
这样做的好处是 graphitetools
可以具有各种有用的实用程序和脚本,您现在还可以以可移植的方式进行部署。
更新 2:写完这个答案后,我决定写一篇关于这种方法的more complete blog post。我希望它有所帮助。
更新 3:我更正了这个答案并添加了更多细节。它以前包含一些关于所有权和权限的错误假设——所有权通常是在创建卷时分配的,即在数据容器中,因为那是创建卷的时候。请参阅this blog。但这不是必需的——您可以将数据容器用作“引用/句柄”,并通过入口点中的 chown 在另一个容器中设置所有权/权限,最后以 gosu 结尾,以正确用户身份运行命令。如果有人对这种方法感兴趣,请发表评论,我可以提供使用这种方法的示例链接。
在官方 redis image 以及所有官方图片中都可以看到一个非常优雅的解决方案。
分步过程描述:
先创建redis用户/组
如 Dockerfile 评论所示:
首先添加我们的用户和组,以确保一致地分配他们的 ID,而不管添加了任何依赖项
使用 Dockerfile 安装 gosu
gosu 是 su
/ sudo
的替代品,可轻松从 root 用户降级。 (Redis 始终使用 redis
用户运行)
配置 /data 卷并将其设置为 workdir
通过使用 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,否则最新的容器将劫持前一个容器的用户权限。
chown
它在 ENTRYPOINT
脚本中?
在大多数情况下,这可以说不是最好的方法,但它还没有被提及,所以也许它会对某人有所帮助。
绑定挂载主机卷 主机文件夹 FOOBAR 挂载在容器 /volume/FOOBAR 修改容器的启动脚本以找到您感兴趣的卷的 GID $ TARGET_GID=$(stat -c "%g" /volume/FOOBAR) 确保您的用户属于具有此 GID 的组(您可能必须创建一个新组)。对于此示例,我将假设我的软件在容器内以无人用户身份运行,因此我想确保无人属于组 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 权限)。此外,它尖叫黑客工作;-)
如果您想成为铁杆,您显然可以通过多种方式扩展它 - 例如在任何子文件、多个卷等上搜索所有组。
$TARGET_GID
更好的 grep 是使用 grep ':$TARGET_GID:'
,否则如果容器有,例如 gid 10001 并且您的主机是 1000,则此检查将通过但它不应该。
chmod g+w -R
OR 在已安装的卷上,以便 tempgroup
仍然可以从容器内部写入。
umask 022
,而不是 umask 002
,因此您在主机端(例如从 IDE)创建的每个文件都会对于需要在容器中写入的任何文件,都需要运行相同的 chmod g+w
命令。但大多数情况下,对于通过 IDE 创建的源代码文件,情况并非如此。 🙃
尝试向 Dockerfile 添加命令
RUN usermod -u 1000 www-data
学分归 https://github.com/denderello/symfony-docker-example/issues/2#issuecomment-94387272
和你一样,我正在寻找一种将用户/组从主机映射到 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-> 用户名(以及组)匹配。现在,您可以在容器内为您的服务配置用户/组设置,就好像它在您的主机系统上工作一样。
当您决定将容器移动到另一台主机时,您只需将服务配置文件中的用户名更改为您在该主机上拥有的用户名。
-u $( id -u $USER ):$( id -g $USER )
传入当前的用户名/组,您不再需要担心用户名。这适用于您想要生成默认具有读/写访问权限的文件(例如二进制文件)的本地开发环境。
好的,现在是 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
id -u <username>
、id -g <username>
、id -G <username>
来获取特定用户的用户 ID 和组 ID
我的做法是检测当前的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"
这是一种仍然使用纯数据容器但不要求它与应用程序容器同步的方法(就具有相同的 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; $@"
为了安全和更改 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
--uidmap
和 --private-uids
选项的痕迹。看起来 PR 没有成功,也没有合并。
"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 所以我认为我们应该等待下一个稳定版本。
基本图像
使用此图片:https://hub.docker.com/r/reduardo7/docker-host-user
或者
重要提示:这会破坏跨主机的容器可移植性。
1) 初始化.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
在我的特定情况下,我试图使用节点 docker 映像构建我的节点包,这样我就不必在部署服务器上安装 npm。它运行良好,直到在容器外部和主机上,我尝试将文件移动到节点 docker 映像创建的 node_modules 目录中,但我被拒绝访问该目录,因为它归 root 所有。我意识到我可以通过将目录从容器中复制到主机上来解决这个问题。通过 docker docs...
复制到本地计算机的文件是使用调用 docker cp 命令的用户的 UID:GID 创建的。
这是我用来更改在 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
要在 docker 主机和 docker 容器之间共享文件夹,请尝试以下命令
docker run -v "$(pwd):$(pwd)" -i -t ubuntu
-v 标志将当前工作目录挂载到容器中。当绑定挂载的卷的主机目录不存在时,Docker会自动为你在主机上创建这个目录,
但是,这里有两个问题:
如果您是非 root 用户,则无法写入挂载的卷,因为共享文件将由主机中的其他用户拥有,您不应在容器内以 root 身份运行该进程,但即使您以某些硬编码用户身份运行它仍然与您的笔记本电脑/Jenkins 上的用户不匹配,
解决方案:
容器:创建一个用户说'testuser',默认用户ID将从1000开始,
主机:创建一个组 id 为 1000 的组,例如“testgroup”,并将目录添加到新组(testgroup
如果您这样做是为了开发,一个好的解决方案是使用 bindfs
:
保留容器用户拥有的源代码。 (如果可能,让容器克隆源代码。)使用 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
是我遇到的最简单的选项。除了克隆之外,没有运行时成本。
如果您使用 Docker Compose,请以 previleged 模式启动容器:
wordpress:
image: wordpress:4.5.3
restart: always
ports:
- 8084:80
privileged: true
/data/newcontainer
应用什么权限?我假设您以 root 身份运行docker
(可以不这样做吗?)另外,如果数据直接挂载在主容器中或通过 仅数据容器我>?/var/lib/docker
某处,但仍然是一个巨大的痛苦