本文共 2170 字,大约阅读时间需要 7 分钟。
对于刚接触 Docker 的开发者来说,看到镜像体积达到几百 MB甚至更高,可能会感到惊讶。毕竟,最后只需要一个小小的可执行文件。但为什么镜像体积会这么大?且慢,让我们一起来探讨几个优化镜像体积的奇妙技巧。
每个刚接触 Docker 的开发者都会遇到这种状况。让我们以一个简单的 Hello World 程序为例:
int main () { puts("Hello, world!"); return 0;} 如果你用下面的 Dockerfile 构建镜像:
FROM gccCOPY hello.c .RUN gcc -o hello hello.cCMD ["./hello"]
你会发现镜像体积远远超过 1 GB。原因在于,这个镜像包含了整个 gcc 编译器的内容。这就是 Docker 镜像的本质:它继承了所有父镜像的内容,包括编译器、库文件、系统文件等。
举个例子,用 Ubuntu 镜像构建容器:
FROM ubuntuCOPY hello.c .RUN gcc -o hello hello.cCMD ["./hello"]
镜像体积会降到 300 MB左右。但这种优化还不够小,因为实际只需要不到 20 KB 的可执行文件!如果你用 Go 语言的镜像构建:
package mainimport "fmt"func main() { fmt.Println("Hello, world!")} 镜像体积会达到 800 MB,而编译后的可执行文件大约 2 MB。这明显还有优化空间。
为了显著减少镜像体积,多阶段构建 是绝佳选择。通过将复杂的构建逻辑分解成多个阶段,只保留最终需要的内容。你可以尝试用 FROM 指令定义不同的阶段:
FROM gcc AS gcc-stageCOPY hello.c .RUN gcc -o hello hello.cFROM ubuntu AS final-stageCOPY --from=gcc-stage hello .CMD ["./hello"]
这个例子中,镜像体积降到了 64 MB,相比原始镜像减少了超过 95%。
小技巧:
COPY --from=0 ./hello .),无需显式指定阶段名称。COPY --from=0 ./hello .。更优镜像选择:
WORKDIR,并在拷贝时使用绝对路径。如果你真心想要将镜像体积压到最小,可以考虑使用 FROM scratch。这种镜像起名称为 scratch,表示一个空虚拟镜像,用于从零构建新镜像。
FROM golangCOPY hello.go .RUN go build hello.goFROM scratchCOPY --from=0 /go/hello .CMD ["./hello"]
这样构建出来的镜像体积仅为 2 MB。然而,这种方法也有些不足:
严重缺陷:
FROM scratchCOPY --from=0 /go/hello .CMD ["./hello"]
docker exec 或其他调试工具,网络堆栈信息不可查看。为了解决上述问题,可以选择一些额外镜像作为替代:
使用 busybox 或 alpine 镜像-- 这些镜像比 scratch 更实用,虽然体积稍大(大约 5 MB),但提供了丰富的系统工具和调试环境。
静态链接-- 如果程序不依赖动态链接库,可以使用静态链接方式编译:
gcc -o hello hello.c -static
这样生成的可执行文件仅 760 KB,适合运行于 scratch 镜像中。
通过多阶段构建和镜像优化,我们可以显著减少镜像体积。以下是不同镜像构建方法的对比:
| 镜像类型 | 镜像体积 | 优化方法 |
|---|---|---|
| 原始镜像 | 1.14 GB | 无优化 |
| Ubuntu 多阶段 | 64.2 MB | 多阶段构建 |
| Alpine 静态 glibc | 6.5 MB | 多阶段构建 + 静态编译 |
| Alpine 动态库 | 5.6 MB | 多阶段构建 + 动态库 |
| Scratch 静态 glibc | 940 KB | FROM scratch + 静态编译 |
| Scratch musl libc | 94 KB | FROM scratch + musl |
通过这些优化技巧,可以将镜像体积从 1 GB 减少到百 KB,甚至更小!虽然使用 FROM scratch 显得简洁,但如果需要调试环境,建议选择 busybox 或 alpine 镜像。希望这些优化技巧能帮你轻松减小镜像体积,同时保持开发和运维的便利性!
转载地址:http://shjxz.baihongyu.com/