Insun Lee

命由己造,相由心生,世间万物皆是化相,心不动,万物皆不动,心不变,万物皆不变
WechatinsunLee
Email insunsgmail.com
Country China
LocationHangZhou, ZheJiang

golang程序如何构建尽可能小的docker镜像?


docker的镜像构建技术

本人推荐的docker镜像构建方法:

  1. 利用多阶构建避免构前期构建环节产生的垃圾文件;
  2. 如果可行则尽量利用alpine作为构建阶段的基础镜像,将busybox作为运行阶段的基础镜像,以获得镜像大小和维护便利性的折中方案;
  3. 是否采用镜像压缩(upx),则取决于您对启动时间的要求和镜像大小的考虑。

下文将逐步介绍以scratch, busybox, alpine为基础镜像进行构建程序和分阶构建方法。

docker镜像的分层构建

docker镜像利用分层构建技术,一层一层叠加构建,前一层作为后一层的构建基础。因此我们在构建镜像时,应特别小心,每一层都应只包含我们需的东西,一些构建环节的中间环境和工具应该在构建完成后删除。

实际上我们在构建镜像时,都会从某一基础镜像开始构建,比如php运行环境的构建:

FROM php:7.4-fpm-alpine3.11

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories 
    && apk --no-cache add --virtual .build-deps 
        $PHPIZE_DEPS 
        openssl-dev 
    && apk --no-cache add 
        freetype-dev 
        libjpeg-turbo-dev 
        libpng-dev  
        tzdata 
    # 设置时区
    && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 
    && echo "Asia/Shanghai" > /etc/timezone 
    # 安装拓展
    && docker-php-ext-configure gd --with-freetype --with-jpeg 
    && docker-php-ext-install -j$(nproc) gd 
    && docker-php-ext-install pdo pdo_mysql opcache exif 
    && pecl install redis 
    && docker-php-ext-enable redis 
    && rm -rf /tem/pear 
    && apk del .build-deps

WORKDIR /www/web

其中FROM php:7.4-fpm-alpine3.11的意思就是从php 7.4-fpm-alpine3.11基础镜像开始构建。apk --no-cache add --virtual .build-deps是创建一个中间构建环境,并在构建完成后删除apk del .build-deps

scratch镜像

docker有个特殊的镜像scratch空镜像。他可作为任何不需要依赖环境的可执行程序的基础镜像。具体来说,对于linux下静态编译的程序来说,并不需要操作系统提供的运行时支持,所需的一切都已经包含在可执行文件中了,比如使用go语言开发的很多应用会使用直接FROM scratch的方式制作镜像,这样制作后的镜像大小通常只有可执行程序本身的容量。

下面是go写的一个简单的hellow world web服务器:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World, your ip: %sn", r.RemoteAddr)
    })

    http.ListenAndServe(":80", nil)
}

编译linux环境下的无依赖可执行程序

  1. 将文件保存为main.go
  2. 执行 GOOS=linux CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o helloworld main.go

将下面内容保存到与上面main.go相同目录的Dockerfile文件中:

FROM scratch
ADD helloworld /
CMD ["/helloworld"]

程序目录结构:

.
├── Dockerfile
├── helloworld
└── main.go

构建docker镜像: docker build -t hellow .
运行docker镜像:docker run -ti --rm -p 80:80 hellow
测试:curl http://localhost/,返回:Hello World, your ip: 172.17.0.1:60996 说明镜像构建成功.

查看镜像大小:

$ docker image ls
REPOSITORY      TAG                  IMAGE ID       CREATED         SIZE
hellow          latest               9c7e438ba30d   6 minutes ago   6.39MB

查看构建后的运用程序大小:

$ ls -lh
-rwxr-xr-x  1 Lee  staff   6.1M Dec 28 09:06 helloworld

可见构建后的docker镜像只比运用程序大0.3m不到。

busybox基础镜像

以scratch为基础镜像构建是有很大的限制性的:由于它是个空镜像因此没有任何系统支持,您无法进入容器,没有时区、没有安全证书等,在后期的维护上非常的不方便。所以我本人并不推荐用scratch作为基础镜像构建。

实际上有个镜像busybox也是非常小的,它提供了最基本的系统命令支持,比如:sh, cat, top等:

$ docker image ls
busybox         latest               6858809bf669   3 months ago     1.23MB

修改Dockerfile,用busybox构建:

FROM busybox
ADD helloworld /
CMD ["/helloworld"]

构建新的镜像:docker build -t hellow-busybox .
查看镜像大小:

$ docker image ls 
REPOSITORY       TAG                  IMAGE ID       CREATED          SIZE
hellow-busybox   latest               6cd8b90e985d   11 seconds ago   7.63MB
hellow           latest               9c7e438ba30d   31 minutes ago   6.39MB

测试:docker run -it --rm --name hellow hellow-busybox
在新的命令行窗口进入程序:docker exec -it hellow /bin/sh
执行top命令看看:

