多个FROM - 它意味着什么

253

我想构建一个Docker镜像,需要同时运行Neo4j数据库和Node.js。

我的第一种方法是声明一个包含Neo4j的基础镜像。参考文档没有以任何有用的方式定义"基础镜像":

基础镜像: 没有父级的镜像是基础镜像

从中我得出结论,只有当该镜像本身没有基础镜像时,才能拥有基础镜像。

但是什么是基础镜像?这是否意味着,如果我在FROM指令中声明neo4j/neo4j,那么当我的镜像运行时,neo数据库将自动运行,并在容器内的7474端口上可用?

阅读Docker参考文档,我看到:

FROM可以在单个Dockerfile中多次出现,以创建多个镜像。只需在每个新的FROM命令之前记录提交输出的最后一个镜像ID即可。

我想要创建多个镜像吗?似乎我想要的是一个包含其他镜像内容(例如neo4j和node.js)的单个镜像。

我在参考手册中没有找到声明依赖关系的指令。是否没有像RPM中那样的依赖关系,即为了运行我的镜像,调用上下文必须先安装所需的镜像?
5个回答

233
截至2017年5月,单个Dockerfile中可以使用多个FROM
请参阅"Builder pattern vs. Multi-stage builds in Docker"(作者:Alex Ellis)和PR 31257(作者:Tõnis Tiigi)。

一般的语法是在Dockerfile中添加更多的FROM - 最后一个FROM声明的是最终基础镜像。要从中间镜像复制文件和输出,请使用COPY --from=<base_image_number>

FROM golang:1.7.3 as builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go    .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app    .
CMD ["./app"]  

结果将会有两张图片,一张是建筑物的图片,另一张只包含生成的应用程序(要小得多)。

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

multi               latest              bcbbf69a9b59        6 minutes ago       10.3MB  
golang              1.7.3               ef15416724f6        4 months ago        672MB  

什么是基础镜像?

一组文件,加上 EXPOSE 端口、ENTRYPOINTCMD
您可以添加文件并基于该基础镜像构建新镜像,使用一个新的 Dockerfile 从一个 FROM 指令开始:在 FROM 后面提到的镜像是您的新镜像的 "基础镜像"。

这是否意味着如果我在 FROM 指令中声明 neo4j/neo4j,那么当我的镜像运行时,Neo 数据库将自动运行,并在容器中的 7474 端口上可用?

只有在不覆盖 CMDENTRYPOINT 的情况下才会如此。
但镜像本身就足够了:如果您必须添加与 neo4j 相关的文件以供您特定的使用,则会使用 FROM neo4j/neo4j


@rainabba 同意。传统的单体应用不容易迁移。有趣的阅读材料:https://martinfowler.com/articles/break-monolith-into-microservices.html,https://threedots.tech/post/microservices-or-monolith-its-detail/,https://hackernoon.com/aws-lambda-should-you-have-few-monolithic-functions-or-many-single-purposed-functions-8c3872d4338f - VonC
如果一个Dockerfile中有多个FROM语句,出于某种原因,它会跳过第一个并仅执行第二个。 - pfincent
@pfincent,我很想看到一个单独的问题,其中包含一个示例以及您正在使用的Docker和操作系统的版本。 - VonC

94
让我总结一下对问题和答案的理解,希望对其他人有所帮助。 问题:假设我有三个图像,苹果、香蕉和橙子。我能否有一个 Dockerfile,其中包含 FROM appleFROM bananaFROM orange,告诉 docker 将这三个应用程序神奇地合并成一个单一的图像(包含三个个别的应用程序),我可以称之为 smoothie答案不行。如果你这样做,你最终会得到四个图像,即你拉取的三个水果图像,加上基于最后一个 FROM 图像的新图像。例如,如果 FROM orange 是 Dockerfile 中的最后一个语句而没有添加任何内容,则 smoothie 图像只是 orange 图像的克隆。

为什么它们没有被合并?我真的想要它

典型的 Docker 镜像将包含应用程序运行所需的几乎所有内容(不包括内核),这通常意味着它们是从所选操作系统的基础镜像和特定版本或发行版构建的。

