构建小容量Docker镜像的技巧

目录
  1. 编写小容量镜像的Dockerfile的技巧
    1. 使用较小的基础镜像
    2. 安装完成后进行清理
    3. 将多个命令集放在一行
    4. 使用script脚本进行软件包安装、处理
  2. 使镜像更小的技巧
  3. 使用BusyBox或Alpine作为基础镜像
  4. 构建静态链接的二进制程序的Docker镜像
  5. 对运行中的容器进行分析,移除无用文件
  6. 使用通用的、单一的、大的基础镜像

作者:杨冬 欢迎转载,也请保留这段声明。谢谢!
出处:https://andyyoung01.github.io/http://andyyoung01.16mb.com/

如果你构建了许多Docker镜像需要分发给不同的用户,则镜像容量的大小会影响到镜像的分发速度。所以如何构建小容量的Docker镜像,如何减小已存在Docker镜像的容量,以及构建小容量Docker镜像的最佳实践,是本篇要讨论的问题。

编写小容量镜像的Dockerfile的技巧

使用较小的基础镜像

减小你的Docker镜像的最简单的方法是从一个较小的基础镜像上进行构建。就是Dockerfile中的FROM命令后选用的基础镜像尽量是比较小的。例如作为基础镜像,centos:7的大小就比ubuntu:14.04要大,而ubuntu:14.04比debian:jessie要大。
不过也要考虑到较小的基础镜像也许会比大的镜像中预配置的软件要少。

安装完成后进行清理

