docker清理镜像缓存_「K8S系列」Docker缓存之Docker镜像层

32a17f9f7c59151d96c8af9a9e7d68bc.png

我开始撰写这篇文章是想揭开docker缓存的神秘面纱,这是以高效且可扩展的方式使用Docker的重要方面之一。

为什么Docker如此普及

自软件开发行业开始以来,如何在各种平台上有效地开发和透明地打包和部署软件包一直是一个挑战。随着信息革命的兴起和软件即服务(SaaS)软件解决方案的普及以及云采用的增长,这一点变得尤为突出。

从更高的层次上讲,这意味着该软件是针对特定平台(例如Linux或Windows或AWS与Azure)开发的,并将其移至其他平台需要从开发,配置到部署的整个周期。

从更细粒度的角度来看,开发/部署范式最明显的效果就是“在我的机器上工作”的口头禅。

尽管已经尝试解决此挑战者,例如Tomcat和Jetty之类的Java容器提供了一些标准化,但专注于单个LVM平台,或者使用VirtualBox和Vagrant等工具作为开发人员支持的虚拟机(VM),直到引入了Docker Containers我们认为我们终于朝着正确的方向发展。

Docker容器是标准化,轻量级和安全的环境,定义为代码,并包装给定应用程序的整个运行时-从内核,所有依赖项以及实际的应用程序代码开始。Docker容器与平台/语言无关,因此可以用来运行以任何编程语言编写的应用程序,并且可以在几乎任何硬件平台上运行。由于它们是轻量级的,因此Docker容器化的应用程序可以在开发人员工作站和生产服务器上同样运行。作为与平台无关的工具,它可用于云原生架构的任何本地部署。

这就是范式转变发生的地方:软件不再打包为特定于平台的二进制工件(jar,dll,tgz),而是打包为Docker映像形式的成熟虚拟环境。这意味着开发人员可以完全像在开发,测试或生产环境中运行代码一样在本地运行代码。运营团队只需要处理Docker映像,而无需了解他们正在部署的特定平台的内部工作原理。而且,公司和团队在运行以不同语言/框架编写的不同应用程序方面具有更大的灵活性,而Docker抽象可以更轻松地编排和将所有活动部分抽象为Docker映像,从而可以广泛采用微服务架构。

Dockerfile和Docker层入门

为了构建Docker映像,我们正在从描述它的代码Dockerfile中有效地构建整个运行时环境。它通常涉及一个起点-一个带有基本内核的知名映像,例如ubuntu或alpine,接着是安装所有依赖项,复制和构建应用程序代码并最终运行该应用程序。根据依赖项的数量及其安装方式,这可能要花费几分钟甚至更长的时间(例如,如果需要从源代码安装依赖项)。

每次更改或更新应用程序代码时,都需要构建可用于部署的映像的新版本。即使通常只更改应用程序代码,也需要从头开始构建整个映像,包括所有依赖项。为了提高典型开发流程的效率并缩短反馈循环周期,Docker引入了层缓存的概念。

让我们看看它是如何工作的。

作为示例,我准备了一个简单的react应用程序和一个用于在开发模式下构建和运行该应用程序的dockerfile。

该应用程序已使用create-react-app工具(https://create-react-app.dev/docs/getting-started/)生成,生成的代码和Dockerfile在此博客随附的github存储库中可用:

https://github.com/aleksav/docker-caching-blog。

让我们探索Dockerfile,它描述了我们要构建的镜像:

FROM node:12.13.1-buster-slim AS devWORKDIR /home/app# 1. Copy dependencies definitionsCOPY ./package.json ./package-lock.json* ./# 2. Install dependenciesRUN npm install# 3. Copy our own codeCOPY public /home/app/publicCOPY src /home/app/src# 4. start the appCMD ["npm", "start"]

Dockerfile中的每一行都对应一个命令。从顶部开始:

  • FROM命令描述了我们需要的基本镜像-在我们的示例中,它是一个node 镜像,它将包含javascript,nodejs,npm和典型javascript项目所需的其他工具
  • WORKDIR-指定镜像中的工作目录
  • COPY —将文件从本地文件系统复制到镜像—这里,我们正在复制应用程序描述符(package.json文件)
  • RUN-执行命令-在这种情况下,我们运行npm install来获取所有项目依赖项
  • COPY —接下来的两个复制命令将源代码复制到docker镜像(这是我们没有复制代码和package.json的原因,稍后再介绍)
  • CMD —最后,我们为图像指定入口点命令—在构建镜像后启动镜像时将运行的命令(为此,我们使用标准npm start)。

下一步是使用我们的应用程序构建docker镜像:

91518d93c2f555954913b3bf438455d1.png

Dockerfile中的每个命令都会创建docker镜像的一层,该包括执行该命令后docker镜像中的所有文件。每个图层均保存为docker镜像状态-在上面的示例中,高亮输出部分显示每一层的结束,并且带有ID标识符。

首先看一下docker层缓存

Docker非常方便-因为它们在每个里程碑都包含docker镜像的状态,并保存在本地文件系统中,所以它们充当缓存。如果一层(或它之前的任何层)没有任何变化,我们可以简单地重新使用文件,而无需重建该特定层(例如,无需再次运行该命令—在长时间运行的情况下可以节省命令)我们很多时间)。

