7 步精简 Docker 镜像(下)

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

作者简介:黄庆兵,毕业于浙大,工作于网易,从事云计算、Docker和Go相关开发及布道工作;喜欢开源,乐于分享,勤于布道,折腾过开源小工具,制作过Docker课程,分享过 Gopher Meetup。我的 Github 账号:https://github.com/bingohuang,欢迎一起来 `写 Go 玩 Docker`!


图注:一张图掌握 Docker 命令 – 完整版

介绍

查看 docker hub 中的一些镜像,我们有时能看到很多极小的镜像,比如:


这么小的镜像是怎么做出来的呢?话接上文,接下来就介绍一些精简 Docker 镜像的终极手段!

精简步骤

 

步骤 5: 使用最精简的 base image

 

方法:使用 scratch 或者 busybox 作为基础镜像。

关于 scratch:

一个空镜像,只能用于构建镜像,通过 FROM scratch
在构建一些基础镜像,比如 debian 、 busybox,非常有用
用于构建超少镜像,比如构建一个包含所有库的二进制文件

关于 busybox

只有 1~5M 的大小
包含了常用的 UNIX 工具
非常方便构建小镜像

这些超小的基础镜像,结合能生成静态原生 ELF 文件的编译语言,比如C/C++,比如 Go,特别方便构建超小的镜像。
cloudcomb-logo(C语言开发) 就是用到了该原理,才能构建出 585 字节的镜像。
redis 同样使用 C语言 开发,看来也有很大的优化空间,下面这个实验,让我们介绍具体的操作方法。

 

 

步骤 6: 提取动态链接的 .so 文件

 

 

实验上下文:
$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="14.04.2 LTS, Trusty Tahr"
$ uname -a
Linux localhost 3.13.0-46-generic #77-Ubuntu SMP
Mon Mar 2 18:23:39 UTC 2015
x86_64 x86_64 x86_64 GNU/Linux

隆重推出 ldd:打印共享的依赖库

$ ldd  redis-3.0.0/src/redis-server
linux-vdso.so.1 =>  (0x00007fffde365000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f307d5aa000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f307d38c000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f307cfc6000)
/lib64/ld-linux-x86-64.so.2 (0x00007f307d8b9000)

将所有需要的 .so 文件打包:

$ tar ztvf rootfs.tar.gz
4485167  2015-04-21 22:54  usr/local/bin/redis-server 
1071552  2015-02-25 16:56  lib/x86_64-linux-gnu/libm.so.6 
141574  2015-02-25 16:56  lib/x86_64-linux-gnu/libpthread.so.0 
1840928  2015-02-25 16:56  lib/x86_64-linux-gnu/libc.so.6 
149120  2015-02-25 16:56  lib64/ld-linux-x86-64.so.2

再制作成 Dockerfile:

FROM scratch 
ADD  rootfs.tar.gz  / 
COPY redis.conf     /etc/redis/redis.conf 
EXPOSE 6379
CMD ["redis-server"]

执行构建:

$ docker build  -t redis-05  .

查看大小:


哇!显著提高啦!

测试一下:

$ docker run -d --name redis-05 redis-05

$ redis-cli  -h  \ 
$(docker inspect -f '{{.NetworkSettings.IPAddress}}' redis-05) 

$ redis-benchmark  -h  \ 
$(docker inspect -f '{{.NetworkSettings.IPAddress}}' redis-05)

总结一下:

1.用 ldd 查出所需的 .so 文件
2.将所有依赖压缩成 rootfs.tar 或 rootfs.tar.gz,之后打进 scratch 基础镜像

 

 

步骤 7: 为 Go 应用构建精简镜像

 

 

Go 语言天生就方便用来构建精简镜像,得益于它能方便的打包成包含静态链接的二进制文件。打个比方,你有一个 4 MB 大小的包含静态链接的 Go 二进制,并且将其打进 scratch 这样的基础镜像,你得到的镜像大小也只有区区的 4 MB。这可是包含同样功能的 Ruby 程序的百分之一啊。这里再给大家介绍一个非常好用开源的 Go 编译工具:golang-builder,并给大家实际演示一个例子。
程序代码:
package main // import "github.com/CenturyLinkLabs/hello"

import "fmt"
func main() {
fmt.Println("Hello World")
}
Dockerfile:
FROM scratch 
COPY hello / 
ENTRYPOINT ["/hello"]
通过 golang-builder打包成镜像:
docker run --rm \
-v $(<strong>pwd</strong>):/src \
-v /var/run/docker.sock:/var/run/docker.sock \
centurylink/golang-builder
查看镜像大小(Mac下测试):
$ docker images 
REPOSITORY   TAG      IMAGE ID       CREATED          VIRTUAL SIZE
hello     latest     1a42948d3224   24 seconds ago         1.59 MB
哇!镜像不到 2 M,基本和 Go 的二进制文件大小一致,这么省力就能创建几 M 大小的镜像,Docker 简直就是为 Go 量身定做的!

总结

1.优化基础镜像
2.串接 Dockerfile 命令
3.压缩 Docker images
4.优化程序依赖
5.选用更合适的开发语言,比如 GO

参考

☛ scratch in Docker Hub
☛ Make FROM scratch a special cased ‘no-base’ spec
☛ vDSO (virtual dynamic shared object)
☛ Small Docker Images For Go Apps (with golang-builder)
☛ Building Docker Images for Static Go Binaries
☛ Go 语言发展历程及应用实践