docker清理镜像缓存_更快构建Docker映像的六种方法(甚至几秒钟)

不知道大家对现在软件交付内心平衡度感受到差异化了吗,这句话说的不以为然,如今,复杂的编排器和CI / CD对于软件开发至关重要,但是结果从提交到测试和交付,再到量产,还有一段很长的路要走。
结合你的经验不知道你对此有什么看法,欢迎结尾讨论留言~

e2179c734962b8cbf4dbc8d069ccf17c.png

以前,做过开发的知道,开发人员过去常常通过FTP将新文件上传到服务器,因此部署花费了几秒钟。但是,现在我们必须创建一个合并请求,并等待很长时间才能使功能进入用户体系当中。而构建Docker镜像是此过程的一部分,可能需要花费数十分钟的时间。这几乎是大家不能接受的。不知道你们环境当中是不是也遇到了此问题,在本文中,我们将对一个简单的应用程序进行docker化,然后使用多种方法来加快构建时间并考虑其细微差别。

最近,我们正在将网站部署到生产环境中,并且在添加新功能和修复旧错误的同时,缓慢部署成为一个大问题,这令我很担忧。

加速构建?Docker镜像的六种方法

这就回到我们的项目当中了,回顾一下我们使用的GitLab进行部署:构建的Docker镜像会将它们推送到我们的Container Registry并将我们的容器镜像部署到生产环境。构建Docker镜像是此列表上最长的过程。例如,构建每个未优化的后端镜像花了14分钟。

306dc988589556ae2321ad5d93d0b22f.png

我感觉必须为此做些事情。我们决定弄清楚为什么构建Docker镜像需要这么长时间以及如何解决这种情况。结果,我们能够将构建时间减少到30秒构建完成!是吧,看来事情做对了,让我们继续研究一下如何优化的吧,这里告诉大家,希望可以帮助你脱离缓慢的苦海当中,?

84527dec7922785a9475fa91d5cc0b96.png

1 Docker镜像是什么?

刚开始聊聊Docker镜像是什么,说白了Docker使打包应用程序并在称为容器的隔离环境中运行它们成为可能性,这个说的有点官方。其实就是由于这种隔离,你可以在单个服务器上同时运行多个容器。但是与虚拟机不同,Docker容器直接在内核上运行,因此它们更轻巧。在运行dockerized这个应用程序之前,我们会构建一个Docker镜像,将应用程序正常运行所需的一切打包到其中。Docker镜像就像文件系统的转换一样。例如,让我们看一下这个Dockerfile:

FROM node:12.16.2
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod

Dockerfile是一组指令。Docker并逐步执行这些指令,并将更改保存到文件系统中,并将其添加到以前的文件系统中。每个命令创建自己的层。完成的Docker镜像将所有这些层组合在一起。

重要的是要知道Docker缓存了每一层。如果自上次构建以来没有任何变化,则Docker将使用已完成的层而不是执行命令。构建速度的主要提高是由于有效的缓存使用,因此在测量构建速度时,我们将重点关注使用就绪缓存构建Docker镜像的速度。让我们一步一步走,先理解一下Dockerfile的图层结构

2 理解Dockerfile图层

  • 1.首先,我们需要在本地删除镜像,以便先前的运行不会影响测试。
docker rmi $(docker images -q)
  • 2.接下来,让我们第一次运行我们的构建。
time docker build -t app .
  • 3.现在,我们更改src / index.html,以模拟程序的工作。

  • 4.然后我们第二次运行构建。

time docker build -t app .

如果我们正确设置了构建环境,那么在开始构建镜像时,Docker将已经拥有大量缓存。我们的目标是学习如何利用缓存,以便尽快执行构建过程。由于我们是在第一次构建镜像时不使用缓存,因此我们可以忽略它的速度。在测试中,我们对构建的第二次运行很感兴趣,就是当我们的缓存已经预热并且准备好就绪时才构建镜像。但是,应用一些技巧也会影响第一个构建。

让我们将上述Dockerfile放在项目文件夹中,然后运行查看一下构建的过程。但是为了便于阅读,这里缩短了所有清单。

 $ time docker build -t app .
