7 步精简 Docker 镜像(上)

社区编辑2018-05-18 11:17

作者简介:黄庆兵,毕业于浙大,工作于网易,现属网易蜂巢,从事云计算和Docker相关开发和布道工作;制作过Docker课程,分享过 Gopher Meetup;喜欢开源,乐于分享,勤于布道,欢迎一起来 ‘写Go 玩Docker’。


介绍

前段时间网易蜂巢曾经推出蜂巢 Logo T恤,用的正是 Docker 镜像制作,最神奇的是,它最终的镜像大小只有 585字节。

$ docker images | grep hub.c.163.com/public/logo 

REPOSITORY               TAG     IMAGE ID        CREATED      SIZE 
hub.c.163.com/public/logo  latest   6fbdd13cd204   11 days ago   585 B

有些镜像都不是我们自己来打包的(比如下载公共镜像),那是否有一些通用的精简 Docker 镜像的手段呢? 答案是肯定的 ,甚至有的镜像可以精简 98%。精简镜像大小的好处不言而喻,既节省了存储空间,又能节省带宽,加快传输。那好,接下来就请跟随我来学习怎么一步步精简 Docker 镜像吧。

镜像层(Layers)

在开始制作镜像之前,首先了解下镜像的原理,而这其中最重要的概念就是镜像层(Layers)。镜像层依赖于一系列的底层技术,比如文件系统(filesystems)、写时复制(copy-on-write)、联合挂载(union mounts)等,幸运的是你可以在很多地方学习到这些技术,这里就不再赘述技术细节。


FROM busybox 

RUN mkdir /tmp/foo 

RUN dd if=/dev/zero of=/tmp/foo/bar bs=1048576 count=100

RUN rm /tmp/foo/bar

以上 Dockerfile 干了这几件事:

  • 基于一个官方的基础镜像 busybox(只有1M多)
  • 创建一个文件夹(/tmp/foo)和一个文件(bar)
  • 为该文件分配了100M大小
  • 再把这个大文件删除

事实上,它最终什么也没做,我们把它构建成镜像看看(构建可以参考一期):

docker build -t busybox:test .

再让我们来对比下原生的 busybox 镜像大小和我们生成的镜像大小:

$ docker images | grep 

busyboxbusybox    test     896c63dbdb96    2 seconds ago    106 MB 

busybox     latest   2b8fd9751c4c     9 weeks ago      1.093 MB

出乎意料的是,却生成了 106 MB 的镜像。多出了 100 M,这是为何?这点和 git 类似(都用到了Copy-On-Write技术),我用 git 做了如下两次提交(添加了又删除),请问 A_VERY_LARGE_FILE 还在 git 仓库中吗?

$ git add  A_VERY_LARGE_FILE

$ git commit

$ git rm  A_VERY_LARGE_FILE

$ git commit

答案是: 在的 ,并且会占用仓库的大小。Git 会保存每一次提交的文件版本,而 Dockerfile 中每一条指令都可能增加整体镜像的大小,即使它最终什么事情都没做。

精简步骤

了解了镜像层知识,有助于我们接下来制作精简镜像。这里开始,以最常用的开源缓存软件 Redis 为例,从一步步试验,来介绍如何制作更精简的 Docker 镜像。

步骤 1:初始化构建 Redis 镜像

直接上 Dockerfile :

FROM ubuntu:trusty 

ENV VER     3.0.0

ENV TARBALL http://download.redis.io/releases/redis-$VER.tar.gz

# ==> Install curl and helper tools...

RUN apt-get update 

RUN apt-get install -y  curl make gcc

# ==> Download, compile, and install...

RUN curl -L $TARBALL | tar zxv 

WORKDIR  redis-$VER

RUN make 

RUN make install

#...

# ==> Clean up...

WORKDIR / 

RUN apt-get remove -y --auto-remove curl make gcc 

RUN apt-get clean 

