[译]渣翻:Dockerfile最佳实践

原文地址:blog.docker.com/2019/07/int…java

现在在 github 上已经有超过一百万的 Dockerfile 文件,但并不是全部的 Dockerfile 都是同个级别的。效率相当重要,本博客系列将介绍 Dockerfile 最佳实践的以下五个方面,以帮助您编写更好的 Dockerfile:递增的构建时间,镜像大小,可维护性,安全性和可重复性。若是您刚开始使用 Docker,那么这第一篇博客便很适合你。该系列的下一篇文章将会更进一步。git

重要提示:下面的提示遵循不断改进 Dockerfile 的过程,以得到基于 Maven 的示例Java 项目。 所以,最后一个 Dockerfile 是推荐的 Dockerfile,而全部中间文件只是为了说明特定的最佳实践。github

递增的构建时间

在开发周期中,构建Docker镜像,进行代码更改,而后重构的时候,利用缓存是很重要的。 缓存有助于避免在不须要时再次运行构建步骤。docker

Tip #1:顺序对缓存很重要

然而,构建步骤(Dockerfile 指令)的顺序很重要,由于当经过更改文件或修改 Dockerfile 中的行来使步骤的缓存无效时,其缓存的后续步骤将会中断。 从最少到最频繁更改的步骤进行排序以优化缓存。缓存

Tip #2:更具体的 COPY 限制缓存破灭

只复制所需的内容。 尽量的避免COPY .。将文件复制到镜像中时,请确保您要复制的内容很是具体。 对正在复制的文件,任何更改都将会破坏缓存。 在上面的示例中,镜像中只须要预先构建的 jar 应用程序,所以只需复制它。 这样不相关的文件更改不会影响缓存。安全

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

每一个RUN 指令均可以看做是可缓存的执行单元。 其中太多多是没必要要的,而将全部命令连接到一个 RUN 指令可能会轻易破坏缓存,从而损害开发周期。 从包管理器安装包时,您老是但愿更新索引并在同一个 RUN 中安装包:它们一块儿造成一个可缓存单元。 不然您可能会安装过期的软件包。app

减少镜像大小

镜像大小是很重要的,那意味着较小的镜像等于更快的部署和较小的攻击面。maven

Tip #4:移除没必要要的依赖

移除没必要要的依赖项,不要安装调试工具。 若是须要,之后能够随时安装调试工具。 某些包管理器(如 apt )会自动安装由用户指定的包推荐的包,从而没必要要地增长占用空间。 Apt 具备 -no-install-recommendations 标志,可确保不安装实际不须要的依赖项。 若是须要,请明确添加。ide

Tip #5:删除包管理器缓存

包管理器维护本身的缓存,这可能最终出如今镜像中。 处理它的一种方法是在安装包的相同RUN指令中删除缓存。 在另外一个RUN指令中删除它不会减少图像大小。工具

还有其余方法能够减小图像大小,例如多阶段构建,这将在本博文末尾介绍。 下一组最佳实践将探讨如何优化Dockerfile的可维护性,安全性和可重复性。

可维护性

Tip #6:尽量使用官方镜像

官方镜像能够节省大量维护时间,由于全部安装步骤都已完成而且应用了最佳实践。 若是您有多个项目,他们能够共享这些镜像,由于它们使用彻底相同的基本镜像。

Tip #7:使用更具体的 tag

不要使用 latest 标签。虽然它具备始终可用于Docker Hub上的官方图像的便利性,但随着时间的推移可能会发生重大变化。 根据您在没有缓存的状况下重建Dockerfile的时间间隔,您可能会构建失败。

替代的是,对您的基础镜像使用更具体的标签。在此例中,咱们使用 openjdk:8。 还有更多可用的标签,所以请查看该图像的Docker Hub文档,其中列出了全部现有的版本。

Tip #8:寻找最小的 Flavor

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

译者注: flavor:(物理学)夸克和反夸克的一种

再生性

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

Tip #9:在一致的环境中构建源代码

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

您应该首先肯定构建应用程序所需的全部内容。 咱们简单的 Java 应用程序须要 Maven 和 JDK,因此让咱们的 Dockerfile 基于 Docker Hub 中包含 JDK 的特定最小官方 maven 镜像。 若是须要安装更多依赖项,能够在 RUN 步骤中执行此操做。

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

咱们解决了环境不一致的问题,但引入了另外一个问题:每次更改代码时,都会获取 pom.xml 中的全部依赖项。 所以有了下一个技巧。

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

经过再次考虑可缓存的执行单元,咱们能够断定获取依赖项是一个单独的可缓存单元,只须要依赖于对 pom.xml 文件而不是源代码的更改。 两个 COPY 步骤之间的 RUN 步骤告诉 Maven 只获取依赖项。

经过在一致的环境中构建引入了另一个问题:咱们的镜像比之前更大,由于它包含运行时不须要的全部构建时依赖项。

Tip #11:使用多阶段构建以移除构建依赖(推荐的Dockerfile)

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

第二阶段是咱们的最后阶段,将产生最终镜像。它将包括运行时的严格必要条件,在本例中是基于 Alpine 的最小 JRE(Java运行时)。 中间 builder 阶段将被缓存但不会出如今最终镜像中。 要将构建工件添加到最终图像中,请使用 COPY --from = STAGE_NAME 。 在本例中,STAGE_NAMEbuilder

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

咱们从不一致地构建臃肿的镜像,到在一致环境中构建出最小镜像,同时缓存友好。 在下一篇博客文章中,咱们将更多地介绍多阶段构建的其余用途。

更多

相关文章
相关标签/搜索