Sending build context to Docker daemon 409MB
Step 1/5 : FROM node:12.16.2
Status: Downloaded newer image for node:12.16.2
Step 2/5 : WORKDIR /app
Step 3/5 : COPY . .
Step 4/5 : RUN npm ci
added 1357 packages in 22.47s
Step 5/5 : RUN npm run build --prod
Date: 2020-04-16T19:20:09.664Z - Hash: fffa0fddaa3425c55dd3 - Time: 37581ms
Successfully built c8c279335f46
Successfully tagged app:latest

real 5m4.541s
user 0m0.000s
sys 0m0.000s

然后,我们必须更改src / index.html的内容并第二次运行它。

$ time docker build -t app .
Sending build context to Docker daemon 409MB
Step 1/5 : FROM node:12.16.2
Step 2/5 : WORKDIR /app
---> Using cache
Step 3/5 : COPY . .
Step 4/5 : RUN npm ci
added 1357 packages in 22.47s
Step 5/5 : RUN npm run build --prod
Date: 2020-04-16T19:26:26.587Z - Hash: fffa0fddaa3425c55dd3 - Time: 37902ms
Successfully built 79f335df92d3
Successfully tagged app:latest

real 3m33.262s
user 0m0.000s
sys 0m0.000s

现在,我们执行docker images命令来查看我们的镜像是否已成功创建:

REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 79f335df92d3 About a minute ago 1.74GB

在构建过程开始之前,Docker会获取当前构建上下文中的所有文件,并将它们发送到Docker守护程序:将构建上下文发送到Docker守护程序409MB。构建上下文由最后一个构建命令参数指示-在我们的示例中,它是一个点(.),这意味着Docker将从当前文件夹中获取所有文件。但是409MB很大,所以我们应该考虑如何解决这种情况。

所以这里给大家分享一下如何在这种情况下的解决方法,这里分为6种。

一、减少镜像上下文大小

1、我们可以将构建所需的所有文件放在单独的文件夹中,并将Docker指向该文件夹,但这并不总是很方便。

2、通过将.dockerignore文件添加到上下文目录中,我们可以排除构建不需要的文件,这样大大减少了镜像的占用大小

让我们再次建立我们的镜像,如果你看到的话,其中镜像的体积已经减少了:

$ time docker build -t app .
Sending build context to Docker daemon 607.2kB
Step 1/5 : FROM node:12.16.2
Step 2/5 : WORKDIR /app
---> Using cache
Step 3/5 : COPY . .
Step 4/5 : RUN npm ci
added 1357 packages in 22.47s
Step 5/5 : RUN npm run build --prod
Date: 2020-04-16T19:33:54.338Z - Hash: fffa0fddaa3425c55dd3 - Time: 37313ms
Successfully built 4942f010792a
Successfully tagged app:latest

real 1m47.763s
user 0m0.000s
sys 0m0.000s

是的,607.2KB比409MB好得多。我们还将镜像大小从1.74GB减小到1.38GB:

REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 4942f010792a 3 minutes ago 1.38GB

现在,我们将尝试进一步减小图像尺寸。

二、使用Alpine Linux

减小Docker镜像大小的另一种方法是使用小的父镜像。父镜像是我们镜像所基于的镜像。最低层由Dockerfile中的FROM命令指示。在本例中,我们将使用基于Ubuntu的镜像,该镜像已安装Node.js。但这几乎是1GB(挺大的容量的!)。

$ docker images -a | grep node
node 12.16.2 406aa3abbc6c 17 minutes ago 916MB

如果使用基于Alpine Linux的镜像,则可以大大减小镜像大小。Alpine Linux它是一种极其轻量级的Linux发行版。Node.js的 Alpine镜像只有88.5MB!因此,让我们用较小的镜像代替较大的镜像,这样似乎体积由大大的减少了不少

FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual build-dependencies add
python
make
g++
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod

另外我们还必须安装构建应用程序所需的一些东西。是的,没有Python就不会构建Angular这个应用程序。

但这是值得的因为我们已经将镜像大小减少了619MB:

REPOSITORY TAG IMAGE ID CREATED SIZE
app latest aa031edc315a 22 minutes ago 761MB

三、使用多阶段构建

我们只会从镜像中获取生产中实际需要的东西。这就是我们现在所拥有的:

