它是关于命名卷(所以没有“数据卷容器”,没有“卷来自”)和 docker-compose.yml。
这里的目标是使用 docker-compose 在两个单独的容器中管理两个服务“appserver”和“server-postgresql”,并使用“volumes:”docker-compose.yml 功能使来自服务“server-postgresql”的数据持久化.
“server-postgresql”的 Dockerfile 如下所示:
FROM ubuntu:14.04
MAINTAINER xxx
RUN apt-get update && apt-get install -y [pgsql-needed things here]
USER postgres
RUN /etc/init.d/postgresql start && \
psql --command "CREATE USER myUser PASSWORD 'myPassword';" && \
createdb -O diya diya
RUN echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/9.3/main/pg_hba.conf
RUN echo "listen_addresses='*'" >> /etc/postgresql/9.3/main/postgresql.conf
CMD ["/usr/lib/postgresql/9.3/bin/postgres", "-D", "/var/lib/postgresql/9.3/main", "-c", "config_file=/etc/postgresql/9.3/main/postgresql.conf"]
添加 docker-compose.yml 如下所示:
version: '2'
services:
appserver:
build: appserver
depends_on:
- server-postgresql
links:
- "server-postgresql:serverPostgreSQL"
ports:
- "1234"
- "1235"
restart: on-failure:10
server-postgresql:
build: serverPostgreSQL
ports:
- "5432"
volumes:
- db-data:/volume_data
restart: on-failure:10
volumes:
db-data:
driver: local
然后,我开始用一切docker-compose up -d
,输入我的服务器的PostgreSQL容器docker-compose exec server-postgresql bash
,快速ls
没有透露/volume_data
,然后我cd
并尝试touch testFile
并得到“权限被拒绝。这是正常的,因为快速ls -l
显示volume_data
归root:root
。
现在我认为正在发生的是,因为我在 Dockerfile 中有USER postgres
,当我运行docker-compose exec
我以用户“postgres”的身份登录(并且 postgresql 守护进程以用户“postgres”的身份运行)同样,因此它将无法写入/volume_data
)。
这是确认的,因为当我运行它时: docker-compose exec --user root server-postgresql bash
并重试cd /volume_data
和touch testFile
,它确实有效(这不是主机和容器之间的权限错误,因为当容器挂载主机文件夹时,有时会出现这种情况,这是典型的 unix 权限错误,因为/volume_data
挂载为 'root:root',而用户 'postgres' 正在尝试写入)。
所以 docker-compose.yml 中应该有一种方法可以将命名卷挂载为特定用户,例如:
version: '2'
services:
appserver:
[...]
server-postgresql:
[...]
volumes:
- db-data:/volume_data:myUser:myGroup
[...]
volumes:
db-data:
driver: local
我能想到的唯一 _dirty_ 解决方法是从 Dockerfile 中删除USER posgres
指令,并更改 ENTRYPOINT 使其指向自定义的“init_script.sh”(因为我将作为“root”运行)删除USER postgres
),此脚本将更改/volume_data
权限,以便 'postgres' 可以在其上写入,然后su postgres
并执行 postgresql 守护进程(在前台)。 但这实际上非常脏,因为它以非标准方式链接 Dockerfile 和 docker-compose.yml(运行时 ENTRYPOINT 将依赖于 docker-compose.yml 提供已安装卷的事实)。
我认为 docker 引擎不支持此功能,因此在将其添加到 API 之前,我们无法在 Compose 中支持它。 但是我认为没有必要添加此功能。 您始终可以将文件 chown 给正确的用户:
version: '2'
services:
web:
image: alpine:3.3
volumes: ['random:/path']
volumes:
random:
$ docker-compose run web sh
/ # touch /path/foo
/ # ls -l /path
total 0
-rw-r--r-- 1 root root 0 Apr 5 16:11 foo
/ # chown postgres:postgres /path/foo
/ # ls -l /path
total 0
-rw-r--r-- 1 postgres postgres 0 Apr 5 16:11 foo
/ #
$ docker-compose run web sh
/ # ls -l /path
total 0
-rw-r--r-- 1 postgres postgres 0 Apr 5 16:11 foo
您面临的问题是关于初始化命名卷。 诚然,这不是 Compose 处理的事情(因为它有点超出范围),但是您可以轻松地使用 docker cli 在运行docker-compose up
之前初始化命名卷。
我确实不确定这是 Docker 还是 Compose 问题,如果我错误归档,请见谅。
这是 Docker API 中的计划,我应该在那里提交问题吗?
我了解手动登录容器并将chown
卷发送给“postgres”用户的可能性。 但问题是,就我而言,我正在使用 Compose,因此我可以立即为新客户端 ( docker-compose -p client_name up
) 实例化新容器,Compose 将创建自定义网络client_name_default
,它将创建容器名称client_name _appserver_1
和client_name _server-postgresql_1
以及_更重要的是_,它将创建卷client_name_db-data
。 所有这些我都不必手动完成,因此它可以由处理客户端注册的脚本运行。
使用您描述的解决方案(使用sh
和chown
在容器中_手动“记录”卷标),我无法使用简单的程序来添加新客户端,这必须由人工照顾。
这就是为什么我认为应该实现这个功能。 在Docker API中,挂载卷时我们可以指定ro
或者rw
(只读或者读写)权限,我觉得应该可以指定user:group
。
你怎么看,我的要求有意义吗?
其实我是带着消息来到这里的,看起来我想要实现的目标是可行的,但我不知道这是一个特性还是一个错误。 这就是我改变的原因:
在我的Dockerfile 中,_before 更改为用户 'postgres'_ 我添加了这些:
# ...
RUN mkdir /volume_data
RUN chown postgres:postgres /volume_data
USER postgres
# ...
这样做是创建一个目录 /volume_data 并更改其权限,以便用户 'postgres' 可以在其上写入。
这是Dockerfile部分。
现在我没有对docker-compose.yml 进行任何更改:所以directory_name_db-data
并将其挂载到/volume_data
并且权限仍然存在! .
这意味着现在,我将我的命名卷安装在 _pre-existing_ 目录/volume_data
,并保留了权限,因此“postgres”可以写入它。
那么,如果这是预期的行为还是违反安全? (不过,在这种情况下,它确实为我服务!)
我相信这是在 Docker 1.10.x 中添加的,以便命名卷将从使用它们的第一个容器初始化。 我认为这是预期的行为。
我也在做命名卷,在 Dockerfile 中设置所有权,并且在 compose 我添加user: postgres
以便即使 PID 1 也归非 root 用户所有。
Docker-compose
volumes
Docker-compose
有driver_opts
来提供选项。
看到chmod
、 chown
类的选项会非常好,即使对于驱动程序local
。
特别是我希望它也将应用于本地创建的主机目录,如果它在启动时不存在。
相关(在某种程度上) https://github.com/moby/moby/pull/28499
有人已经在 Moby 项目上打开了一个问题吗?
@dnephin的答案不起作用。 问题是因为我们以标准用户身份运行容器, chown
或chmod
命令失败,卷由 root 拥有,标准用户无法更改权限。
@jcberthon建议的方法是以 root 作为起始用户运行容器,然后在 chown/chmod 之后使用USER
命令,这样它基本上就是“删除特权”。
如果您可以控制 Docker 映像,那很好,但是如果您使用现有的映像,这不是一个真正的选择。
@dragon788和@micheljung ,我解决了我的问题。
实际上,真正的问题是我的 Dockerfile,我声明了一个VOLUME
,然后我修改了该卷中文件的所有权和权限。 这些变化都丢失了。 通过简单地将VOLUME
声明移动到 Dockerfile 的末尾(或删除它,因为它是可选的),我的问题就解决了。 权限是正确的。
所以错误是:
FROM blabla
RUN do stuff
VOLUME /vol
RUN useradd foo && chown -R foo /vol
USER foo
CMD ["blabla.sh"]
上面示例 Dockerfile 中的chown
在构建过程中丢失了,因为我们在它之前声明了VOLUME
。 当运行容器,命名空间内dockerd副本的内容/vol
的前VOLUME
声明(所以用root权限)。 因此,正在运行的进程无法修改或更改权限,因此即使在 blabla.sh 脚本中强制执行 chown 也无法工作。
通过将文件更改为:
FROM blabla
RUN do stuff
RUN useradd foo && chown -R foo /vol
USER foo
VOLUME /vol
CMD ["blabla.sh"]
问题已经解决了。
@jcberthon你能分享一下你如何在你的 docker-compose.yml 中将你的卷 /vol 与主机系统绑定吗?
我正在 Fedora 上使用 Docker(因此,启用了 SELinux)并且上述方法都不适合我。 理想情况下,我想在用户(无 root)的上下文中在我的容器中运行应用程序,但是这个 Volume 问题是一个障碍。
唯一对我有用的解决方法是消除我的应用程序用户并以 root 用户身份运行/拥有所有内容。
嗨@renanwilliam和@egee-irl
我一直在几个操作系统上使用上述解决方案,包括。 Fedora 26 和 CentOS 7 都强制使用 SELinux,Ubuntu 16.04、17.10 和 Raspbian 9 三个版本都激活了 AppArmor(混合了 amd64 和 armhf 平台)。
所以正如我所说,我现在已经在我的 Dockerfile 末尾移动了VOLUME ...
声明,但是您可以将其全部删除,它不是必需的。 我通常还会在 Dockerfile 中创建用户时修复用户 ID(例如useradd -u 8002 -o foo
)。 然后我可以简单地在主机上重用该 UID 来为文件夹提供适当的权限。
所以下一步是在主机上创建/vol
目录的“挂件”,假设它是/opt/mycontainer1/vol
,那就是
$ sudo mkdir -p /opt/mycontainer1/vol
$ sudo chown -R 8002 /opt/mycontainer1/vol
$ sudo chmod 0750 /opt/mycontainer1/vol
然后当以用户 foo 运行容器时,它将能够写入/opt/mycontainer1/vol
目录。 就像是:
$ sudo -u docker-adm docker run --name mycontainer1 -v /opt/mycontainer1/vol:/vol mycontainer1-img
在基于 SELinux 的主机上,您可能希望为卷添加:z
:Z
选项,以便 Docker 适当地标记文件夹。 z
和Z
之间的区别在于小写的z
将标记卷,以便 SELinux 可能允许该主机的所有容器访问该目录(但显然仅当您将其绑定挂载到另一个容器时),而大写的Z
将标记它,以便只有该特定容器才能访问该目录。 因此,在带有 SELinux 的 Fedora 上,您可能想尝试:
$ sudo -u docker-adm docker run --name mycontainer1 -v /opt/mycontainer1/vol:/vol:Z mycontainer1-img
更新:你可以在这里查看我的 repo https://github.com/jcberthon/unifi-docker我正在使用这种方法并解释如何配置主机和运行你的容器。 我希望这可以帮助进一步解决您的问题。
顺便说一句,我很抱歉@renanwilliam很长时间没有回复你。 今年年底我没有太多空闲时间......
因此,对于不耐烦的人来说,长话短说:
RUN mkdir /volume_data
RUN chown postgres:postgres /volume_data
预先创建卷目录并使用chown
解决它,因为卷将保留预先存在的目录的权限。
这是一个糟糕的解决方法,因为它不明显(在 Dockerfile 中执行 chown,然后在挂载期间继承该所有权)。 在 docker-compose 和 docker CLI 中公开所有者和组控制将是 unix 样式命令最不令人惊讶的路径。
@维拉斯夫
一个小技巧:将 2 RUN ...
合并为一个,这样可以避免创建额外的图层,并且是最佳实践。 所以你的 2 行应该是
RUN mkdir /volume_data && chown postgres:postgres /volume_data
但是请注意(正如我在上面的评论中提到的),您需要在使用VOLUME ...
声明卷(或实际上不声明卷)之前执行上述RUN ...
命令。 如果您(像我一样)在声明卷后更改所有权,那么这些更改不会被记录和丢失。
@colbygk确实很方便,但这不是 Linux 的工作方式。 Docker 使用 Linux 挂载命名空间来创建不同的单目录层次结构( /
和子文件夹),但 AFAIK 目前在 Linux 挂载命名空间中没有用户/组映射或权限覆盖。 容器内的那些“挂载”(包括绑定挂载卷)位于主机上的文件系统上(当然,除非您使用其他一些 Docker 卷插件),并且该文件系统尊重 Linux VFS 层,它可以完成所有工作文件权限检查。 主机上甚至可能存在一些 MAC(例如 SELinux、AppArmor 等),它们可能会干扰容器访问容器内的文件。 实际上,如果您执行chroot
,您可能会遇到类似的问题,因为您可以在 chroot 中绑定挂载文件夹,您还会遇到在 chroot 环境中运行的进程可能具有错误的有效 UID/GID 来访问文件的问题绑定安装。
简单的 Linux(实际上也是 Unix)规则适用于容器。 诀窍是看到和理解当今 Linux 命名空间的可能性和局限性,然后如何解决诸如此类的问题变得更加清晰。 我完全使用经典的 Unix 命令解决了它。
@jcberthon感谢您的周到回复:
我认为这应该是一个按照您的建议推送到插件层的问题,因此可以成为 Docker 附带的通用卷处理程序插件的一部分。 对我来说,强制外部资源(特定容器的外部)遵守本质上是静态的关系,这些关系在容器派生自的图像中定义,这对我来说似乎非常不云/容器。
在“unix”的其他类似区域中还有其他类似的 uid/gid 映射示例:
如果我错了,请纠正我; https://github.com/zfsonlinux/zfs/issues/4177似乎是由“LXC/LXD 的领导者”作为与 Linux 上的 ZFS 相关的问题未正确提供 UID/GID 信息以允许将这些映射到几乎以我们在这里讨论的 _exact_ 方式创建容器。 仔细查看https://github.com/zfsonlinux/zfs/issues/4177似乎 zfs 卷类型实际上已经可以支持命名空间之间的这种 uid/gid 映射,但没有公开这样做的控件。
大多数使用 Docker 的人用于开发/CI,我们使用“通用”图像,例如 php/nginx(运行程序)或 gradle/python(构建器),因此一个好的解决方案将:
既然我们可以很容易地决定一个卷的写权限(X:X:OPTION_READ_WRITE),那么同样的方式添加所有者呢?
SOURCE:TARGET:RW:OWNER
我有同样的问题。 可能有一个最佳实践,而我的方法是_不_它:
version: '3.5'
services:
something:
image: someimage
user: '1000'
expose:
- 8080
volumes:
- dev:/app
volumes:
dev:
这会导致EACCES: permission denied, access '/app'
。
我们应该怎么做? 即定义一个新卷并能够使用非 root 用户访问它?
嗨@Redsandro
最好在someimage
Docker 映像中将/app
的 UID 设置为 1000。 或者,如果您无法控制它,您应该在您的撰写文件中使用user: ...
图像作者打算使用的 UID 或 GID。
当然,如果镜像的作者使用了 UID 0 并且您不想以 root 身份运行该服务(并且它可以以非特权用户身份运行),那么向 Docker 镜像作者提出问题。
@Redsandro检查解决方法: Docker 和 --userns-remap,如何管理卷权限以在主机和容器之间共享数据?
因为这不是 docker 管理的东西,所以您可以在相关容器开始之前创建另一个容器来配置您的卷,例如使用https://hub.docker.com/r/hasnat/volumes-provisioner
version: '2'
services:
volumes-provisioner:
image: hasnat/volumes-provisioner
environment:
PROVISION_DIRECTORIES: "65534:65534:0755:/var/data/prometheus/data"
volumes:
- "/var/data:/var/data"
prometheus:
image: prom/prometheus:v2.3.2
ports:
- "9090:9090"
depends_on:
- volumes-provisioner
volumes:
- "/var/data/prometheus/data:/prometheus/data"
我不明白为什么 Docker 没有修复这个问题,我同意我们可以出于开发目的对其进行 hack,但是在生产中,没有办法!
恕我直言, podman 能够以(非特权)用户身份运行(另请参见此处),并且可能会解决此问题。 有人也在研究compose 解决方案,Podman API 在大多数情况下有意与 Docker 兼容。
[podman] 可能会帮助这里的一些人,因为它在很大程度上与 Docker 兼容。
完全同意,不幸的是 podman 在 Mac 上不起作用
完全同意,不幸的是 podman 在 Mac 上不起作用
好吧,恕我直言,Docker 和 podman 都不会在本地运行。 但是 OS/X 上的 Docker 安装很好地隐藏了虚拟机的东西。
但我同意手动设置 VM 以获得适当的开发系统可能会很痛苦。
不过,这里有点离题了。
我不再是 OS/X 用户,但我刚刚看到有一个 _experimental_ podman dmg 。
我猜在不久的将来可能会发展出一些类似的生态系统,因为已经可以访问 podman programmatic甚至podman-compose 。
当在共享集群上没有 sudo 权限的用户不小心通过 docker-compose 创建了一些由 root 拥有的文件夹,然后他们甚至无法自己删除这些文件夹时,这尤其成为一个问题。
我也遇到了这个问题。 我们正在尝试按照jpetazzo 的帖子中的指导在docker 。
我们希望从 docker-compose 文件启动这个容器,并且能够将宿主机的 docker socket 挂载到 docker 组下的容器机器中。 这样我们就可以使用 root 以外的其他用户从容器内部运行 docker。
目前,由于我无法指定绑定挂载文件的所有权和权限,因此无法实现。
把我的两分钱放在这里:
对于 docker-compose "native" 解决方案,我这样做了,因为大多数人已经在他们的图像库中拥有 alpine:
volumes:
media:
services:
mediainit:
image: alpine
entrypoint: /bin/sh -c "chown -v nobody:nogroup /mnt/media && chmod -v 777 /mnt/media"
container_name: mediainit
restart: "no"
volumes:
- "media:/mnt/media"
当然不是最安全的方法,但只有被授予容器特权的容器才会看到它,所以这没什么大不了的,但是如果您的内核支持,您可以轻松地将其设为 chown user:user或做一些 setacl 幻想它。
编辑:似乎您必须在 chmod 之前先 chown 文件夹,否则它不会“粘住”,至少在我的测试中是这样。
卷所有权不受 docker-compose 的控制,所以这个讨论应该在 moby 项目 repo 下进行。
对于在此处寻找解决方法的人的一些说明:
Docker 卷,当第一次被容器使用时,会从容器继承它的初始内容和权限。 这意味着您可以像这样配置您的图像:
#Dockerfile
FROM alpine
RUN addgroup -S nicolas && adduser -S nicolas -G nicolas
RUN mkdir /foo && chown nicolas:nicolas /foo
# empty, but owned by `nicolas`. Could also have some initial content
VOLUME /foo
USER nicolas
这样的图像在没有显式卷(将使用随机 ID 有意创建)或使用尚不存在的命名卷运行时,将“传播”对卷的权限:
➜ docker run --rm -it -v some_new_volume:/foo myimage
/ $ ls -al /foo
total 8
drwxr-xr-x 2 nicolas nicolas 4096 Oct 18 08:30 .
drwxr-xr-x 1 root root 4096 Oct 18 08:30 ..
使用在撰写文件中声明的卷时同样适用:
#docker-compose.yml
version: "3"
services:
web:
image: myimage
command: ls -al /foo
volumes:
- db-data:/foo
volumes:
db-data:
➜ docker-compose up
Creating volume "toto_db-data" with default driver
Creating toto_web_1 ... done
Attaching to toto_web_1
web_1 | total 8
web_1 | drwxr-xr-x 2 nicolas nicolas 4096 Oct 18 08:30 .
web_1 | drwxr-xr-x 1 root root 4096 Oct 18 08:37 ..
toto_web_1 exited with code 0
如果您重新附加已被另一个容器使用的卷,这将不起作用。 更改卷所有权或在创建时对其进行控制必须由引擎或具有某些特定选项的卷驱动程序实现。 否则你将不得不依赖一些chown
建议的技巧^。
希望这可以帮助。
我正在关闭此问题,因为 compose 无法控制卷创建,但已公开引擎 API。
最有用的评论
其实我是带着消息来到这里的,看起来我想要实现的目标是可行的,但我不知道这是一个特性还是一个错误。 这就是我改变的原因:
在我的Dockerfile 中,_before 更改为用户 'postgres'_ 我添加了这些:
这样做是创建一个目录 /volume_data 并更改其权限,以便用户 'postgres' 可以在其上写入。
这是Dockerfile部分。
现在我没有对docker-compose.yml 进行任何更改:所以
directory_name_db-data
并将其挂载到/volume_data
并且权限仍然存在! .这意味着现在,我将我的命名卷安装在 _pre-existing_ 目录
/volume_data
,并保留了权限,因此“postgres”可以写入它。那么,如果这是预期的行为还是违反安全? (不过,在这种情况下,它确实为我服务!)