Docker体积优化

cc 发布于2020年10月24日 ∣ 约1338字 · 需3分钟 阅读()

写在docker未死前

docker虽好,体积难料,兴致勃勃来跑个hello world结果生成了1G的镜像,直接浇灭胸中热火

我们的理想要求是镜像体积跟可执行文件大小相当

为什么生成的镜像体积那么大?对于直接上来构建镜像的都是直接拉一个流行的基础镜像,然后在镜像中安装编译工具链。流行的镜像一般都是经典系统CentOs、Debian、Fedora、Ubuntu等镜像,相对来说环境熟悉,工具丰富,对于新手来说只需要写代码,按常规流程写构建过程就好,正是因为内置了很多实际跑项目时不需要的工具,占用了许多空间;有时候编译时还依赖编译器等其他系统没内置的工具,也会被拉取内置到镜像中;编译过程中如果没有清理中间生成文件也会占用空间。所以主要从两个方面优化,选择一个小的镜像作为基础镜像,去掉编译工具链及生成的中间件

目前小镜像有alpine跟busybox,另外还有个虚拟镜像scratch,当然这些公具也有一些问题,最重要的是缺少编译工具,不可能装一遍工具来处理

不管怎样我们绕不过编译过程,好在推出了多阶段构建,可以多个FROM来标志各个构建阶段

1FROM gcc AS stage0					#初始构建,从gcc镜像开始构建
2COPY hello.c .
3RUN gcc -o hello hello.c
4FROM ubuntu									#第二次阶段,从ubuntu开始构建
5COPY --from=stage0 hello .	#将第一阶段编译的文件复制到本地
6#COPY --from=0 hello .			#可以直接用0开始的序号引用阶段
7CMD ["./hello"]							#

AS stage0可以不写,阶段从0开始递增。为了方便维护还是用AS命名各阶段比较好

1FROM golang
2WORKDIR /app							#指定工作目录(默认root),会自动创建目录
3COPY . .								  #"."表示当前目录,从本地目录拷到镜像工作目录中
4ARG GOPROXY="https://goproxy.cn"	#代理加速下载依赖包
5ARG CGO_ENABLED=0					#禁用c,防止动态连接调用
6RUN go build
7FROM alpine
8COPY --from=0 /app/xxx .	#from指定来源阶段,将单体执行文件xxx考到根目录下
9CMD ["./xxx"]							#根目录下执行xxx,前面的"."可以去掉

使用go.mod管理模块的项目,生成的文件名是go.mod文件中的module名字,build中指定生成名字也没有用。

上面的分阶段构建是先用工具链完整的镜像对源码进行编译打包成可执行文件,再将执行文件添加到一个最小镜像里,实现体积的缩小。docker里面的编译默认是动态连接的,不加条件参数限制其静态编译的话会导致standard_init_linux.go:211: exec user process caused "no such file or directory”错误。scratch是个虚拟镜像没有任何添加文件,busybox不含标准库,alpine使用的musllibc跟标准库glibc不兼容。所以选用这三个的时候需要注意下动态连接库的问题。一般是直接静态编译,一个是拷贝文件到镜像中

1#c语言可以添加-static来静态编译
2gcc -o hello hello.c -static
3#go
4CGO_ENABLED=0  #禁用c

虽然scratchs对体积没影响,但是也是因为什么工具都没带,很不方便调试,用不了shell之类的工具

busybox基于scratch,alpine基于busybox

查看镜像文件

1#构建成功,运行失败时
2dive image_name		# 镜像比较大时加载时间会比较长
3#容器运行成功后可使用
4docker exec container_name ls	#不进入容器运行命令查看文件
5#没有启动时
6docker -it image_name /bin/sh

对于编译型项目,使用小体积镜像确实有用。对于脚本型项目,本身执行环境就很大了,安装一些库以后非常大,小镜像非但不能减少太多体积,还因为环境缺失造成库的安装失败。