$ docker run app ls -lah
total 576K
drwxr-xr-x 1 root root 4.0K Apr 16 19:54 .
drwxr-xr-x 1 root root 4.0K Apr 16 20:00 ..
-rwxr-xr-x 1 root root 19 Apr 17 2020 .dockerignore
-rwxr-xr-x 1 root root 246 Apr 17 2020 .editorconfig
-rwxr-xr-x 1 root root 631 Apr 17 2020 .gitignore
-rwxr-xr-x 1 root root 181 Apr 17 2020 Dockerfile
-rwxr-xr-x 1 root root 1020 Apr 17 2020 README.md
-rwxr-xr-x 1 root root 3.6K Apr 17 2020 angular.json
-rwxr-xr-x 1 root root 429 Apr 17 2020 browserslist
drwxr-xr-x 3 root root 4.0K Apr 16 19:54 dist
drwxr-xr-x 3 root root 4.0K Apr 17 2020 e2e
-rwxr-xr-x 1 root root 1015 Apr 17 2020 karma.conf.js
-rwxr-xr-x 1 root root 620 Apr 17 2020 ngsw-config.json
drwxr-xr-x 1 root root 4.0K Apr 16 19:54 node_modules
-rwxr-xr-x 1 root root 494.9K Apr 17 2020 package-lock.json
-rwxr-xr-x 1 root root 1.3K Apr 17 2020 package.json
drwxr-xr-x 5 root root 4.0K Apr 17 2020 src
-rwxr-xr-x 1 root root 210 Apr 17 2020 tsconfig.app.json
-rwxr-xr-x 1 root root 489 Apr 17 2020 tsconfig.json
-rwxr-xr-x 1 root root 270 Apr 17 2020 tsconfig.spec.json
-rwxr-xr-x 1 root root 1.9K Apr 17 2020 tslint.json

使用docker run app ls -lah,我们根据应用程序镜像运行一个容器并执行ls -lah命令,然后退出容器。

对于生产,我们只需要dist文件夹。另外,我们需要以某种方式发送文件。我们可以运行某种Node.js HTTP服务器,但是有一种更简单的方法。我们使用Nginx镜像,并将dist文件夹和下面的小配置文件放入其中:

server {
listen 80 default_server;
server_name localhost;
charset utf-8;
root /app/dist;

location / {
    try_files $uri $uri/ /index.html;
}
}

我们将通过使用多阶段构建来实现。让我们更改我们的

Dockerfile:
FROM node:12.16.2-alpine3.11 as builder
RUN apk --no-cache --update --virtual build-dependencies add
python
make
g++
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod

FROM nginx:1.17.10-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/static.conf /etc/nginx/conf.d
COPY --from=builder /app/dist/app .

现在,我们有两个FROM命令,每个命令都开始其自己的构建过程阶段。我们命名了第一阶段的构建器,第二个阶段的构建器开始了创建最终镜像的过程。在最后一步中,我们将构建从构建器阶段复制到最终的Nginx镜像。镜像尺寸已大大减小:

REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 2c6c5da07802 29 minutes ago 36MB

让我们将镜像作为容器运行,并确保一切正常:

docker run -p8080:80 app

使用-p8080:80选项,我们将主机的端口8080转发到运行Nginx的容器的端口80。现在,我们在浏览器中打开http:// localhost:8080 /并查看我们的应用程序。可以访问!

40c2b2a1edad4451be78ae98a08260ae.png将镜像大小从1.74GB减少到36MB可以大大缩短应用程序投产的时间。再次让我们回到构建时间看一下

$ time docker build -t app .
Sending build context to Docker daemon 608.8kB
Step 1/11 : FROM node:12.16.2-alpine3.11 as builder
Step 2/11 : RUN apk --no-cache --update --virtual build-dependencies add python make g++
---> Using cache
Step 3/11 : WORKDIR /app
---> Using cache
Step 4/11 : COPY . .
Step 5/11 : RUN npm ci
added 1357 packages in 47.338s
Step 6/11 : RUN npm run build --prod
Date: 2020-04-16T21:16:03.899Z - Hash: fffa0fddaa3425c55dd3 - Time: 39948ms
---> 27f1479221e4
Step 7/11 : FROM nginx:stable-alpine
Step 8/11 : WORKDIR /app
---> Using cache
Step 9/11 : RUN rm /etc/nginx/conf.d/default.conf
---> Using cache
Step 10/11 : COPY nginx/static.conf /etc/nginx/conf.d
---> Using cache
Step 11/11 : COPY --from=builder /app/dist/app .
Successfully built d201471c91ad
Successfully tagged app:latest