RUN rm -rf /var/lib/apt/lists/*  /redis-$VER

#...

CMD ["redis-server"]

结合注释,读起来并不困难,用到的都是常规的几个命令,简要介绍如下:

  • FROM:顶头写,指定一个基础镜像,此处基于 ubuntu:trusty
  • ENV:设置环境变量,这里设置了 VER 和 TARBALL 两个环境变量
  • RUN:最常用的 Dockerfile 指令,用于运行各种命令,这里调用了 8 次 RUN 指令
  • WORKDIR:指定工作目录,相当于指令 cd
  • CMD:指定镜像默认执行的命令,此处默认执行 redis-server 命令来启动 redis

执行构建:

$ docker build  -t redis:lab-1  .

注:国内网络,更新下载可能会较慢

查看大小:


动辄就有 300多 M 的大小,不能忍,下面我们开始一步步优化。

步骤 2: 优化基础镜像

方法:选用更小的基础镜像。

常用的 Linux 系统镜像一般有 ubuntu、centos、debian,其中debian 更轻量,而且够用,对比如下:

REPOSITORY     TAG         IMAGE ID           VIRTUAL SIZE
—————       ——          ————                ————

centos                   7            214a4932132a     215.7 MB 

centos                   6            f6808a3e4d9e       202.6 MB 

ubuntu                trusty         d0955f21bf24       188.3 MB 

ubuntu                 precise     9c5e4be642b7     131.9 MB 

debian                 jessie         65688f7c61c4       122.8 MB 

debian                 wheezy     1265e16d0c28       84.96 MB

替换 debian:jessie 作为我们的基础镜像。
优化 Dockerfile:
FROM debian:jessie

#...

执行构建:

$ docker build  -t redis:lab-2  .

查看大小:


步骤3:串联 Dockerfile 指令

方法: 串联你的 Dockerfile 指令(一般是 RUN 指令)。
Dockerfile 中的 RUN 指令通过 && 和 / 支持将命令串联在一起,有时能达到意想不到的精简效果。
优化 Dockerfile:

FROM debian:jessie 
ENV VER     3.0.0
ENV TARBALL http://download.redis.io/releases/redis-$VER.tar.gz 
RUN echo "==> Install curl and helper tools..."  && \apt-get update&& \    
  apt-get install -y  curl make gcc   && \    
  echo "==> Download, compile, and install..."  && \   
  curl -L $TARBALL | tar zxv  && \    
  cd redis-$VER               && \    
  make                        && \    
  make install                && \    
  ...   
  echo "==> Clean up..."  && \    
  apt-get remove -y --auto-remove curl make gcc  && \ 
  apt-get clean                                  && \    
  rm -rf /var/lib/apt/lists/*  /redis-$VER
#...
CMD ["redis-server"]

构建:
$ docker build  -t redis:lab-3  .

查看大小:


哇!一下子减少了 50%,效果明显啊!这是最常用的一个精简手段了。

步骤 4:压缩你的镜像

方法:试着用命令或工具压缩你的镜像
docker 自带的一些命令还能协助压缩镜像,比如 export 和 import

$ docker run -d redis:lab-3 
$ docker export 71b1c0ad0a2b | docker import - redis:lab-4\

但麻烦的是需要先将容器运行起来,而且这个过程中你会丢失镜像原有的一些信息,比如:导出端口,环境变量,默认指令。所以一般通过命令行来精简镜像都是实验性的,那么这里再推荐一个小工具: docker-squash。用起来更简单方便,并且不会丢失原有镜像的自带信息。
下载安装:https://github.com/jwilder/docker-squash#installation(复制此链接到浏览器打开)
压缩操作:

$ docker save redis:lab-3 \  

  | sudo docker-squash -verbose -t redis:lab-4  \  

  | docker load

注:该工具在 Mac 下并不好使,请在 Linux 下使用

对比大小:


Dockerfile 运行指令,以及试着压缩你的镜像。通过这几个技巧,已经可以将 300M 大小的镜像压缩到 150M,压缩率50%,效果还是不错。但这还远远不够,下篇我们将介绍一些终极手段,压缩效果可以达到 98%哦!

参考

☛ Dockerfile Best Practices – take 2 – by Michael Crosby, 2014-03-09.
☛ Optimizing Docker Images – by Brian DeHamer, 2014-07-28.
☛ Squashing Docker Images – by Jason Wilder, 2014-08-19.