从头构建 100kB Docker 镜像
📓要点
你可能觉得 100MB 的 Alpine 镜像已经很小了——但 100kB 的镜像呢?更小的镜像传输速度更快,而且包含的攻击向量更少。此外,通过优化镜像,你可以发现并精准地隔离应用运行所需的资源。
让我们进行优化。
基于 scratch 的 docker 镜像有两个关键特征:
- Dockerfile 有两个构建阶段:
- 构建器——包含所有构建依赖项,包括源、库和工具等。
- 最终映像,包含二进制文件和任何运行时依赖项(配置文件、证书和动态链接库)
- 最终的镜像是
FROM scratch
——空的docker镜像
通过这种方法,您的运行时映像将包含应用程序运行所需的全部内容 - 无需可能配置错误或被利用的额外配置文件、守护程序或库。
让我们先来看一下一个基本的静态 Hello World 临时镜像。我们将使用 C 语言,因为它所需的依赖最少,并且生成的 ELF 目标二进制文件非常小。
创建 Hello World
hello.c
#include <stdio.h>
int main(void){
puts("Hello World\n");
}
Makefile
hello: hello.c
gcc -o $@ $< -static
Dockerfile
.dockerignore
# this prevents our host binary from sneaking into the build
hello
Dockerfile
FROM alpine:latest as builder
WORKDIR /build
RUN apk update && \
apk add gcc make libc-dev
COPY . ./
RUN make
FROM scratch
WORKDIR /
COPY --from=builder /build/hello .
CMD ["/hello"]
注意,我们有两个FROM
s,其中一个叫做builder
。最终的镜像将是我们的运行器。使用COPY --from
我们可以指定哪些文件需要放入暂存镜像。对于静态 ELF 二进制文件,我们只需要/build/hello
构建并运行
$ docker build . -t hello-scratch|tail -n 1
Successfully tagged hello-scratch:latest
$ docker run hello-scratch
Hello World
我们的最终图像为 82.7kB
$ docker images |grep hello-scratch | egrep -o '[^ ]+$'
82.7kB
里面有什么?
使用docker save
您可以检查图像并发现它只layer.tar
包含一个文件:hello
$ mkdir hello-scratch && cd hello-scratch
$ docker save hello-scratch |tar -x
$ ls
3e69d91b5842be72dcd4175adcf218a03f78826504be6a46ed41c099e97520e8.json e599e214ce17b356493f9524fa57f7ef816d21dd78020019196020c770a39954 manifest.json repositories
$ ✗ tar -tf e599e214ce17b356493f9524fa57f7ef816d21dd78020019196020c770a39954/layer.tar
hello
后续步骤
在接下来的文章中,我将展示一些更复杂的示例。对于 C、C++、golang 和 Rust 等静态编译的应用程序来说,这个过程最简单。但是,只要使用合适的工具,假设您将所有运行时依赖项都收集到最终scratch
镜像中,就可以构建任何镜像。