关于share:如何在构建期间将主机卷装载到Dockerfile中的docker容器中

How to mount host volumes into docker containers in Dockerfile during build

原始问题:如何在dockerfile中使用volume指令?

我要解决的实际问题是--在构建期间如何将主机卷装入dockerfile中的docker容器,即在docker build期间具有docker run -v /export:/export功能。

对我来说,背后的原因是,在Docker中构建东西时,我不希望那些(apt-get install个)缓存锁定在单个Docker中,而是共享/重用它们。这就是我问这个问题的主要原因。

最新更新:

在Docker v18.09之前,正确的答案应该是从以下内容开始的:

There is a way to mount a volume during a build, but it doesn't involve Dockerfiles.

然而,这是一个不恰当的、有组织的和支持的答案。当我重新安装docker contains时,我偶然发现了以下文章:

Dockerize apt缓存服务https://docs.docker.com/engine/examples/apt-cacher-ng/

这是码头工人对这个/我的问题的解决办法,不是直接的,而是间接的。这是道克建议我们做的正统做法。我承认这比我在这里要问的要好。

另一种方法是,新接受的答案,例如v18.09中的buildKit。

选择适合你的。

曾经是:有一个解决方案——rocker,它不是来自docker,但是现在rocker已经停止了,我再次把答案回复到"不可能"。

旧更新:所以答案是"不可能"。我可以接受它作为答案,因为我知道这个问题已经在https://github.com/docker/docker/issues/3156上广泛讨论过。我可以理解,可移植性是Docker开发人员的首要问题;但是作为Docker用户,我不得不说我对这个缺失的特性非常失望。让我引用前面讨论中的一句话来结束我的论点:"我希望使用Gentoo作为基础图像,但绝对不希望在构建图像后,超过1GB的移植树数据出现在任何层中。"如果不是因为安装过程中图像中必须出现巨大的移植树,您可以有一些很好的紧凑的容器。"是的,我可以使用wget或curl下载我需要的任何内容,但事实上,现在仅仅考虑移植性就迫使我在每次构建Gentoo基础图像时下载超过1GB的移植树,这就不在效率高,不易使用。此外,包存储库将始终位于/usr/portage下,因此在gentoo下始终是可移植的。我再次尊重这个决定,但请允许我同时表达我的失望。谢谢。

原始问题详情:

通过卷共享目录http://docker.readthedocs.org/en/v0.7.3/use/workingu与卷/

它说数据卷功能"从Docker远程API的版本1开始就可用"。我的Docker是1.2.0版,但我发现上面文章中给出的示例不起作用:

1
2
3
4
5
# 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容器的正确方法是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
$ 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指令告诉docker要安装什么。这将严重破坏可移植性。此指令告诉Docker,这些目录中的内容不在图像中,可以使用--volumes-from命令行参数从其他容器访问。必须使用-v /path/on/host:/path/in/container运行容器才能从主机访问目录。

无法在生成期间装载主机卷。没有特权构建和装载主机也会严重降低可移植性。您可能想尝试使用wget或curl下载构建所需的任何内容并将其放置到位。


更新:有人只是不把不当作答案,我非常喜欢它,尤其是这个特别的问题。

好消息,现在有办法了--

解决方案是rocker:https://github.com/grammarly/rocker

John Yani说,"在我看来,它解决了dockerfile的所有弱点,使其适合开发。"

摇杆

https://github.com/grammarly/rocker网站