real 2m17.700s
user 0m0.000s
sys 0m0.000s

四、更改图层顺序

Docker缓存了前三个步骤(使用缓存)。在第四步中,将复制所有项目文件,在第五步中,npm ci将安装依赖项,整个过程耗时47.338秒。如果依赖关系很少更改,为什么每次都需要重新安装依赖关系?让我们看看为什么它们没有被缓存。事实是,Docker会逐层检查以查看命令和与其关联的文件是否已更改。第四步,我们复制项目的所有文件,其中一些已更改。这就是为什么Docker不仅不使用该层的缓存版本,而且不使用以下缓存版本的原因!让我们对Dockerfile进行一些更改。

FROM node:12.16.2-alpine3.11 as builder
RUN apk --no-cache --update --virtual build-dependencies add
python
make
g++
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build --prod

FROM nginx:1.17.10-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/static.conf /etc/nginx/conf.d
COPY --from=builder /app/dist/app .

首先,复制package.json和package-lock.json,然后安装依赖项,然后复制整个项目。结果是:

$ time docker build -t app .
Sending build context to Docker daemon 608.8kB
Step 1/12 : FROM node:12.16.2-alpine3.11 as builder
Step 2/12 : RUN apk --no-cache --update --virtual build-dependencies add python make g++
---> Using cache
Step 3/12 : WORKDIR /app
---> Using cache
Step 4/12 : COPY package*.json ./
---> Using cache
Step 5/12 : RUN npm ci
---> Using cache
Step 6/12 : COPY . .
Step 7/12 : RUN npm run build --prod
Date: 2020-04-16T21:29:44.770Z - Hash: fffa0fddaa3425c55dd3 - Time: 38287ms
---> 1b9448c73558
Step 8/12 : FROM nginx:stable-alpine
Step 9/12 : WORKDIR /app
---> Using cache
Step 10/12 : RUN rm /etc/nginx/conf.d/default.conf
---> Using cache
Step 11/12 : COPY nginx/static.conf /etc/nginx/conf.d
---> Using cache
Step 12/12 : COPY --from=builder /app/dist/app .
Successfully built a44dd7c217c3
Successfully tagged app:latest

real 0m46.497s
user 0m0.000s
sys 0m0.000s

该过程花费了46秒而不是3分钟,这要好得多!按正确的顺序排列图层很重要:首先,我们复制不变的图层,然后复制很少更改的图层,最后复制经常更改的图层。

接下来,让我们谈谈在CI / CD系统中构建Docker镜像的方法。

五、使用以前的镜像作为缓存源

如果我们使用某种SaaS解决方案来构建Docker镜像,则本地docker缓存可能绝对为空。我们需要给Docker一个先前构建的镜像,以便它准备就绪。

例如,让我们考虑在GitHub Actions中构建应用程序。我们将使用以下配置文件:

on:
push:
branches:
- master

name: Test docker build

jobs:
deploy:
name: Build
runs-on: ubuntu-latest
env:
IMAGE_NAME: docker.pkg.github.com/${{ github.repository }}/app
IMAGE_TAG: ${{ github.sha }}

steps:
- name: Checkout
  uses: actions/checkout@v2

- name: Login to GitHub Packages
  env:
    TOKEN: ${{ secrets.GITHUB_TOKEN }}
  run: |
    docker login docker.pkg.github.com -u $GITHUB_ACTOR -p $TOKEN

- name: Build
  run: |
    docker build \
      -t $IMAGE_NAME:$IMAGE_TAG \
      -t $IMAGE_NAME:latest \
      .

- name: Push image to GitHub Packages
  run: |
    docker push $IMAGE_NAME:latest
    docker push $IMAGE_NAME:$IMAGE_TAG

- name: Logout
  run: |
    docker logout docker.pkg.github.com