不考虑所有可能的发行版、文件系统、库和应用程序的情况下成功合并图像,这是 Docker 不想做的事情。相反,开发人员应该采用微服务范式,运行多个容器,根据需要彼此通信。

替代方案是什么?

图像合并的一个可能的用例是混合和匹配 Linux 发行版和我们想要的应用程序,例如 Ubuntu 和 Node.js。这不是解决方案:

FROM ubuntu
FROM node

如果我们不想使用应用程序镜像选择的Linux发行版,我们可以选择自己喜欢的发行版,并使用软件包管理器来安装应用程序,例如:
FROM ubuntu
RUN apt-get update &&\
    apt-get install package1 &&\
    apt-get install package2

但是你可能已经知道了。往往在所选的发行版中没有可用的快照或包,或者它不是所需的版本,或者它不能在docker容器中直接使用,这就是想要使用镜像的动机。据我所知,如果你真的想遵循单体应用程序的方法,唯一的选择是按照长时间的方式进行。
例如,在Node.js的情况下,您可能希望手动安装最新版本,因为apt提供的是一个古老的版本,而snap不包含在Ubuntu镜像中。对于neo4j,我们可能需要下载该软件包并手动将其添加到镜像中,根据文档和许可证。
如果大小不重要,一种策略是从最难手动安装的基础映像开始,然后在其上添加其余映像。
何时使用多个FROM指令
还有一个选项是使用多个FROM语句,并在构建阶段之间或最终阶段之间手动复制内容。换句话说,如果您知道自己在做什么,可以手动合并图像。根据文档
引入新的构建阶段时,可以通过向FROM指令添加AS name来为其命名。名称可用于后续的FROMCOPY --from=<name>指令,以引用在该阶段中构建的映像。
个人而言,我只会在使用自己的映像或遵循应用程序供应商的文档时才使用此合并方法,但如果您需要它或者感觉幸运,那么它就在那里。
但是,这种方法更好的应用是当我们实际上想要使用来自不同镜像的临时容器进行构建或执行某些操作,并在复制所需输出后将其丢弃。
例子
我想要一个只有gpgv的精简映像,并基于这个Unix & Linux答案,我安装了整个gpg,然后只复制所需的二进制文件到最终映像中。
FROM docker.io/photon:latest AS builder
RUN yum install gnupg -y

FROM docker.io/photon:latest
COPY --from=builder /usr/bin/gpgv /usr/bin/
COPY --from=builder /usr/lib/libgcrypt.so.20 /usr/lib/libgpg-error.so.0 /usr/lib/

Dockerfile 的其余部分按照通常的方式进行。


1
我觉得Docker缺少一个非常关键的功能:能够导入/扩展Dockerfile,而不仅仅是Docker镜像。在几乎所有情况下,我想做的事情似乎都是以智能的方式通过组合两个Dockerfile来构建一个镜像:基本上,将一个Dockerfile中的所有ENVRUN指令追加到另一个Dockerfile的末尾,可能在此之前先检查它们的FROM是否“相似”(相同的发行版)。如果这听起来对你来说太含糊不清,那么请考虑到许多人可能已经在做这件事,只是通过复制和粘贴而不是内置到工具中。 - undefined
请参阅这个持续了10年的讨论:https://github.com/moby/moby/issues/735 - undefined
同意有一个支持“合并”的功能会很好,或者至少尝试一下。我最初以为FROM可能是一种方法,后来意识到它不是,所以我写了这个答案来解释情况并提供其他选择。如果你有其他解决方案,我们很愿意听听。 - undefined

28

对我来说,第一个答案过于复杂、历史悠久且缺乏信息。


这实际上相当简单。Docker提供了一个名为多阶段构建的功能,基本思想是:

  • 通过强制你允许列出你所需要的内容,使你不必手动删除不需要的内容,
  • 释放因Docker实现而占用的资源。

让我们从第一个开始。通常情况下,使用Debian之类的东西,你会看到:

RUN apt-get update \ 
  && apt-get dist-upgrade \
  && apt-get install <whatever> \
  && apt-get clean

