编写 helloworld.c
1
2
3
4
5
6
|
#include <stdio.h>
int main() {
printf("Hello, World!");
return 0;
}
|
Dockerfile
在同一目录下,创建 Dockerfile 文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
FROM debian:bookworm
# 安装编译器
RUN apt update
RUN apt install gcc -y
# 把 C 代码复制进来
COPY helloworld.c /helloworld.c
# 编译
RUN gcc helloworld.c -o helloworld
CMD ["./helloworld"]
|
假设我们要在一个什么环境都没有的服务器中运行 c 代码,首先需要安装编译器,再将代码复制进来,最后编译运行。
Dockerfile 中也是如此,
FROM debian:bookworm 表示新构建的镜像以 Debian Bookworm 操作系统环境为基础,这个镜像通常包含了基本的系统工具和库文件。
RUN apt update 更新包列表。
RUN apt install gcc -y 安装 gcc 编译器。
COPY helloworld.c /helloworld.c 将本地的 helloworld.c 文件复制到镜像中的 /helloworld.c。
RUN gcc helloworld.c -o helloworld 使用 gcc 编译镜像中的 helloworld.c 文件,生成可执行文件 helloworld。
CMD ["./helloworld"] 设置容器启动时运行的命令,即运行生成的 helloworld 可执行文件。
问题 1 : RUN apt install gcc -y 和 COPY helloworld.c /helloworld.c 两条指令的顺序可以调换吗?
不能。Docker 会按照 Dockerfile 中指令的顺序,依次执行每一条指令;同时利用缓存机制来加速构建过程,如果某条指令已经成功执行过,并且其上下文没有变化,Docker 就会直接使用缓存,而不是重新执行该指令。上述两条指令顺序调换后,如果 helloworld.c 文件的内容发生了变化,就会重新执行安装 gcc 的指令,造成了时间和资源的浪费。
问题 2 : 为什么不能直接把本机编译好的可执行文件放入镜像中?
构建镜像 运行容器
1
|
docker build . -t dockerpractice
|
1
|
docker run --rm dockerpractice
|
发现问题
1
2
3
|
❯ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
dockerpractice latest 4d25a09e669d About a minute ago 392MB
|
发现单单一个输出 “Hello, World!” 的镜像,就有392MB的大小,可见存在很大的优化空间。回过头看,Dockerfile 中 RUN apt install gcc -y 安装了 gcc 编译器及依赖库,而我的代码只需要用到 c 编译器的那一部分,因此想要减小镜像的体积,可以使用 Multi-Stage bulid ,在第一个阶段安装 gcc 编译器,编译好代码,然后在第二个阶段只复制编译好的文件。
参考:
-
Why and How to Reduce Your Docker Image Size ?
-
Multi-stage 官方文档
解决方案
重新改写 Dockerfile 文件如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
FROM debian:bookworm AS build
RUN mkdir -p /var/c-app
WORKDIR /var/c-app
# 装编译器
RUN apt update
RUN apt install gcc -y
# 把 C 代码复制进来
COPY helloworld.c /helloworld.c
# 编译
RUN gcc /helloworld.c -o /var/c-app/helloworld
FROM scratch
COPY --from=build /var/c-app/helloworld /helloworld
CMD ["/helloworld"]
|
但是运行容器时得到报错信息如下。
1
|
exec /helloworld: no such file or directory
|
报错信息显示的是没有找到 helloworld 这个文件,为了方便 debug,先将第二阶段的镜像改为 alpine:3.20,在镜像中开 shell 检查文件的位置。
1
|
docker run --rm -it dockerpractice /bin/sh
|
1
2
|
/ # ls -l /helloworld
-rwxr-xr-x 1 root root 70440 Oct 17 08:17 /helloworld
|
发现文件是存在的,报错信息有误导,使用 ldd 列出该可执行文件在运行时需要加载的共享库文件及其路径。
ldd prints the shared objects (shared libraries) required by each program or shared object specified on the command line.
1
2
3
|
/ # ldd helloworld
/lib/ld-linux-aarch64.so.1 (0xffffaae33000)
libc.so.6 => /lib/ld-linux-aarch64.so.1 (0xffffaae33000)
|
因此,报错信息并不是指找不到这个文件,而是找不到文件运行时依赖的库。
第一个解决方案是将依赖的共享库文件也复制到镜像中。
1
2
3
4
5
|
FROM scratch
COPY --from=build /lib/ld-linux-aarch64.so.1 /lib/ld-linux-aarch64.so.1
COPY --from=build /lib/aarch64-linux-gnu/libc.so.6 /lib/aarch64-linux-gnu/libc.so.6
COPY --from=build /var/c-app/helloworld /helloworld
CMD ["/helloworld"]
|
1
2
|
❯ docker run --rm dockerpractice
Hello, World!
|
1
2
3
|
❯ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
dockerpractice latest 61733b7ae7c7 3 minutes ago 1.92MB
|
镜像大小降到了 1.92MB。
gcc 默认为动态编译,因此第二个解决方案可以是在编译时,将其改为静态编译。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
FROM debian:bookworm AS build
RUN mkdir -p /var/c-app
WORKDIR /var/c-app
# 装编译器
RUN apt update
RUN apt install gcc -y
# 把 C 代码复制进来
COPY helloworld.c /helloworld.c
# 编译
RUN gcc /helloworld.c -o /var/c-app/helloworld -static
FROM scratch
COPY --from=build /var/c-app/helloworld /helloworld
CMD ["/helloworld"]
|
1
2
|
❯ docker run --rm dockerpractice
Hello, World!
|
1
2
3
|
❯ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
dockerpractice latest 61733b7ae7c7 3 minutes ago 703kB
|
最终镜像大小降到了 703kB。