简介

本文将涵盖Dockerfile最佳实践的五个方面,以帮助您编写更好的Dockerfiles:增量构建时间减少image大小可维护性安全性和可重复性

增量构建时间

在开发周期中,在构建Docker镜像,进行代码更改,然后重建时,利用缓存非常重要。缓存有助于避免在不需要时再次运行构建步骤。

提示#1:订单对缓存很重要




dockerfile CMD执行可执行文件 dockerfile cmd run_应用程序


但是,构建步骤(Dockerfile指令)的顺序很重要,因为当通过更改文件或修改Dockerfile中的行来使步骤的缓存无效时,其缓存的后续步骤将会中断。从最少到最频繁更改的步骤订购步骤以优化缓存。

提示#2:更具体的COPY限制缓存失败


dockerfile CMD执行可执行文件 dockerfile cmd run_Dockerfile_02


只复制所需的内容。如果可能,请避免“复制”。将文件复制到image中时,请确保您对要复制的内容非常具体。对正在复制的文件的任何更改都将破坏缓存。在上面的示例中,image中只需要预先构建的jar应用程序,因此只能复制它。这样不相关的文件更改不会影响缓存。

提示#3:识别可缓存单元,例如apt-get update&install


dockerfile CMD执行可执行文件 dockerfile cmd run_应用程序_03


每个RUN指令都可以看作是可缓存的执行单元。其中太多可能是不必要的,而将所有命令链接到一个RUN指令可能会轻易破坏缓存,从而损害开发周期。从包管理器安装包时,您总是希望更新索引并在同一个RUN中安装包:它们一起形成一个可缓存单元。否则您可能会安装过时的软件包。

减少image大小

image大小可能很重要,因为较小的image等于更快的部署和较小的攻击面。

提示#4:删除不必要的依赖项


dockerfile CMD执行可执行文件 dockerfile cmd run_应用程序_04


删除不必要的依赖项,不要安装调试工具。如果需要,以后可以随时安装调试工具。某些包管理器(如apt)会自动安装由用户指定的包推荐的包,从而不必要地增加占用空间。Apt具有-no-install-recommendations 标志,可确保不安装实际不需要的依赖项。如果需要,请明确添加。

提示#5:删除包管理器缓存


dockerfile CMD执行可执行文件 dockerfile cmd run_缓存_05


包管理器维护自己的缓存,这可能最终出现在image中。处理它的一种方法是在安装包的相同RUN指令中删除缓存。在另一个RUN指令中删除它不会减小image大小。

还有其他方法可以减少image大小,例如多阶段构建,这将在本博文末尾介绍。Ť 他旁边设置的最佳实践将看看我们如何能够优化可维护性,安全性,以及Dockerfile的重复性。

可维护性

提示#6:尽可能使用官方image


dockerfile CMD执行可执行文件 dockerfile cmd run_应用程序_06


官方image可以节省大量维护时间,因为所有安装步骤都已完成并且应用了最佳实践。如果您有多个项目,他们可以共享这些图层,因为它们使用完全相同的基本image。

提示#7:使用更具体的tag


dockerfile CMD执行可执行文件 dockerfile cmd run_应用程序_07


不要使用最新的tag。它具有始终可用于Docker Hub上的官方image的便利性,但随着时间的推移可能会发生重大变化。根据您在没有缓存的情况下重建Dockerfile的时间间隔,您可能会有失败的构建。

相反,请为基本image使用更具体的tag。在这种情况下,我们使用openjdk。还有更多可用的tag,因此请查看 该image的Docker Hub文档,其中列出了所有现有的变体。

提示#8:寻找最小的size


dockerfile CMD执行可执行文件 dockerfile cmd run_应用程序_08


这些tag中的一些具有最小的size,这意味着它们甚至是更小的image。纤薄的变体基于剥离的Debian,而alpine变体则基于更小的Alpine Linux分布image。一个显着的区别是debian仍然使用GNU libc而alpine使用musl libc虽然小得多,但在某些情况下可能会导致兼容性问题。在openjdk的情况下,jre风格只包含java运行时,而不是sdk; 这也大大减少了image尺寸。

可重复性

到目前为止,上面的Dockerfiles假设你的jar工件是在主机上构建的。这并不理想,因为您失去了容器提供的一致环境的好处。例如,如果您的Java应用程序依赖于特定库,则可能会引入不受欢迎的不一致,具体取决于构建应用程序的计算机。

提示#9:在一致的环境中从源代码构建

源代码是您想要构建Docker镜像的真实来源。Dockerfile只是蓝图。


dockerfile CMD执行可执行文件 dockerfile cmd run_应用程序_09


您应该首先确定构建应用程序所需的所有内容。我们简单的Java应用程序需要Maven和JDK,所以让我们的Dockerfile基于Docker Hub中包含JDK的特定最小官方maven image。如果需要安装更多依赖项,可以在RUN步骤中执行此操作。

pom.xml和src文件夹将被复制,因为它们是生成app.jar应用程序的最终RUN步骤所需的。(-e标志显示错误,-B以非交互式名称“批处理”模式运行)。mvn package

我们解决了不一致的环境问题,但引入了另一个问题:每次更改代码时,都会获取pom.xml中描述的所有依赖项。因此下一个提示。

提示#10:在单独的步骤中获取依赖项


dockerfile CMD执行可执行文件 dockerfile cmd run_缓存_10


通过再次考虑可缓存的执行单元,我们可以决定获取依赖项是一个单独的可缓存单元,只需要依赖于对pom.xml而不是源代码的更改。两个COPY步骤之间的RUN步骤告诉Maven只获取依赖项。

通过在一致的环境中构建引入了另外一个问题:我们的image比以前更大,因为它包含运行时不需要的所有构建时依赖项。

提示#11:使用多阶段构建来删除构建依赖项(推荐的Dockerfile)


dockerfile CMD执行可执行文件 dockerfile cmd run_应用程序_11


多阶段构建可由多个FROM语句识别。每个FROM开始一个新的阶段。它们可以用AS关键字命名,我们用它来命名我们的第一阶段“构建器”,以便稍后引用。它将在一致的环境中包含所有构建依赖项。

第二阶段是我们的最后阶段,将产生最终image。它将包括运行时的严格必要条件,在本例中是基于Alpine的最小JRE(Java运行时)。中间构建器阶段将被缓存但不会出现在最终image中。为了将构建工件添加到最终image中,请使用。在这种情况下,STAGE_NAME是构建者。COPY --from=STAGE_NAME


dockerfile CMD执行可执行文件 dockerfile cmd run_Dockerfile_12


多阶段构建是消除构建时依赖性的首选解决方案。