原始问题:如何在Dockerfile中使用VOLUME指令?
我想解决的实际问题是 - 如何在构建期间将主机卷装入Dockerfile中的docker容器,即在docker run -v /export:/export
期间具有docker build
功能。
对我而言,背后的原因是在Docker中构建东西时,我不希望将这些(apt-get install
)缓存锁定在单个docker中,而是分享/重用它们。这是我问这个问题的主要原因。
最近更新:
在docker v18.09之前,正确的答案应该是以以下内容开头的答案:
有一种方法可以在构建期间安装卷,但它不涉及Dockerfiles。
然而,这是一个声明不清,有组织和支持的答案。当我重新安装我的docker包含时,我碰巧偶然发现了以下文章:
Dockerize apt-cacher-ng服务 https://docs.docker.com/engine/examples/apt-cacher-ng/
这是码头工人对这个/我的问题的解决方案,不是直接而是间接的。这是码头工人建议我们做的正统方式。而且我承认它比我试图在这里问的更好。
另一种方法是,新接受的答案,例如v18.09中的Buildkit。
挑选适合你的人。
是的:有一个解决方案 - 摇杆,不是来自Docker,但现在摇杆停止了,我将答案又回到了“不可能”。
旧更新:答案是“不可能”。我可以接受它作为答案,因为我知道这个问题已经在https://github.com/docker/docker/issues/3156进行了广泛的讨论。我可以理解,可移植性对于docker开发者来说是一个至关重要的问题;但作为码头用户,我不得不说我对这个缺失的功能感到非常失望。让我通过前面讨论的引用来结束我的论点:“我想使用Gentoo作为基本图像,但是一旦构建了图像,绝对不希望> 1GB的Portage树数据存在于任何层中。如果在安装过程中没有巨大的portage树必须出现在图像中,那么可能会有一些不错的紧凑型容器。“是的,我可以使用wget或curl来下载我需要的任何东西,但是现在仅仅是一个可移植性考虑因此迫使我每次构建Gentoo基本映像时下载> 1GB的Portage树既不高效也不用户友好。此外,软件包存储库将始终位于/ usr / portage下,因此在Gentoo下始终是PORTABLE。我再一次尊重这个决定,但请允许我在同一时间表达我的失望。谢谢。
原始问题详情:
从
通过卷共享目录 http://docker.readthedocs.org/en/v0.7.3/use/working_with_volumes/
它表示数据卷功能“自Docker Remote API版本1以来已经可用”。我的docker是版本1.2.0,但我发现上面文章中给出的示例不起作用:
# BUILD-USING: docker build -t data .
# RUN-USING: docker run -name DATA data
FROM busybox
VOLUME ["/var/volume1", "/var/volume2"]
CMD ["/usr/bin/true"]
Dockerfile中通过VOLUME命令将主机挂载的卷挂载到docker容器中的正确方法是什么?
$ apt-cache policy lxc-docker
lxc-docker:
Installed: 1.2.0
Candidate: 1.2.0
Version table:
*** 1.2.0 0
500 https://get.docker.io/ubuntu/ docker/main amd64 Packages
100 /var/lib/dpkg/status
$ cat Dockerfile
FROM debian:sid
VOLUME ["/export"]
RUN ls -l /export
CMD ls -l /export
$ docker build -t data .
Sending build context to Docker daemon 2.56 kB
Sending build context to Docker daemon
Step 0 : FROM debian:sid
---> 77e97a48ce6a
Step 1 : VOLUME ["/export"]
---> Using cache
---> 59b69b65a074
Step 2 : RUN ls -l /export
---> Running in df43c78d74be
total 0
---> 9d29a6eb263f
Removing intermediate container df43c78d74be
Step 3 : CMD ls -l /export
---> Running in 8e4916d3e390
---> d6e7e1c52551
Removing intermediate container 8e4916d3e390
Successfully built d6e7e1c52551
$ docker run data
total 0
$ ls -l /export | wc
20 162 1131
$ docker -v
Docker version 1.2.0, build fa7b24f
首先,回答“为什么VOLUME
不起作用?”在Dockerfile中定义VOLUME
时,只能定义目标,而不能定义卷的源。在构建期间,您只能从中获取匿名卷。将在每个RUN
命令上安装匿名卷,预先填充图像内容,然后在RUN
命令结束时丢弃。仅保存对容器的更改,而不保存对卷的更改。
由于已经提出了这个问题,因此发布了一些可能有用的功能。首先是多阶段构建,允许您在第一阶段构建低效的磁盘空间,并将所需的输出复制到您发布的最终阶段。第二个功能是Buildkit,它可以显着改变图像的构建方式,并将新功能添加到构建中。
对于多阶段构建,您将拥有多个FROM
行,每个行开始创建单独的图像。默认情况下,仅标记最后一个图像,但您可以复制前一个阶段的文件。标准用途是具有构建二进制或其他应用程序工件的编译器环境,以及作为复制该工件的第二阶段的运行时环境。你可以有:
FROM debian:sid as builder
COPY export /export
RUN compile command here >/result.bin
FROM debian:sid
COPY --from=builder /result.bin /result.bin
CMD ["/result.bin"]
这将导致构建仅包含生成的二进制文件,而不是完整/导出目录。
Buildkit将于18.09年开始实验。它是构建过程的完全重新设计,包括更改前端解析器的能力。其中一个解析器更改已实现了RUN --mount
选项,该选项允许您为运行命令安装缓存目录。例如。这是一个安装一些debian目录的程序(重新配置debian映像,这可以加速重新安装包):
# syntax = docker/dockerfile:experimental
FROM debian:latest
RUN --mount=target=/var/lib/apt/lists,type=cache \
--mount=target=/var/cache/apt,type=cache \
apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
git
您可以为您拥有的任何应用程序缓存调整缓存目录,例如maven为$ HOME / .m2,golang为/root/.cache。
TL; DR:答案在这里:使用RUN --mount
语法,您还可以绑定构建上下文中的mount只读目录。该文件夹必须存在于构建上下文中,并且不会映射回主机或构建客户端:
# syntax = docker/dockerfile:experimental
FROM debian:latest
RUN --mount=target=/export,type=bind,source=export \
process export directory here...
请注意,因为目录是从上下文安装的,所以它也是以只读方式挂载的,并且您无法将更改推送回主机或客户端。在构建时,您需要18.09或更高版本的安装并启用export DOCKER_BUILDKIT=1
构建工具包。
无法使用VOLUME
指令告诉docker要挂载什么。这将严重破坏可移植性。该指令告诉docker这些目录中的内容不会进入图像,并且可以使用--volumes-from
命令行参数从其他容器访问。您必须使用-v /path/on/host:/path/in/container
运行容器才能从主机访问目录。
无法在构建期间安装主机卷。没有特权构建,安装主机也会严重降低可移植性。您可能希望尝试使用wget或curl下载构建所需的任何内容并将其放置到位。
更新:有人不会不接受答案,我非常喜欢它,特别是对于这个特殊的问题。
好消息,现在有办法 -
解决方案是Rocker:https://github.com/grammarly/rocker
John Yani said,“IMO,它解决了Dockerfile的所有弱点,使其适合开发。”
https://github.com/grammarly/rocker
通过引入新命令,Rocker旨在解决以下用例,这对于简单的Docker来说很痛苦:
- 在构建阶段挂载可重用卷,因此依赖关系管理工具可以在构建之间使用缓存。
- 与构建共享ssh密钥(用于提取私有存储库等),而不将它们留在生成的图像中。
- 在不同的映像中构建和运行应用程序,能够轻松地将工件从一个映像传递到另一个映像,理想情况下在单个Dockerfile中具有此逻辑。
- 直接从Dockerfiles标记/推送图像。
- 从shell构建命令传递变量,以便可以将它们替换为Dockerfile。
和更多。这些是阻碍我们在Grammarly采用Docker的最关键问题。
截至2018年初,集装箱生态系统比三年前该项目启动时要成熟得多。现在,摇杆的一些关键和突出的功能可以通过码头构建或其他支持良好的工具轻松覆盖,尽管某些功能仍然是摇杆独有的。有关详细信息,请参阅https://github.com/grammarly/rocker/issues/199。
有一种方法可以在构建期间安装卷,但它不涉及Dockerfiles。
该技术将来自您想要使用的任何基础的create a container(使用-v
选项将容积安装在容器中),运行shell脚本来完成图像构建工作,然后将commit the container作为图像完成。
这不仅会遗漏你不想要的多余文件(这对于安全文件也很好,比如SSH文件),它也会创建一个单独的图像。它有缺点:commit命令不支持所有Dockerfile指令,如果你需要编辑你的构建脚本,它就不会让你在中断时接听。
更新:
例如,
CONTAINER_ID=$(docker run -dit ubuntu:16.04)
docker cp build.sh $CONTAINER_ID:/build.sh
docker exec -t $CONTAINER_ID /bin/sh -c '/bin/sh /build.sh'
docker commit $CONTAINER_ID $REPO:$TAG
docker stop $CONTAINER_ID
在运行容器时,会创建主机上的目录并将其装入容器中。你可以找到它的目录
$ docker inspect --format "{{ .Volumes }}" <ID>
map[/export:/var/lib/docker/vfs/dir/<VOLUME ID...>]
如果要从容器中的主机安装目录,则必须使用-v
参数并指定目录。在你的情况下,这将是:
docker run -v /export:/export data
因此,您将使用容器内的hosts文件夹。
我认为您可以通过docker命令运行构建来执行您想要做的事情,该命令本身在docker容器中运行。见Docker can now run within Docker | Docker Blog。例如,在探索如何使用Create the smallest possible Docker container | Xebia Blog时,使用了这样的技术,但实际上从容器中访问了外部的docker。
另一篇相关文章是Optimizing Docker Images | CenturyLink Labs,它解释了如果你最终在构建期间下载了东西,你可以通过在一个RUN步骤中下载,构建和删除下载来避免在最终图像中浪费空间。
这很难看,但我这样表达了这样的表象:
Dockerfile:
FROM foo
COPY ./m2/ /root/.m2
RUN stuff
image build.是:
docker build . -t barImage
container="$(docker run -d barImage)"
rm -rf ./m2
docker cp "$container:/root/.m2" ./m2
docker rm -f "$container"
我有一个java版本,可以将Universe下载到/root/.m2中,并且每次都这样做。 imageBuild.sh
在构建之后将该文件夹的内容复制到主机上,并且Dockerfile
将它们复制回映像以进行下一次构建。
这就像卷如何工作(即它在构建之间持续存在)。
这是使用构建和提交的两步方法的简化版本,没有shell脚本。它涉及:
通过相对较小的更改,附加步骤仅为构建时间添加了几秒钟。
基本上:
docker build -t image-name . # your normal docker build
# Now run a command in a throwaway container that uses volumes and makes changes:
docker run -v /some:/volume --name temp-container image-name /some/post-configure/command
# Replace the original image with the result:
# (reverting CMD to whatever it was, otherwise it will be set to /some/post-configure/command)
docker commit --change="CMD bash" temp-container image-name
# Delete the temporary container:
docker rm temp-container
在我的用例中,我想预先生成一个maven toolchains.xml文件,但我的许多JDK安装都在一个在运行时才可用的卷上。我的一些图像与所有JDKS都不兼容,因此我需要在构建时测试兼容性并有条件地填充toolchains.xml。请注意,我不需要图像可移植,我不会将其发布到Docker Hub。