本文阅读大约需要15分钟。
如今,GitHub上有超过一百万个Dockerfile ,但并非所有Dockerfile都是一样创建的。 效率至关重要,本文将涵盖Dockerfile最佳实践的五个方面,以帮助您编写更好的Dockerfile:递增的构建时间,镜像大小,可维护性,安全性和可重复性。 如果您刚开始使用Docker,那么这第一篇文章就是给您的!
重要说明 : 以下技巧遵循了基于Maven的示例Java项目不断改进的Dockerfile的过程。 因此, 最后一个Dockerfile 是推荐的Dockerfile,而所有中间Dockerfile只是为了说明特定的最佳实践。
增量构建时间
在开发周期中,构建Docker镜像,更改代码然后重新构建时,利用缓存非常重要。 缓存有助于避免在不需要时再次运行构建步骤。
提示1:Order对于缓存很重要
但是,构建步骤(Dockerfile指令)的顺序很重要,因为当通过更改文件或修改Dockerfile中的行使步骤的缓存无效时,其缓存的后续步骤将中断。 将您的步骤从最少到最频繁更改的步骤排序,以优化缓存。
提示2:更具体的COPY限制缓存破坏
仅复制所需内容。 如果可能,请避免"复制"。 将文件复制到镜像中时,请确保对要复制的内容非常明确。 对要复制的文件的任何更改都会破坏缓存。 在上面的示例中,镜像中仅需要预构建的jar应用程序,因此仅复制它即可。 这样,不相关的文件更改将不会影响缓存。
提示3:确定可缓存单元,例如apt-get更新和安装
每个RUN指令都可以看作是可缓存的执行单元。 它们过多可能是不必要的,而将所有命令链接到一条RUN指令会轻易破坏高速缓存,从而损害开发周期。 从程序包管理器安装程序包时,您始终希望更新索引并以相同的RUN模式安装程序包:它们一起形成一个可缓存单元。 否则,您将冒着安装过时的软件包的风险。
缩小镜像体积
镜像大小可能很重要,因为较小的镜像等于更快的部署和较小的攻击面。
提示4:删除不必要的依赖项
删除不必要的依赖项,并且不要安装调试工具。 如果需要,以后可以随时安装调试工具。 某些软件包管理器(如apt)会自动安装用户指定的软件包推荐的软件包,从而不必要地增加了占用空间。 apt具有–no-install-recommends 标志,可确保不安装实际上不需要的依赖项。 如果需要它们,则显式添加它们。
提示5:删除软件包管理器缓存
程序包管理器维护自己的缓存,该缓存可能最终出现在镜像中。 一种解决方法是在安装软件包的同一条RUN指令中删除缓存。 在另一个RUN指令中将其删除不会减小镜像大小。
还有其他减小镜像大小的方法,例如在本博文末尾将介绍的多阶段构建。 下一组最佳实践将研究如何优化Dockerfile的可维护性,安全性和可重复性。
可维护性
提示6:尽可能使用官方镜像
正式镜像可以节省大量维护时间,因为所有安装步骤都已完成,并且采用了最佳实践。 如果您有多个项目,则它们可以共享这些镜像,因为它们使用的是完全相同的基础镜像。
提示7:使用更具体的标签
不要使用最新标签。 它的便利在于总是可以在Docker Hub上获取最新的官方镜像,但是随着时间的推移可能会有重大更改。 根据在没有缓存的情况下重新构建Dockerfile的时间间隔,可能会出现构建失败。
而是对基础镜像使用更具体的标签。 在这种情况下,我们使用的是openjdk。 还有更多可用的标签,因此请查看该镜像的Docker Hub文档 ,其中列出了所有现有的变体。
提示#8:寻找最小的镜像版本
这些标签中的一些标签具有极小的体积,这意味着它们甚至是更小的镜像。 Slim标签基于精简的Debian,而alpine标签则基于甚至更小的Alpine Linux发行镜像。 一个显着的区别是,debian仍然使用GNU libc,而alpine使用musl libc,尽管它很小,但在某些情况下可能会引起兼容性问题。 对于openjdk,jre风格仅包含Java运行时,而不包含sdk。 这也大大减小了镜像体积。
再现性
到目前为止,上述Dockerfile已假设您的jar包是在主机上构建的。 这是不理想的,因为您失去了容器提供的一致环境的好处。 例如,如果您的Java应用程序依赖于特定的库,则可能会导致依赖的不一致,具体取决于构建应用程序的计算机。
提示9:在一致的环境中从源代码构建
源代码是您要构建Docker镜像的真实来源。 Dockerfile只是蓝图。
您应该首先确定构建应用程序所需的所有内容。 我们简单的Java应用程序需要Maven和JDK,所以让我们的Dockerfile基于来自Docker Hub的特定最小官方Maven镜像,其中包括JDK。 如果您需要安装更多的依赖项,则可以在RUN步骤中进行。
将复制pom.xml和src文件夹,这是最终RUN步骤所需的,它们将生成带有mvn package的app.jar应用mvn package 。 (-e标志用于显示错误,-B标志可以在非交互的"批处理"模式下运行)。
我们解决了不一致的环境问题,但又引入了另一个问题:每次更改代码时,都会提取pom.xml中描述的所有依赖项。 因此,下一个技巧。
提示10:在单独的步骤中获取依赖项
通过再次考虑可缓存的执行单元,我们可以确定获取依赖项是一个单独的可缓存单元,只需要依赖对pom.xml的更改,而不必依赖于源代码。 两个COPY步骤之间的RUN步骤告诉Maven仅获取依赖项。
在一致的环境中进行构建会引入另一个问题:我们的镜像比以前大得多,因为它包含了运行时不需要的所有构建时依赖性。
提示#11:使用多阶段构建来删除构建依赖关系(推荐Dockerfile)
多个FROM语句可识别多阶段构建。 每个FROM都开始一个新阶段。 可以使用AS关键字来命名它们,我们使用该关键字来命名第一个阶段的"构建器",以供以后参考。 它将在一致的环境中包括我们所有的构建依赖项。
第二阶段是我们的最后阶段,它将产生最终的镜像。 它将包括运行时所必需的严格条件,在这种情况下,它是基于Alpine的最小JRE(Java运行时)。 中间生成器阶段将被缓存,但不会出现在最终镜像中。 为了使构建jar包进入最终镜像,请使用COPY --from=STAGE_NAME 。 在这种情况下,STAGE_NAME是构建者。
多阶段构建是消除构建时依赖性的首选解决方案。
我们从不一致地构造肿的镜像到在一致的环境中建立对缓存友好的最小镜像。
译文地址:https://www.docker.com/blog/intro-guide-to-dockerfile-best-practices/