By introducing new commands, Rocker aims to solve the following use cases, which are painful with plain Docker:

  • Mount reusable volumes on build stage, so dependency management tools may use cache between builds.
  • Share ssh keys with build (for pulling private repos, etc.), while not leaving them in the resulting image.
  • Build and run application in different images, be able to easily pass an artifact from one image to another, ideally have this logic in a single Dockerfile.
  • Tag/Push images right from Dockerfiles.
  • Pass variables from shell build command so they can be substituted to a Dockerfile.
  • And more. These are the most critical issues that were blocking our adoption of Docker at Grammarly.

    更新:根据Github上的官方项目报告,Rocker已经停产。

    As of early 2018, the container ecosystem is much more mature than it was three years ago when this project was initiated. Now, some of the critical and outstanding features of rocker can be easily covered by docker build or other well-supported tools, though some features do remain unique to rocker. See https://github.com/grammarly/rocker/issues/199 for more details.


    首先,回答"为什么VOLUME不起作用?"在dockerfile中定义VOLUME时,只能定义目标,不能定义卷的源。在生成过程中,您只能从中获取匿名卷。匿名卷将安装在每个RUN命令上,并预先填充图像内容,然后在RUN命令结束时丢弃。只保存对容器的更改,而不保存对卷的更改。

    由于这个问题已经被提出,一些功能已经发布,可能会有所帮助。首先是多阶段构建,它允许您在第一阶段构建效率低下的磁盘空间,并将所需的输出复制到所发送的最后一个阶段。第二个特性是buildKit,它显著地改变了图像的构建方式,并在构建中添加了新的功能。

    对于多阶段构建,您将拥有多个FROM行,每个行开始创建单独的图像。默认情况下,只标记最后一个图像,但可以从以前的阶段复制文件。标准用法是使用编译器环境来构建二进制或其他应用程序工件,并使用运行时环境作为在该工件上复制的第二个阶段。你可以:

    1
    2
    3
    4
    5
    6
    7
    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将于2009年18月退出实验。这是对构建过程的完全重新设计,包括更改前端解析器的能力。其中一个解析器更改已经实现了RUN --mount选项,它允许您为运行命令挂载一个缓存目录。例如,这里有一个安装了一些debian目录的目录(通过重新配置debian映像,这可以加快包的重新安装速度):

    1
    2
    3
    4
    5
    6
    7
    # 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只读目录。文件夹必须存在于生成上下文中,并且未映射回主机或生成客户端:

    1
    2
    3
    4
    # 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的构建工具包。


    有一种方法可以在构建期间装载卷,但它不涉及dockerfiles。

    该技术将从您想要使用的任何基础创建一个容器(使用-v选项将您的卷装入容器中),运行一个shell脚本来完成映像构建工作,然后在完成后将容器作为映像提交。

    这样不仅可以省去您不想要的多余文件(这对安全文件也很好,比如ssh文件),还可以创建单个映像。它有缺点:commit命令不支持所有dockerfile指令,而且如果您需要编辑构建脚本,它也不允许您在停止时拾取。


    运行容器时,会在主机上创建一个目录并将其装入容器中。你可以找到这个目录

    1
    2
    $ docker inspect --format"{{ .Volumes }}" <ID>
    map[/export:/var/lib/docker/vfs/dir/<VOLUME ID...>]

    如果要从主机在容器中装入目录,则必须使用-v参数并指定目录。在您的情况下,这将是:

    1
    docker run -v /export:/export data

    所以您将使用容器中的hosts文件夹。


    我认为您可以通过一个docker命令来运行构建,该命令本身在docker容器中运行。见docker现在可以在docker docker博客中运行。使用了这样一种技术,但实际上是通过容器访问外部Docker,例如,在探索如何创建最小的Docker容器Xebia博客时。

    另一篇相关文章是优化docker images centurylink labs,它解释了如果在构建过程中下载内容,可以通过一个运行步骤下载、构建和删除下载来避免在最终图像中浪费空间。


    很难看,但我的外表是这样的:

    Dockerfile:

    1
    2
    3
    FROM foo
    COPY ./m2/ /root/.m2
    RUN stuff

    图像:

    1
    2
    3
    4
    5
    docker build . -t barImage
    container="$(docker run -d barImage)"
    rm -rf ./m2
    docker cp"$container:/root/.m2" ./m2
    docker rm -f"$container"

    我有一个Java构建,将宇宙下载到/root / m2,并且每次都这样做。生成后,imageBuild.sh将该文件夹的内容复制到主机上,Dockerfile将其复制回映像,以便下次生成。

    这类似于一个卷的工作方式(即在构建之间保持)。