你可以通过移除软件包和清理垃圾文件来减小镜像的大小。例如使用apt包管理器的系统可以通过apt-get purgeapt-get autoremove,及apt-get clean命令移除软件包,使用rm -rf /var/lib/apt/lists/*rm -rf /tmp清除一些临时文件。
这里需要注意的是,因为Docker的层次系统,Dockerfile中的每一个RUN命令都会创建一个新的copy-on-write层,从而增加了最终镜像的大小,即使是通过RUN指令运行移除文件的命令。所以上面的这些命令如果都单独地以Dockerfile中的RUN指令执行,最终的镜像大小不会减小,反而会增加。这就需要我们下面的对Dockerfile的进一步改进。

将多个命令集放在一行

你可以将多个命令放在一个RUN指令下,来减少Docker镜像中的层。以Nginx的官方的Dockerfile为例:

Dockerfilelink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
FROM debian:jessie
MAINTAINER NGINX Docker Maintainers "docker-maint@nginx.com"
ENV NGINX_VERSION 1.11.3-1~jessie
RUN apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62 \
&& echo "deb http://nginx.org/packages/mainline/debian/ jessie nginx" >> /etc/apt/sources.list \
&& apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \
ca-certificates \
nginx=${NGINX_VERSION} \
nginx-module-xslt \
nginx-module-geoip \
nginx-module-image-filter \
nginx-module-perl \
nginx-module-njs \
gettext-base \
&& rm -rf /var/lib/apt/lists/*
# forward request and error logs to docker log collector
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

代码中的第7-19行中包含了多个命令,但都放在一个RUN指令下,从而只会在最终的镜像中创建一层。

使用script脚本进行软件包安装、处理

通过上面的技术,你可能认为Dockerfile的可读性不强,它看起来有些凌乱。为了使Dockerfile更加整洁,你可以将所有的命令放入一个脚本中,然后通过RUN命令运行此脚本,这样可以使Dockerfile可读性更强。
经过上面的这些更改,你可以得到一个更小的镜像。不过,减少再次构建时间的缓存在这样编写Dockerfile时便不可用了。所以你需要权衡镜像大小,构建灵活性及构建时间来组织你的Dockerfile。

使镜像更小的技巧

假如从第三方得到了一个镜像,你想减小此镜像的大小。最简单的方式是启动此镜像,在容器中移除不必要的文件。可以通过以下的步骤实现:

  1. 运行镜像
  2. 进入容器
  3. 移除不必要的文件
  4. 将容器保存(commit)成为新的镜像。
  5. 使镜像平坦化(flatten the image)。

首先运行镜像文件,然后使用docker run -it ...命令得到容器的shell。如果镜像是基于Debian的,可以使用dpkg -l |awk '{print $2'}来列出镜像包含的软件包,然后选择出可以移除的包,使用命令apt-get purge -y package_name移除它们。一旦删除了所有可以安全删除的软件包后,可以使用apt-get autoremoveapt-get clean来清除apt的缓存。
另外可以将系统中使用不到的文档删除。例如使用命令rm -rf /usr/share/doc/* /usr/share/man/* /usr/share/info/*会删除前面三个目录下的文档。另外可以探索/var目录下的文件,因为此目录下经常临时数据等文件。可以使用如下命令删除该目录下的所有以log为后缀的文件:find /var | grep '\.log$' | xargs rm -v
如果镜像是基于yum包管理器的,可以通过yum进行类似于上面apt-get命令的操作,移除不必要的软件包。
完成了上面的清理后,使用docker commit命令保存上面对容器进行的更改。
最后,务必要记住如果想减少镜像的容量,必须将镜像进行平坦化(flatten the image)处理。因为docker的层次文件系统,镜像的容量只能随着操作的进行而越来越大。对容器的平坦化处理也很简单,只需要将镜像export再import后,便可以。可以使用如下命令:docker export image_name | docker import - new_image_name

使用BusyBox或Alpine作为基础镜像

在构建镜像时,我们通常使用常见的Linux发行版作为基础镜像,如Ubuntu、CentOs等。这些镜像的大小通常都在几十上百兆。而可以用在嵌入式系统中的精简的Linux版本通常只有几兆大小,例如解压后的官方的busybox镜像的大小为1.113MB,而alpine镜像的大小为4.795MB。使用它们作为基础镜像,必然会使最终镜像的容量急剧减小。
不过,在享受精简容量镜像的同时,有效地使用它是需要付出一定的代价的。因为Busybox是如此精简,以至于它没有bash,而是由ash替代,另外它也没有包管理器,你不能使用类似于apt-get或yum install之类的命令。Alpine提供了一个包管理器,方便了常用软件包的安装。

构建静态链接的二进制程序的Docker镜像

虽然可以通过移除冗余文件等方式减小镜像的大小,不过有另外一种方式,编译没有外部库依赖的最小的二进制可执行程序。
如果你了解编程语言的话,可以知道Go语言的一个有趣的特性就是它能相对容易得编译出静态二进制程序。而C语言也可以通过静态链接来将所有的依赖库都编译进可执行程序中。这样做的结果是最终的可执行程序是一个单一的文件,把这个文件放入容器中就可以使镜像中没有其它的文件从而减小镜像的大小。

对运行中的容器进行分析,移除无用文件

我们可以对一个运行中的容器进行分析,通过工具来找出哪些文件被用到了,哪些文件没有被用到。此种方式可以说是一个学习价值大于实用价值的选择,如果在生产系统上实施的话,可能会导致很大的风险,因为也许在进行分析时某些文件并没有被用到,所以删除了,但是实际上它有可能会在之后的某个时间被调用。我们可以使用inotify-tools工具来进行分析。

使用通用的、单一的、大的基础镜像

这与减小镜像容量的大小的初衷看起来有些矛盾,但使用通用单一的较大镜像在某些方面却可以节省磁盘空间和网络带宽。
回忆一下当容器运行时Docker使用的是copy-on-write的机制。这意味着可能有上百个Ubuntu容器正在运行,但是对于每个运行的容器,只有一小部分额外的磁盘空间被使用。但如果你有很多不同的、小的镜像存储在服务器上,它占用的磁盘空间可能甚至比一个大的、通用的、包含所有需要内容的镜像所占用的磁盘空间还要大。
这可能会使你想起共享库的原理。一个共享库可以由多个程序共享,从而减少磁盘和内存的使用。同样,在一个Docker环境中使用一个共享的基础镜像可以节省磁盘空间,因为它只需要被下载一次,而且它包括你需要的所有内容,在多个镜像中需要的程序和库现在只需要一次。
另外,一个单一的集中管理的镜像可以带来其它的好处。对镜像的维护可以集中化,对镜像的改进可以共享,构建的问题只用解决一次。