我们可以用上述内容来解释这一切。上述命令被链接在一起,因此它代表了一个单一的更改,不需要中间的图像。如果它是这样写的,
RUN apt-get update ;
RUN apt-get dist-upgrade;
RUN apt-get install <whatever>;
RUN apt-get clean;

这将导致多出3个临时中间映像。将其减少到一个映像后,仍然存在一个问题: apt-get clean 不会清除安装过程中使用的工件。如果Debian维护者在其安装中包含修改系统的脚本,则该修改也将存在于最终解决方案中(例如,参见类似pepperflashplugin-nonfree的内容)。
通过使用多阶段构建,您可以获得单个更改操作的所有好处,但需要手动允许白名单并使用此处记录的COPY --from语法复制引入临时映像的文件。此外,在没有替代方案(例如apt-get clean)且否则最终映像中会有大量不必要的文件的情况下,这是一个很好的解决方案。
另请参见
- Multi-stage builds - COPY syntax

13
谢谢,但我不明白你如何解决我的问题。对我而言,“FROM”是一种继承机制,使用多个指令意味着我可以从多个父类继承。在你的回答中,你没有提到“FROM”或利用他人软件包装的概念。 - ekkis
2
也许这就是混淆的原因。FROM主要是一个命名空间声明。那里的限定词更像是扩展而不是继承。您可以声明多个命名空间。每个命名空间都可以扩展另一个命名空间。如果其他答案适合您,请坚持使用它。 - Evan Carroll
@ekkis 您不使用 FROM 继承第一个映像,而是至少 据我所理解 ,您可以从多个映像中复制所需的目录。我不知道如何找出此答案提到的每行图像 ID,但应该可以在网络上找到。无论如何,多阶段构建 链接与第一个答案相同。 - questionto42

7

这里可能是使用多个FROM的最基本用例之一,也称为多阶段构建。

我希望有一个dockerfile文件,并且我想更改一个单词,根据我设置该单词,我可以获得不同的图像,具体取决于我是否要运行、开发或发布应用程序!

运行 - 我只想运行应用程序

开发 - 我想编辑代码并运行应用程序

发布 - 在生产环境中运行应用程序

假设我们在dotnet环境下工作。这是一个单一的Dockerfile。如果没有多阶段构建,将会有多个文件(构建器模式)。

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/runtime:5.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY ["ConsoleApp1/ConsoleApp1.csproj", "ConsoleApp1/"]
RUN dotnet restore "ConsoleApp1/ConsoleApp1.csproj"
COPY . .
WORKDIR "/src/ConsoleApp1"
RUN dotnet build "ConsoleApp1.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "ConsoleApp1.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ConsoleApp1.dll"]

想要运行应用程序吗?请将上面的dockerfile中FROM base AS final保留不变。

想要在容器中开发源代码吗?请将相同的那一行改为FROM build AS final

想要发布到生产环境吗?请将相同的那一行改为FROM publish AS final


0

我同意帖子的观点,这个功能对于Docker非常有用!以下是对同一问题的不同看法:

如果您有多个FROM(或者例如一个“FROM”和多个“MERGE”),那么可以使用Docker注册表版本控制系统来获取基本Docker映像和其他容器元素,这是胜利的:我有第三方开发工具,这些工具不存在于.deb格式中,因此必须通过解压tball进行安装,并且非常庞大,因此在docker主机上进行缓存非常重要,但是镜像的版本控制/更改控制也同样重要。 我认为我可以简单地使用“RUN git ....”,docker将为我处理新层的缓存,这正是我想要的; 因为另一个容器将具有相同的基本映像但不同的巨大第三方工具集,因此基本映像和工具映像的缓存非常重要(第三方工具tar可能与ubuntu的基本映像一样大,因此这些的缓存也非常重要)。 (建议的)功能只允许在中央repo中管理所有这些元素。版本控制系统。

换个说法,为什么我们要使用FROM呢?如果我只是使用RUN命令来克隆一个Ubuntu镜像作为我的“基础镜像/层”,这将创建一个新的层,并且Docker会缓存它...那么使用FROM有什么区别/优势呢,除了它使用了Docker的内部版本控制系统/语法之外?

这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - MD. RAKIB HASAN

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接