docker镜像瘦身
参考:https://mp.weixin.qq.com/s/hn8Env3e5PztLgOCAYZamA
在构建 docker 容器时,我们一般希望尽量减小镜像,以便加快镜像的分发;但是不恰当的镜像构建方式,很容易导致镜像过大,造成带宽和磁盘资源浪费,尤其是遇到 daemonset 这种需要在每台机器上拉取镜像的服务,会造成大量资源浪费;而且镜像过大还会影响服务的启动速度,尤其是处理紧急线上镜像变更时,直接影响变更的速度。如果不是刻意控制镜像大小、注意镜像瘦身,一般的业务系统中可能 90% 以上的大镜像都存在镜像空间浪费的现象(不信可以尝试检测看看)。因此我们非常有必要了解镜像瘦身方法,减小容器镜像。
一、判断镜像是否需要瘦身
通常,我们可能都是在容器镜像过大,明显影响到镜像上传/拉取速度时,才会考虑到分析镜像,尝试镜像瘦身。此时采用的多是 docker image history 等 docker 自带的镜像分析命令,以查看镜像构建历史、镜像大小在各层的分布等。然后根据经验判断是否存在空间浪费,但是这种判断方式起点较高、没有量化,不方便自动化判断。当前,社区中也有很多镜像分析工具,其中比较流行的 dive 分析工具,就可以量化给出容器镜像有效率、镜像空间浪费率等指标。
# curl -OL https://github.com/wagoodman/dive/releases/download/v0.9.1/dive_0.9.1_linux_amd64.rpm # rpm -ivh dive_0.9.1_linux_amd64.rpm # dive mysql:lastest
采用 dive 对一个 mysql 镜像进行效率分析,发现镜像有效率只有 41%,镜像空间浪费率高达 59%,显然需要瘦身。
二、镜像瘦身方法
2.1 使用Alpine Linux
Alpine Linux是一个基于BusyBox和Musl Libc的Linux发行版,其最大的优势就是小。一个纯的基础Alpine Docker镜像在压缩后仅有2.67MB
2.2多阶段构建
所谓多阶段构建,实际上是允许在一个 Dockerfile 中出现多个 FROM 指令。最后生成的镜像,以最后一条 FROM 构建阶段为准,之前的 FROM 构建阶段会被抛弃。通过多阶段构建,后一个阶段的构建过程可以直接利用前一阶段的构建缓存,有效降低镜像大小。一个典型的场景是将编译环境和运行环境分离,以一个 go 项目镜像构建过程为例:
# Go语言编译环境基础镜像 FROM golang:1.16-alpine # 拷贝源码到镜像 COPY server.go /build/ # 指定工作目录 WORKDIR /build # 编译镜像时,运行 go build 编译生成 server 程序 RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags '-w -s' -o server # 指定容器运行时入口程序 ENTRYPOINT ["/build/server"]
这种传统的构建方式有以下缺点:
- 基础镜像为支持编译环境,包含大量go语言的工具/库,而运行时并不需要。
- COPY 源码,增加了镜像分层,同时有源码泄漏风险。
采用多阶段构建方式,可以将上述传统的构建方式修改如下:
## 1 编译构建阶段 # Go语言编译环境基础镜像 FROM golang:1.16-alpine AS build # 拷贝源码到镜像 COPY server.go /build/ # 指定工作目录 WORKDIR /build # 编译镜像时,运行 go build 编译生成 server 程序 RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags '-w -s' -o server ## 2 运行构建阶段 # 采用更小的运行时基础镜像 FROM scratch # 从编译阶段仅拷贝所需的编译结果到当前镜像中 COPY --from=build /build/server /build/server # 指定容器运行时入口程序 ENTRYPOINT ["/build/server"]
可以看到,使用多阶段构建,可以获取如下好处:
- 最终镜像只关心运行时,采用了更小的基础镜像。
- 直接拷贝上一个编译阶段的编译结果,减少了镜像分层,还避免了源码泄漏。
2.3减少镜像分层
镜像的层就像 Git 的提交(commit)一样,用于保存镜像的当前版本与上一版本之间的差异,但是镜像层会占用空间,拥有的层越多,最终的镜像就越大。在构建镜像时,RUN, ADD, COPY 指令对应的层会增加镜像大小,其他命令并不会增加最终的镜像大小。
可以尝试合并相关指令,以减小镜像分层;COPY 指令转换合并到 RUN 指令