我正在检查 docker 文档,了解如何使用命名卷在容器之间共享数据。 在 Populate a volume using a container明确规定:
If you start a container which creates a new volume, as above, and the container has files or directories in the directory to be mounted (such as /app/ above), the directory’s contents are copied into the volume. The container then mounts and uses the volume, and other containers which use the volume also have access to the pre-populated content.
所以我做了一个简单的例子:
- 我启动一个容器,该容器创建卷并将其安装到包含现有文件的目录
- 我启动了第二个容器,我可以在其上安装卷,实际上我可以看到第一个容器的文件。
到目前为止一切顺利。 不过,我想看看是否可以从多个容器中预先填充内容。 我所做的是
- 创建两个简单的镜像,它们各自的配置文件位于同一目录中
FROM alpine:latest
WORKDIR /opt/test
RUN mkdir -p "/opt/test/conf" && \
echo "container from image 1" > /opt/test/conf/config_1.cfg
FROM alpine:latest
WORKDIR /opt/test
RUN mkdir -p "/opt/test/conf" && \
echo "container from image 2" > /opt/test/conf/config_2.cfg
- 创建一个 docker compose,定义一个安装在两个服务上的命名卷
services:
test_container_1:
image:
test_image_1
volumes:
- test_volume:/opt/test/conf
tty: true
test_container_2:
image:
test_image_2
volumes:
- test_volume:/opt/test/conf
tty: true
volumes:
test_volume:
- 已启动服务。
> docker-compose -p example up
Creating network "example_default" with the default driver
Creating volume "example_test_volume" with default driver
Creating example_test_container_2_1 ... done
Creating example_test_container_1_1 ... done
Attaching to example_test_container_1_1, example_test_container_2_1
根据日志,container_2 首先被创建并预填充了该卷。然而,该卷随后被挂载到了container_1,挂载上唯一可用的文件显然是/opt/test/conf/config_2.cfg
,有效地删除了config_1。
所以我的问题是,是否可以使用来自 2 个或更多容器的数据填充一个卷。
我想探索这一点的原因是,我可以从不同的容器加载额外的应用程序配置,以支持 Multi-Tenancy 场景,而无需重新设计应用程序以从不同的文件夹读取租户配置。
提前谢谢
最佳答案
一旦命名卷中有任何内容,Docker 就永远不会自动将内容复制到其中。它不会合并来自两个不同图像的内容,不会在其中一个图像发生更改时更新音量,也不会发生其他任何情况。
我建议您忽略 Docker 文档中引用的段落。假设您安装到容器中的任何卷最初都是空的。这与 Docker 绑定(bind)挂载(主机目录)、Kubernetes 持久卷以及除 Docker 命名卷之外的任何其他类型的存储所获得的行为相匹配。不要在镜像中的内容上安装卷。
如果可以的话,重组您的应用程序以避免共享文件。例如,我看到的命名卷的一种常见用途是尝试将静态 Assets 重新发布到反向代理;您可以将静态资源COPY
到专用的Web 服务器镜像中,而不是尝试使用命名卷(它永远不会自行更新)。这避免了尝试在此处使用卷的各种复杂性。
如果您确实在这件事上别无选择,那么您可以在两个容器中使用专用代码来解决此问题。这里的基本设置是:
- 在应用程序目录之外的某个位置有一个数据目录,并将卷挂载到那里。
- 将原始文件包含在图像中不同的位置。
- 在入口点包装器脚本中,将原始文件复制到数据目录(已安装的卷)中。
为了便于论证,假设您已将应用程序安装到 /opt/test
中,数据目录将为 /etc/test
。入口点包装器脚本可以少至
#!/bin/sh
# Copy config files from the application tree into the config tree
# (overwriting anything that's already there)
cp /opt/test/* "$TEST_CONFIG_DIR"
# Run the main container command
exec "$@"
在 Dockerfile 中,您需要确保该目录存在(如果您使用非 root 用户,则该用户需要拥有写入该目录的权限)。
FROM alpine
WORKDIR /opt/test
COPY ./ ./
ENV TEST_CONFIG_DIR=/etc/test
RUN mkdir "$TEST_CONFIG_DIR"
ENTRYPOINT ["./entrypoint.sh"]
CMD ["./my_app"]
最后,在 Compose 设置中,将卷挂载到该数据目录上(您不能使用环境变量,但请考虑图像 API 的文件系统路径部分):
version: '3.8'
volumes:
test_config:
services:
one:
build: ./one
volumes:
- test_config:/etc/test
two:
build: ./two
volumes:
- test_config:/etc/test
例如,您可以运行:
docker-compose run one ls /etc/test
docker-compose run two ls /etc/test
查看两组文件都出现在那里。
入口点脚本是您控制的代码。除了最后的 exec "$@"行来运行主容器命令之外,没有什么特别神奇的地方。例如,如果您想忽略已经存在的文件,或者如果您有办法合并更改,那么您可以实现比简单的 cp 命令更聪明的东西。
关于docker - 使用多个容器填充卷,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72761941/