zoukankan      html  css  js  c++  java
  • 减小容器镜像的三板斧

    在构建容器镜像时,我们总是希望得到尺寸更小的镜像。比如尽可能的减少镜像中的层数,因为创建新的层是有代价的,每个层都会产生一些数据上的开销。常见的手段是通过 && 把多个 RUN 指令合并为一个:

    # 两个 RUN 指令会创建两个镜像层
    FROM ubuntu
    RUN apt-get update
    RUN apt-get install vim
    
    # 通过 && 把两个 RUN 指令合并为一个,
    # 这样只会创建一个镜像层,
    # 从而减小最终镜像的尺寸
    FROM ubuntu
    RUN apt-get update && apt-get install vim

    而现在,我们有了更多的选择。我们可以使用 multi-stage 技术(关于 multi-stage 技术,请参考笔者的博文《Dockerfile 中的 multi-stage》)来减小容器镜像的尺寸,并且还可以使用 multi-stage 技术结合不同的父镜像来极大的减小最终的镜像的尺寸。有没有很心动呢?接下来就让我们通过 demo 演示笔者砍掉镜像虚膘的三板斧!

    通过 multi-stage 减小镜像尺寸

    让我们创建一个 host 在容器中的 nodejs 程序,并用它来进行本文的 demo 演示。先创建 index.js 文件,其内容如下:

    const express = require('express')
    const app = express()
    
    app.get('/', (req, res) => res.send('Hello World!'))
    
    app.listen(3000, () => {
      console.log(`Example app listening on port 3000!`)
    })

    然后创建 package.json 文件,内容如下:

    {
      "name": "hello-world",
      "version": "1.0.0",
      "main": "index.js",
      "dependencies": {
        "express": "^4.16.2"
      },
      "scripts": {
        "start": "node index.js"
      }
    }

    最后创建的 Dockerfile 文件内容如下:

    FROM node:8
    
    EXPOSE 3000
    WORKDIR /app
    COPY package.json index.js ./
    RUN npm install
    
    CMD ["npm", "start"]

    我们使用父镜像 node:8 来构建并运行上面的 nodejs 应用:

    $ docker build -t node-demo .
    $ docker run -p 3000:3000 -ti --rm --init node-demo

    这样应用程序就运行起来了,然后可以通过 http://localhost:3000/ 访问它。

    现在让我们把注意力转移到运行容器的镜像 node-demo 身上,先看看它的构建历史:

    $ docker history node-demo

    我们的构建过程只是在父进行的基础上增了 3M 多一点的数据。
    下面在 Dockerfile 中使用 multi-stage(关于 multi-stage 技术,请参考笔者的博文《Dockerfile 中的 multi-stage》):

    FROM node:8 as build
    
    WORKDIR /app
    COPY package.json index.js ./
    RUN npm install
    
    FROM node:8
    
    COPY --from=build /app /
    EXPOSE 3000
    CMD ["npm", "start"]

    把上面的内容保存到 Dockerfile.multi 文件中,然后构建镜像 node-demo-multi:

    $ docker build --no-cache -t node-demo-multi . -f Dockerfile.multi

    构建成功后看看 node-demo-multi 的历史:

    $ docker history node-demo-multi

    这次在父镜像的基础上增加的数据更少,总共只有 1.63M,应该是 multi-stage 在中间过程中进行了层合并的结果。然后再对比一下两个镜像的大小:

    $ docker images|grep node-demo

    在我们的场景下使用了 multi-stage 产生的镜像只比没有使用的情况小了 1M,但是对于镜像层数比较多的场景效果会更加明显。关于 multi-stage 的更多内容,请参考笔者的博文《Dockerfile 中的 multi-stage》。

    移除镜像中的非必要内容

    以我们使用的父镜像 node:8 为例:

    其 676M 的体积中包含了众多运行 nodejs 应用并不需要的程序,比如 npm,bash 和其它众多的文件。如果我们能够把这些不需要的文件全部移除,镜像就会缩小很多。但是,具体该怎么做呢?答案是:使用合适的父镜像!
    Google 在 github 上创建了 distroless 项目专门来做这件事!Distroless 项目提供的容器镜像只包含运行应用程序的最新集合,所以能够把镜像压缩到很小。但是需要为不同的应用程序使用不同的镜像,下面是当前已经提供的镜像:

    让我们使用 distroless 提供的 nodejs 镜像来重新构建 demo 应用的镜像:

    FROM node:8 as build
    
    WORKDIR /app
    COPY package.json index.js ./
    RUN npm install
    
    FROM gcr.io/distroless/nodejs
    
    COPY --from=build /app /
    EXPOSE 3000
    CMD ["index.js"]

    把上面的内容保存到文件 Dockerfile.less 中,并运行下面的命令:

    $ docker build --no-cache -t node-demo-less . -f Dockerfile.less
    $ docker run -p 3000:3000 -ti --rm --init node-demo-less

    应用程序可以正常的运行,然后让我们看看刚才构建的容器镜像 node-demo-less:

    只有 76.7M!这确实太不可思议了,在吃惊之于让我们回归理性,看看 distroless 究竟是如何把镜像做的这么小?我想先用 docker exec 命令进入容器内部看看情况,结果是我无法用下面的命令进入到容器内部:

    $  docker exec -it <container id> bash

    结论是为了减小镜像的大小,镜像中没有 bash 这样的工具。那么镜像中有什么?答案是只有 nodejs。你唯一能通过 docker exec 运行的命令就是:

    $ docker exec -it <container id> node

    这同样让人大吃一惊,不能进入容器的话可怎么调查故障?但现实就是这么残酷,当你享受极小的镜像时,你也为方便性付出了代价。其实故障调查完全可以通过完善的日志系统来解决。镜像中只有一个程序带来的另一个好处是安全性的提升!

    使用 Alpine 作为 base 镜像

    除了 Google 的 distroless 项目,其实我们还可以有其他的选择,它就是 Alpine。使用 Alpine 作为父镜像同样也会为你带来意想不到的惊喜(如果你留意了《Dockerfile 中的 multi-stage》一文中的镜像大小)!

    Alpine Linux 是一个基于 musl libc 和 busybox 的,以安全性为目标的轻量级 Linux 发行版。换句话说就是:它的 size 更小,安全性更高!
    接下来让我们用 Alpine 版的父镜像构建 demo 程序镜像:

    FROM node:8 as build
    
    WORKDIR /app
    COPY package.json index.js ./
    RUN npm install
    
    FROM node:8-alpine
    
    COPY --from=build /app /
    EXPOSE 3000
    CMD ["npm", "start"]

    把上面的内容保存到文件 Dockerfile.alpine 中,并运行下面的命令:

    $ docker build --no-cache -t node-demo-alpine . -f Dockerfile.alpine
    $ docker run -p 3000:3000 -ti --rm --init node-demo-alpine

    应用程序可以正常的运行,然后让我们看看刚才构建的容器镜像 node-demo-alpine:

    只有 69.7M,比使用 distroless 项目创建的镜像还要小!能用 docker exec 命令进入容器吗?让我们运行一个命名的容器试试:

    $ docker run -p 3000:3000 -d --name democon --init node-demo-alpine
    $ docker exec -it democon sh

    这次我们成功的进入了容器,虽然不支持 bash,但能有个 shell 哥们就感觉幸福无边了!

    Alpine Linux 看起来很完美,但是请把接下来的文章读完!
    Alpine Linux 中的 C 库不是我们常用的 glibc,而是 muslc。也就是说,使用基于 Alpine 的镜像有可能产生 C 库不同导致的问题。举个例子,PhantomJS 就不能在 Alpine 中正常工作。

    我们究竟该如何选择父镜像

    • 如果是在生产环境中使用,并且有安全性的考虑,建议使用 distroless 镜像。
    • 如果要保持尽可能小的镜像,建议使用 alpine 进行。
    • 开发、测试环境中建议使用官方镜像通过 multi-stage 构建,方便问题调查。

    下表展示了使用不同方式构建的镜像大小:

    总结

    控制镜像的大小是一件永远在路上的事情,使用 multi-stage 技术并选择合适的父镜像可以把我们从繁琐的操作中解放出来,这就是生产力啊!需要注意的是,你需要小心的选择父镜像,选择体积又小又适合你的应用程序的镜像可是个技术活儿呢!

    参考:
    3 simple tricks for smaller Docker images

  • 相关阅读:
    【repost】Javascript操作DOM常用API总结
    【repost】JavaScript运动框架之速度时间版本
    【repost】对JAVASCRIPT匿名函数的理解(透彻版)
    【repost】 原生JS执行环境与作用域深入理解
    【repost】如何学好编程 (精挑细选编程教程,帮助现在在校学生学好编程,让你门找到编程的方向)四个方法总有一个学好编程的方法适合你
    【repost】Chrome 控制台console的用法
    【repost】一探前端开发中的JS调试技巧
    【repost】JS中的异常处理方法分享
    BI_DBA_安装(4):安装DAC及配置客户端
    BI_DBA_安装(3):安装informatic
  • 原文地址:https://www.cnblogs.com/sparkdev/p/8594602.html
Copyright © 2011-2022 走看看