精简docker镜像大小的必要性
docker镜像由很多镜像层(Layers)组成(最多127层),镜像层依赖于一系列的底层技术,比如文件系统(filesystems)、写时复制(copy-on-write)、联合挂载(union mounts)等技术,你可以查看docker社区文档以了解更多有关docker存储驱动的内容,这里就不再赘述技术细节。总的来说,dockerfile中的每条指令都会创建一个镜像层,继而会增加整体镜像的尺寸。 下面是精简docker镜像尺寸的好处: 1. 减少构建时间 2. 减少磁盘使用量 3. 减少下载时间 4. 因为包含文件少,攻击面减小,提高了安全性 5. 提高部署速度
五点建议减小docker镜像尺寸
优化基础镜像
优化基础镜像的方法就是选用合适的更小的基础镜像,常用的 Linux 系统镜像一般有 Ubuntu、CentOs、Alpine,其中Alpine更推荐使用。大小对比如下:
1 | lynzabo@ubuntu ~/s> docker images |
Alpine是一个高度精简又包含了基本工具的轻量级Linux发行版,基础镜像只有4.41M,各开发语言和框架都有基于Alpine制作的基础镜像,强烈推荐使用它。Alpine镜像各个语言和框架支持情况,可以参考《优化docker镜像、加速应用部署,教你6个小窍门》。 查看上面的镜像尺寸对比结果,你会发现最小的镜像也有4.41M,那么有办法构建更小的镜像吗?答案是肯定的,例如 gcr.io/google_containers/pause-amd64:3.1 镜像仅有742KB。为什么这个镜像能这么小?在为大家解密之前,再推荐两个基础镜像: 1、scratch镜像 scratch是一个空镜像,只能用于构建其他镜像,比如你要运行一个包含所有依赖的二进制文件,如Golang程序,可以直接使用scratch作为基础镜像。现在给大家展示一下上文提到的Google pause镜像dockerfile:
1 | FROM scratch |
Google pause镜像使用了scratch作为基础镜像,这个镜像本身是不占空间的,使用它构建的镜像大小几乎和二进制文件本身一样大,所以镜像非常小。当然在我们的Golang程序中也会使用。对于一些Golang/C程序,可能会依赖一些动态库,你可以使用自动提取动态库工具,比如ldd、linuxdeployqt等提取所有动态库,然后将二进制文件和依赖动态库一起打包到镜像中。 2、busybox镜像 scratch是个空镜像,如果希望镜像里可以包含一些常用的Linux工具,busybox镜像是个不错选择,镜像本身只有1.16M,非常便于构建小镜像。
串联 dockerfile 指令
大家在定义dockerfile时,如果太多的使用RUN指令,经常会导致镜像有特别多的层,镜像很臃肿,而且甚至会碰到超出最大层数(127层)限制的问题,遵循 dockerfile 最佳实践,我们应该把多个命令串联合并为一个 RUN(通过运算符&&和/ 来实现),每一个 RUN 要精心设计,确保安装构建最后进行清理,这样才可以降低镜像体积,以及最大化的利用构建缓存。 下面是一个优化前dockerfile:
1 | FROM ubuntu |
构建镜像,名称叫 test/test:0.1。 我们对dockerfile做优化,优化后dockerfile:
1 | FROM ubuntu |
构建镜像,名称叫 test/test:0.2。 对比两个镜像大小:
1 | root@k8s-master:/tmp/iops# docker images |
可以看到,将多条RUN命令串联起来构建的镜像大小是每条命令分别RUN的三分之一。 提示:为了应对镜像中存在太多镜像层,docker 1.13版本以后,提供了一个压扁镜像功能,即将 dockerfile 中所有的操作压缩为一层。这个特性还处于实验阶段,docker默认没有开启,如果要开启,需要在启动docker时添加-experimental 选项,并在docker build 构建镜像时候添加 –squash 。我们不推荐使用这个办法,请在撰写 dockerfile 时遵循最佳实践编写,不要试图用这种办法去压缩镜像。
使用多阶段构建
dockerfile中每条指令都会为镜像增加一个镜像层,并且你需要在移动到下一个镜像层之前清理不需要的组件。实际上,有一个dockerfile用于开发(其中包含构建应用程序所需的所有内容)以及一个用于生产的瘦客户端,它只包含你的应用程序以及运行它所需的内容。这被称为“建造者模式”。docker 17.05.0-ce版本以后支持多阶段构建。使用多阶段构建,你可以在dockerfile中使用多个FROM语句,每条FROM指令可以使用不同的基础镜像,这样您可以选择性地将服务组件从一个阶段COPY到另一个阶段,在最终镜像中只保留需要的内容。 下面是一个使用COPY –from 和 FROM … AS … 的dockerfile:
1 | # Compile |
构建镜像,你会发现生成的镜像只有上面COPY 指令指定的内容,镜像大小只有2M。这样在以前使用两个dockerfile(一个dockerfile用于开发和一个用于生产的瘦客户端),现在使用多阶段构建就可以搞定。
业务服务镜像技巧
docker在build镜像的时候,如果某个命令相关的内容没有变化,会使用上一次缓存(cache)的文件层,在构建业务镜像的时候可以注意下面两点: 不变或者变化很少的体积较大的依赖库和经常修改的自有代码分开; 因为cache缓存在运行docker build命令的本地机器上,建议固定使用某台机器来进行docker build,以便利用cache。 下面是构建Spring Boot应用镜像的例子,用来说明如何分层。其他类型的应用,比如Java WAR包,Nodejs的npm 模块等,可以采取类似的方式。 1、在dockerfile所在目录,解压缩maven生成的jar包
1 | $ unzip <path-to-app-jar>.jar -d app |
2、dockerfile 我们把应用的内容分成4个部分COPY到镜像里面:其中前面3个基本不变,第4个是经常变化的自有代码。最后一行是解压缩后,启动spring boot应用的方式。
1 | FROM openjdk:8-jre-alpine |
这样在构建镜像时候可大大提高构建速度。 ###其他优化方法 1. RUN命令中执行apt、apk或者yum类工具技巧 如果在RUN命令中执行apt、apk或者yum类工具,可以借助这些工具提供的一些小技巧来减少镜像层数量及镜像大小。举几个例子: (1)在执行apt-get install -y 时增加选项— no-install-recommends ,可以不用安装建议性(非必须)的依赖,也可以在执行apk add 时添加选项–no-cache 达到同样效果; (2)执行yum install -y 时候, 可以同时安装多个工具,比如yum install -y gcc gcc-c++ make …。将所有yum install 任务放在一条RUN命令上执行,从而减少镜像层的数量; (3)组件的安装和清理要串联在一条指令里面,如 apk –update add php7 && rm -rf /var/cache/apk/* ,因为dockerfile的每条指令都会产生一个文件层,如果将apk add … 和 rm -rf … 命令分开,清理无法减小apk命令产生的文件层的大小。 Ubuntu或Debian可以使用 rm -rf /var/lib/apt/lists/* 清理镜像中缓存文件;CentOS等系统使用yum clean all 命令清理。 2. 压缩镜像 docker 自带的一些命令还能协助压缩镜像,比如 export 和 import
1 | $ docker run -d test/test:0.2 |
使用这种方式需要先将容器运行起来,而且这个过程中会丢失镜像原有的一些信息,比如:导出端口,环境变量,默认指令。 查看这两个镜像history信息,如下,可以看到test/test:0.3 丢失了所有的镜像层信息:
1 | root@k8s-master:/tmp/iops# docker history test/test:0.3 |
社区里还有很多压缩工具,比如docker-squash ,用起来更简单方便,并且不会丢失原有镜像的自带信息,大家有兴趣可以试试。 转自米生态云