Docker 学习记录 1: 容器化用 c 语言写的 "Hello, World" 应用程序

考虑到 c 语言是编译型语言,在容器化过程中,需要确保容器内有适当的编译环境,这增加了镜像的大小和构建时间,要优化这个问题需要掌握更多的技巧,因此选择从这里开始学起。

编写 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 -yCOPY 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 编译器,编译好代码,然后在第二个阶段只复制编译好的文件。

参考:

  1. Why and How to Reduce Your Docker Image Size ?

  2. 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。

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy