Administrator
发布于 2025-03-20 / 0 阅读
0

dockerfile多重构建

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