$ top
CPU:  0.0% usr  0.4% sys  0.0% nic 99.5% idle  0.0% io  0.0% irq  0.0% sirq
Load average: 0.00 0.00 0.00 1/363 25
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
   25    18 root     R     1316  0.0   0  0.1 top
    1     0 root     S     689m 34.6   1  0.0 /helloworld
   18     0 root     S     1320  0.0   1  0.0 /bin/sh

alpine基础镜像

alpine是基于busybox的又一基础镜像。下面是关于Alpine的介绍:

Alpine 操作系统是一个面向安全的轻型 Linux 发行版。它不同于通常 Linux 发行版,Alpine 采用了 musl libc 和 busybox 以减小系统的体积和运行时资源消耗,但功能上比 busybox 又完善的多,因此得到开源社区越来越多的青睐。在保持瘦身的同时,Alpine 还提供了自己的包管理工具 apk,可以通过https://pkgs.alpinelinux.org/packages 网站上查询包信息,也可以直接通过 apk 命令直接查询和安装各种软件。

Alpine 由非商业组织维护的,支持广泛场景的 Linux发行版,它特别为资深/重度Linux用户而优化,关注安全,性能和资源效能。Alpine 镜像可以适用于更多常用场景,并且是一个优秀的可以适用于生产的基础系统/环境。

Alpine Docker 镜像也继承了 Alpine Linux 发行版的这些优势。相比于其他 Docker 镜像,它的容量非常小,仅仅只有 5 MB 左右(对比 Ubuntu 系列镜像接近 200 MB),且拥有非常友好的包管理机制。官方镜像来自 docker-alpine 项目。

目前 Docker 官方已开始推荐使用 Alpine 替代之前的 Ubuntu 做为基础镜像环境。这样会带来多个好处。包括镜像下载速度加快,镜像安全性提高,主机之间的切换更方便,占用更少磁盘空间等。

Alpine镜像大小:

$ docker image ls
busybox         latest               6858809bf669   3 months ago     1.23MB
alpine          latest               a24bb4013296   7 months ago     5.57MB

利用alpine构建hellow world测试程序:

FROM alpine
ADD helloworld /
CMD ["/helloworld"]

编译:docker build -t hellow-alpine .
查看镜像大小:

$ docker image ls
hellow-alpine    latest               4814dea089b3   About a minute ago   12MB
hellow-busybox   latest               6cd8b90e985d   8 minutes ago        7.63MB
hellow           latest               9c7e438ba30d   39 minutes ago       6.39MB

镜像分阶构建技术

Docker 17.05版本以后,官方就提供了一个新的特性:Multi-stage builds(多阶段构建)。 使用多阶段构建,你可以在一个 Dockerfile中使用多个 FROM语句。每个 FROM指令都可以使用不同的基础镜像,并表示开始一个新的构建阶段。你可以很方便的将一个阶段的文件复制到另外一个阶段,在最终的镜像中保留下你需要的内容即可。

docker镜像在构建时,可以利用upx进行压缩以减小docker镜像大小。下面增加upx压缩镜像技术,来说明分阶构建:

修改Dockerfile*:

FROM alpine as builder
ADD helloworld /
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && 
    apk --no-cache add upx 
    && upx /helloworld

FROM busybox as runner
COPY --from=builder /helloworld /helloworld
CMD ["/helloworld"]

编译:docker build -t hellow-upx .
查看镜像大小:

$docker image ls
hellow-upx       latest               e22aadba7290   3 minutes ago    4.7MB
hellow-alpine    latest               4814dea089b3   13 minutes ago   12MB
hellow-busybox   latest               6cd8b90e985d   19 minutes ago   7.63MB
hellow           latest               9c7e438ba30d   51 minutes ago   6.39MB

可以看到利用分阶构建进行压缩的镜像文件是最小的。上面Dockerfile的要点在于:

  1. 将镜像的压缩(如果有其他操作,比如需要时区、certificates等也可以),放在构筑阶段获取;
  2. 将最终的编译结果复制到执行层以减小构筑中产生的垃圾文件或者其他不必要的环境。
需要注意的是,upx固然会使镜像文件大小大大瘦身,但是在镜像运行时需额外的cpu资源和时间用于脱壳(解压),因此是否采用upx取决于您。

拓展阅读:Why Golang?

Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.

喜欢一门编程语言、一个人、一件事可以有N种理由,讨厌它亦是如此。从我个人的角度来说,下面的几个理由足够让我在适当的场景下选择它:

  1. 学习成本低,golang的语法简单易学,对我来说可以拿python来比较;
  2. 跨平台,golang写的程序交叉编译后可以运行于windows、linux、macosx、freebsd等;
  3. 单文件,golang写的程序,可以将所有的依赖包直接编译到程序中——一个文件扔到对应的平台就可运行;
  4. 高性能,golang号称接近c的性能,实际在使用过程中,性能确实非常棒;
  5. docker本身就是go写的,因此golang程序可以很好的运行于docker,关于docker信息可以参考wiki信息Docker
  • 分享:
评论

    • 博主

    说点什么