构建镜像并将其推送到GitHub Packages花费了2分20秒:

ed2ad9fa92df4ca3dbd0db977c3df6c6.png现在,我们将更改构建配置,以便Docker将使用之前步骤中的缓存层:

on:
push:
branches:
- master

name: Test docker build

jobs:
deploy:
name: Build
runs-on: ubuntu-latest
env:
IMAGE_NAME: docker.pkg.github.com/${{ github.repository }}/app
IMAGE_TAG: ${{ github.sha }}

steps:
- name: Checkout
  uses: actions/checkout@v2

- name: Login to GitHub Packages
  env:
    TOKEN: ${{ secrets.GITHUB_TOKEN }}
  run: |
    docker login docker.pkg.github.com -u $GITHUB_ACTOR -p $TOKEN

- name: Pull latest images
  run: |
    docker pull $IMAGE_NAME:latest || true
    docker pull $IMAGE_NAME-builder-stage:latest || true

- name: Images list
  run: |
    docker images

- name: Build
  run: |
    docker build \
      --target builder \
      --cache-from $IMAGE_NAME-builder-stage:latest \
      -t $IMAGE_NAME-builder-stage \
      .
    docker build \
      --cache-from $IMAGE_NAME-builder-stage:latest \
      --cache-from $IMAGE_NAME:latest \
      -t $IMAGE_NAME:$IMAGE_TAG \
      -t $IMAGE_NAME:latest \
      .

- name: Push image to GitHub Packages
  run: |
    docker push $IMAGE_NAME-builder-stage:latest
    docker push $IMAGE_NAME:latest
    docker push $IMAGE_NAME:$IMAGE_TAG

- name: Logout
  run: |
    docker logout docker.pkg.github.com

在这里我们必须解释为什么我们需要两个构建命令。问题是在多阶段构建中,生成的镜像是最后一个阶段的一组图层。上一阶段的图层未包含在镜像中。因此,当使用来自先前构建的最终镜像时,Docker将无法找到准备就绪的层以使用Node.js构建镜像(构建器阶段)。为了解决此问题,我们创建了一个中间镜像 $ IMAGE_NAME-builder-stage,并将其发送到GitHub Packages,以便它可以用作后续构建的缓存源。

92678efcd2b1fef8f9391a32eddcfe18.png

总构建时间减少到1.5分钟。花了半分钟来拉之前的镜像。

六、使用预构建的Docker镜像

解决清晰的Docker缓存问题的另一种方法是将某些层移动到另一个Dockerfile,分别构建该镜像,将其推送到Container Registry,并将其用作父镜像。

让我们创建用于构建Angular应用程序的Node.js镜像。首先,我们在项目中创建一个Dockerfile.node:

FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual build-dependencies add
python
make
g++

然后,我们构建公共镜像并将其推送到Docker Hub:

docker build -t exsmund/node-for-angular -f Dockerfile.node .
docker push exsmund/node-for-angular:latest

现在,我们在主Dockerfile中使用完成的镜像:

FROM exsmund/node-for-angular:latest as builder
...

在此示例中,构建时间没有减少,但是如果有许多项目并且必须在所有项目中放置相同的依赖项,则预构建的镜像可能会很有用。

de4542f31e69893ba73646afd6de3836.png

在本文中,我们研究了几种加快构建Docker镜像的方法。如果你想更快地部署,可以尝试下:

  • 减少构建上下文的大小;

  • 使用小的父镜像;

  • 使用多阶段构建;

  • 在Dockerfile中重新排序命令以有效利用缓存; 

  • 在CI / CD系统中配置缓存源;

  • 使用预先构建的Docker镜像

我希望这些示例阐明Docker的工作方式,并且将能够使用我的技巧来优化你的部署。如果你想尝试本文中的示例,可参见以下存储库链接:

https : //github.com/devopsprodigy/test-docker-build。

09c3d62c8f3b84071c66e982b3055dfa.gif

书籍把我们引入最美好的社会,使我们认识各个时代的伟大智者。——史美尔斯

39e5ff99b19596903c33956475cdfa7e.png

如果喜欢?文章的话,点点关注,就差你的关注了,更多好玩有趣的云原生前沿技术尽在云原生CTO

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值