优化 Dockerfile,缩减镜像尺寸
原文:Refactoring a Dockerfile for image size
Docker 社区最近有一篇关于镜像文件尺寸的文章。Docker 和社区都提倡更小的镜像。下面列出今天 Docker Hub 上的 Top 10 镜像的尺寸( Latest ):
镜像名称 | 尺寸(MB) |
---|---|
busybox | 1 |
ubuntu | 188 |
swarm | 17 |
nginx | 134 |
registry | 423 |
redis | 151 |
mysql | 360 |
mongo | 317 |
node | 643 |
debian | 125 |
使用较小的基础镜像(例如 Alphine Linux, BusyBox 等)有很多好处。这方面有不少相关文档:
- Create the smallest possible docker container
- Docker Images
- Create super small docker images
- Docker images base os size comparison
所以我假设你已经试用了其中的一个为基础镜像。接下来的事情就取决于操作者控制镜像尺寸的能力了。特别的,我们会验证一些缩减镜像大小的手段的效果,例如把多个 RUN
指令中的命令集中到一行,一些关于 apt-get
的技巧(例如移除 apt-get 缓存以及 --no-install-recommends
)
在同一行中进行清理工作
Docker 镜像的基础是一种层次化文件系统。每一层都只是包含同下面一层不同的部分。在最上方只能看到一个统一视图,而无法获知他的构建历史。Dockerfile 的每一行都会在顶部建立一个新的层。
例如我们以这样一个 Dockerfile 片段开始:
ADD https://storage.googleapis.com/golang/go1.5.3.src.tar.gz /tmp
# do some things with that file
RUN rm /tmp/go1.5.3.src.tar.gz
你可以能会觉得删除 .tar.gz
文件是个负责任的好办法。但是包含这一文件的层还遗留在镜像之中。rm
操作只是对最终镜像隐藏了这一文件,使用 docker pull
获取这一镜像时还是会下载这些内容的。
更好的办法就是把这些操作集中到一行之中,这样就不会提交多个层了。例如,对上面的指令进行简单的改写:
RUN curl -o \
/tmp/go.1.5.3.src.tar.gz \
https://storage.googleapis.com/golang/go1.5.3.src.tar.gz && \
<do some things with the file> && \
rm /tmp/go1.5.3.src.tar.gz
这样看起来有点丑,不过能够优化镜像尺寸。如果你讨厌这种写法,可以建立一个脚本,然后用 ADD
、RUN
来在 Dokerfile 中运行。
用正确的方式移除 apt/yum 缓存
多数 Dockerfile 作者都知道应该 apt-get remove
所有不必要的包。一个例子就是,用 curl/wget 来进行下载安装其他软件。可以事后使用 apt-get remove curl
,但是同上面的例子一样,curl 所在的层已经被封装到镜像之中,因此也应该把删除(包括相关的自动安装的依赖包)工作放到同一行中。
一个实际的例子。
这是一个简化版本的用于运行 Python 服务的 Dokerfile,我们将对他进行优化。
FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl python-pip
RUN pip install requests
ADD ./my_service.py /my_service.py
ENTRYPOINT ["python", "/my_service.py"]
myservice.py
是一个 Python 脚本:
#!/usr/bin/python
print 'Hello, world!'
接下来 build 并检查尺寸:
$ sudo docker build -t size .
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
size latest da8a9be731ac 4 seconds ago 360.5 MB
ubuntu 14.04 6cc0fc2a5ee3 2 weeks ago 187.9 MB
188 MB 的基础镜像已经很吓人了,不过更值得注意的是,只是一个 hello-world 的 Python 脚本为什么会导致镜像尺寸倍增。这 360.5 MB 中到底有什么?其中包含了最上一层(本例中是 da8a9be731ac),以及用于建立顶层的所有的层的内容。
添加一个清理层
我们也许应该做一些清理工作,我们试试这样的 Dockerfile :
FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl python-pip
RUN pip install requests
## Clean up
RUN apt-get remove -y python-pip curl
RUN rm -rf /var/lib/apt/lists/*
ADD ./my_service.py /my_service.py
ENTRYPOINT ["python", "/my_service.py"]
Build,然后再看尺寸:
$ sudo docker build -t size .
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
size latest c6dacdd00660 2 seconds ago 361.3 MB
ubuntu 14.04 6cc0fc2a5ee3 2 weeks ago 187.9 MB
事与愿违,镜像更大了。
在同一层进行清理
接下来试试把 APT 操作进行合并:
FROM ubuntu:14.04
RUN apt-get update && \
apt-get install -y curl python-pip && \
pip install requests && \
apt-get remove -y python-pip curl && \
rm -rf /var/lib/apt/lists/*
ADD ./my_service.py /my_service.py
ENTRYPOINT ["python", "/my_service.py"]
Build 然后查看镜像:
$ sudo docker build -t size .
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
size latest e531f8674f33 9 seconds ago 338 MB
ubuntu 14.04 6cc0fc2a5ee3 2 weeks ago 187.9 MB
果然小了一点点 —— 只是一点点。
进一步优化 APT
apt-get install
过程中加入了很多 推荐
内容。推荐包只是简单的加入了一些可有可无的依赖。有些用户会因为特殊的应用方式,或者环境要求才需要这些东西,换句话说,这些推荐内容并非必要。
在 Ubuntu 14.04 中运行 PIP,很容易就可以得出结论:删除推荐包并不会有什么副作用。这一点在产品发布之前是完全可以确认的。可以在 Docker Hub 上看看 redis,mysql,mongo,postgres,elasticsearch 等的镜像,使用这一技巧来缩减镜像尺寸。
实际操作一下带有 --no-install-recommends
选项的 APT-GET:
FROM ubuntu:14.04
RUN apt-get update && \
apt-get install -y --no-install-recommends curl python-pip && \
pip install requests && \
apt-get remove -y python-pip curl && \
rm -rf /var/lib/apt/lists/*
ADD ./my_service.py /my_service.py
ENTRYPOINT ["python", "/my_service.py"]
Build,再次检查尺寸:
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
size latest fddc30aee4dc 6 seconds ago 229.2 MB
ubuntu 14.04 6cc0fc2a5ee3 2 weeks ago 187.9 MB
这次我们把镜像缩小了 120 MB。
Dockerfile 的语法非常简单,也同样有优化的需求,因此在组织中应用 Docker 时,需要为 Dockerfile 建立健全相应的策略来优化这一过程。