dockerfile多重构建
多重构建缩小镜像体积
安装gcc环境
显示可用组安装包,就是安装系统时需要选择的那个环境,如最小化
yum group list | more安装开发者工具包
yum -y group install "Development Tools"正常构建
创建hello.c
vi hello.c
/* hello.c */
int main () {
puts("Hello, world!");
return 0;
}执行测试
gcc -o hello hello.c
./hello
Hello World!
ll -h
-rwxr-xr-x 1 root root 8.2K Aug 7 10:47 hello
-rw-r--r-- 1 root root 75 Aug 7 10:48 hello.c并通过下面的 Dockerfile 构建镜像:
vi gcc-Dockerfile
FROM gcc
COPY hello.c .
RUN gcc -o hello hello.c
CMD ["./hello"]
docker build -f gcc-Dockerfile -t hello/gcc:v1.0 .
docker image list | grep hello
hello/gcc v1.0 b6ecbe2d7270 2 months ago 1.23GB
docker run hello/gcc:v1.0
Hello World!可以看到构建成功的镜像体积远远超过了 1G因为该镜像包含了整个gcc镜像的内容,而我们正常的文件大小也不超过10kb
vi ubuntu
FROM ubuntu
RUN apt-get update
RUN apt-get install -y gcc
COPY hello.c .
RUN gcc hello.c -o hello
CMD ["./hello"]
docker build -f ubuntu -t hello/ubuntu:v1.0 .
docker image list | grep hello
hello/ubuntu v1.0 440e56f34d20 22 seconds ago 279MB
docker run hello/ubuntu:v1.0
Hello World!如果使用ubuntu镜像,安装 C 编译器,最后编译程序,你会得到一个大概300mb大小的镜像,比上面的镜像小多了,但还是不够小
多重构建
Ubuntu二次构建
vi ubuntu-Dockerfile
FROM gcc
COPY hello.c .
RUN gcc -o hello hello.c
FROM ubuntu
COPY --from=0 hello .
CMD ["./hello"]
docker build -f ubuntu-Dockerfile -t hello/gcc/ubuntu:v1.0 .
docker image list | grep hello
hello/gcc/ubuntu v1.0 fcbdadbe8023 2 months ago 72.8MB
hello/gcc v1.0 b6ecbe2d7270 2 months ago 1.23GB
docker run hello/gcc:v1.0
Hello World!使用基础镜像gcc来编译hello.c,然后开启一个新的编译阶段,使用Ubuntu作为基础镜像,将可执行文件从一阶段拷贝到最终的镜像中,最后的镜像大小是72.8MB,对比直接使用gcc的1.23GB镜像大小减少了95%左右
还能不能继续优化,当然可以
ll -h
-rwxr-xr-x 1 root root 8.2K Aug 7 10:47 hello
-rw-r--r-- 1 root root 75 Aug 7 10:48 hello.c可以看到我们正常的文件大小也不超过10kb,那么我们到底能不能将镜像缩减到这么小?能否构建一个只包含我需要的程序,没有任何多余文件的镜像?
答案是肯定的,你只需要将多阶段构建的第二阶段的基础镜像改为scratch就好了。scratch是一个虚拟镜像,不能被 pull,也不能运行,因为它表示空、nothing!这就意味着新镜像的构建是从零开始,不存在其他的镜像层
scratch二次构建
vi scratch-Dockerfile
FROM gcc
COPY hello.c .
RUN gcc -o hello hello.c -static
FROM scratch
COPY --from=0 hello .
CMD ["./hello"]
docker build -f scratch-Dockerfile -t hello/gcc/scratch:v1.0 .
docker image list | grep hello
hello/gcc/scratch v1.0 de40656df6bb 2 months ago 910kB
hello/gcc/ubuntu v1.0 fcbdadbe8023 2 months ago 72.8MB
hello/gcc v1.0 b6ecbe2d7270 2 months ago 1.23GB
docker run hello/gcc/scratch:v1.0
Hello World!可以看到我们这次的镜像已经小到1mb左右,可能你想说也没有将镜像缩小到真的只有10kb左右,别急看我再编译时加入了-static参数。因为真的使用scratch参数又很多坑
缺少缺少 libc
在编译时你会遇到缺少库的问题,都知道c语言程序使用库文件,但是scratch完全是空的不包含这些,所以说我们要将所需要的库加入
使用 gcc 作为编译器,只需加上一个参数 -static
编译完的可执行文件大小为910kb,相比纯文件8.3kb是大了好多,这是因为可执行文件中包含了其运行所需要的库文件。编译完的程序就可以跑在 scratch镜像中了
缺少 shell
如果你使用类似这样的作为输出,那么也会遇到报错,因为缺少bin/sh,使用这两种会调用bin/sh进行输出
CMD ./hello
CMD /bin/sh -c "./hello"所以说我们使用这种写法,这样docker就可以直接调用输出,而不用bin/sh
CMD ["./hello"]缺少调试工具
scratch因为什么都没有,一切可以使用的命令都没有,你也无法像正常机器可以将CMD行改为这样进入系统进行调试,他什么都没有是空的,也无法doker exec进入调试
CMD ["sh", "-c", "./hello && tail -f /dev/null"]所以说为了解决这种问题,我们可以用一些定制的小镜像镜像多重构建,如busybox或 alpine等,他们也仅比scratch多了几mb,但多出不少可用的调试工具,从整体来看,这只是牺牲了少量的空间来换取调试的便利性,还是很值得的
alpine二次构建
vi alpine-Dockerfile
FROM alpine
RUN apk add build-base
COPY hello.c .
RUN gcc -o hello hello.c -static
FROM alpine
COPY --from=0 hello .
CMD ["./hello"]
docker build -f alpine-Dockerfile -t hello/gcc/alpine:v1.0 .
docker image list | grep hello
hello/gcc/alpine v1.0 cd5a10bb5059 2 months ago 6.5MB
hello/gcc/scratch v1.0 de40656df6bb 2 months ago 910kB
hello/gcc/ubuntu v1.0 fcbdadbe8023 2 months ago 72.8MB
hello/gcc v1.0 b6ecbe2d7270 2 months ago 1.23GB
docker run hello/gcc/alpine:v1.0
Hello World!聪明的你或许发现了,为什么alpine还是使用-static参数,也是缺少库么,并不是它使用的是 musl libc,这个库相比于 glibc 更小、更简单、更安全,但是与大家常用的标准库 glibc 并不兼容。
你可能又要问了:既然 musl libc 更小、更简单,还特么更安全,为啥其他发行版还在用 glibc?
因为 glibc 有很多额外的扩展,并且很多程序都用到了这些扩展,而 musl libc 是不包含这些扩展的。也就是说,如果想让程序跑在 Alpine 镜像中,必须在编译时使用 musl libc 作为动态库。我们这里就先还是使用静态库
好的看到现在的镜像已经这么小了而且还具备一定的,是不是已经没办法优化了呢?
聪明的你或许又发现了,既然是基础镜像大小会影响最终的构建大小,为什么不一开始就使用alpine构建呢
当然可以
全阶段alpine构建
vi alpineall-Dockerfile
FROM alpine
RUN apk add build-base
COPY hello.c .
RUN gcc -o hello hello.c
FROM alpine
COPY --from=0 hello .
CMD ["./hello"]
docker build -f alpineall-Dockerfile -t hello/gccalpineall:v1.0 .
docker image list | grep hello
hello/gccalpineall v1.0 25393e5f620c 2 months ago 5.6MB
hello/gcc/alpine v1.0 cd5a10bb5059 2 months ago 6.5MB
hello/gcc/scratch v1.0 de40656df6bb 2 months ago 910kB
hello/gcc/ubuntu v1.0 fcbdadbe8023 2 months ago 72.8MB
hello/gcc v1.0 b6ecbe2d7270 2 months ago 1.23GB
docker run hello/gccalpineall:v1.0
Hello World!可以看到使用全阶段alpine镜像大小又有进一步的缩小,而且由于使用alpine作为基础镜像编译hello.c连使用静态库的问题也一带解决了。
当然你也看到了,没有安装gcc的编译器使用了build-base,这是因为如果安装 gcc,就只有编译器,没有标准库。build-base 相当于 Ubuntu 的 build-essentials,引入了编译器、标准库和 make 之类的工具。
alpine是基于Ubuntu的精简镜像
甚至我们可以使用小镜像+scratch构建出一个相当小的镜像出来
alpine+scratch构建
vi alpine2-Dockerfile
FROM alpine
RUN apk add build-base
COPY hello.c .
RUN gcc -o hello hello.c
FROM scratch
COPY --from=0 hello .
CMD ["./hello"]
docker build -f alpine2-Dockerfile -t hello/alpine/scratch:v1.0 .
docker image list | grep hello
hello/alpine/scratch v1.0 c235d9823e4a 2 months ago 83kB
hello/gccalpineall v1.0 25393e5f620c 2 months ago 5.6MB
hello/gcc/alpine v1.0 cd5a10bb5059 2 months ago 6.5MB
hello/gcc/scratch v1.0 de40656df6bb 2 months ago 910kB
hello/gcc/ubuntu v1.0 fcbdadbe8023 2 months ago 72.8MB
hello/gcc v1.0 b6ecbe2d7270 2 months ago 1.23GB
docker run hello/alpine/scratch:v1.0
Hello World!至于为什么不用scratch+scratch,scratch本身就是空的,没办法编译hello.c,除非你手写一个系统进去
到现在基于多重构建减少镜像体积的常用方法就差不多了,如果需要可以灵活使用多重构建来使镜像大小变小
镜像大小对比
docker image list | grep hello
hello/alpine/scratch v1.0 c235d9823e4a 2 months ago 83kB
hello/gccalpineall v1.0 25393e5f620c 2 months ago 5.6MB
hello/gcc/alpine v1.0 cd5a10bb5059 2 months ago 6.5MB
hello/gcc/scratch v1.0 de40656df6bb 2 months ago 910kB
hello/gcc/ubuntu v1.0 fcbdadbe8023 2 months ago 72.8MB
hello/gcc v1.0 b6ecbe2d7270 2 months ago 1.23GB