为了证明这一点,让我们重建相同的docker镜像。镜像的初始构建花费了几分钟(主要是由于npm install命令需要一些时间来获取依赖)。我们不会更改Dockerfile或源代码-因此期望生成相同的映像:

f5e3b9c3dad274092acd3d3d76fd5fb8.png

这次,构建仅需2秒钟,这要归功于所有docker层已经构建并已从缓存中提供服务(如上面突出显示的输出所示)。

当我们更改代码时会发生什么?让我们来看看。

下面我们来运行下Docker容器启动这个镜像:

docker run -it -p 3000:3000 hello-world-react-docker

打开浏览器窗口,位于http:// localhost:3000。可以看到如下界面,我们已经成功构建并运行了。

0553866abde7c214bfc4884601b686cf.png

让我们更新屏幕上显示的消息:打开src / App.js文件,并将文本更新为“ Hello,World-这是Docker调用上的React!”。保存文件后,让我们使用相同的命令再次重建镜像:

612e93ca3a50ede5c837c640a071a749.png

构建过程花费了大约5秒钟的时间,所以与上一次运行(在进行更改之前)相比要长一些-但绝对不会比我们第一次构建它的时间长-为什么?

从上面突出显示的输出(—>使用缓存行)中可以看到,缓存中未使用的第一层是复制源代码—这是预期的,因为我们刚刚更改了源文件以更新显示的消息。但是,以前的任何层都没有受到此更改的影响(包括臭名昭著的缓慢的npm install命令),这意味着我们可以使用所有先前层的缓存版本,从而使构建非常快速和高效。

再次运行该应用程序,将在浏览器中显示新消息-因此您可以确认我们所做的更改已内置到新的Docker镜像中。

问题是,Docker如何知道可从缓存中使用哪些层以及需要重建哪些层?让我们在下一部分中进行探讨。

我们已经说过,来自Dockerfile描述符的每个命令都将导致一个层-每个命令还带有与其缓存行为相对应的规则。对于我们使用的命令:

  • WORKDIR-除非工作目录更改,否则将缓存层
  • COPY —除非已复制的任何文件或目录此后发生更改,否则将缓存层。这包括重命名文件,更改内容,创建时间戳pr权限更改。
  • RUN —除非命令更改(不同的脚本或不同的参数),否则将缓存层

需要重申的重要一点是,如果任何一层遇到高速缓存缺失,并且由于上述任何原因而需要重建,那么无论是否更改了什么,所有后续层都将被重建。这就是为什么计划Dockerfile中的命令顺序以实现最有效的缓存使用和最佳构建性能非常重要。

这对于COPY命令来说最关键-最佳做法是分割复制或添加到Docker映像的文件和目录,以便将最经常更改的文件尽可能晚地复制-以最大程度地减少缓存的影响错过后续命令。如果存在任何已知的速度慢且效率低的命令,这尤其重要。

注意:如果您要使用docker开发更复杂的javascript项目(服务器或客户端),请继续学习Eric的Docker开发最佳实践。

在我们的示例中(这是大多数javascript项目所特有的)-变化最大的代码是应用程序的源代码,这就是我们最后复制它的原因。我们也知道npm install命令很慢,因此我们要确保尽可能多地从缓存层提供该命令。执行npm install取决于存在的package.json文件,因此我们首先复制这些文件,然后安装依赖项,然后再复制源代码。

此设置在不同情况下的表现如何:

  • 更改源代码(作为我们运行的最后一个示例,这是开发人员的工作),我们将代码更改,测试,发布等等。由于源文件最后被复制到了docker镜像,因此所有先前的层都被缓存了,因此只需要重建最后几层,我们就可以在几秒钟内准备好更新的docker镜像
  • 更改或添加依赖关系-这涉及更新package.json文件,从而重新构建下面的所有层-我们再次运行npm install,然后复制源代码-这需要更长的时间,类似于我们的初始构建。但这是预期的-如果依赖关系发生变化,我们需要安装依赖关系
  • 更改基本映像-假设我们已升级到最新的节点版本。由于我们正在更新FROM命令(这是构建过程遇到的第一层),因此后续的任何层都不能从缓存中重用,并且需要重新构建。这也是常识-如果升级核心依赖项(如节点版本),我们希望重建整个映像以确保所有依赖项都与更改兼容。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值